rust_template.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. use crate::VERSION;
  2. use anchor_cli::rust_template::{get_or_create_program_id, ProgramTemplate};
  3. use anchor_cli::{create_files, Files};
  4. use anchor_syn::idl::types::{
  5. Idl, IdlArrayLen, IdlDefinedFields, IdlGenericArg, IdlType, IdlTypeDef, IdlTypeDefGeneric,
  6. IdlTypeDefTy,
  7. };
  8. use anyhow::Result;
  9. use heck::{ToSnakeCase, ToUpperCamelCase};
  10. use std::path::{Path, PathBuf};
  11. // Anchor CLI version
  12. // TODO: use the stable version once the new IDL standard is released
  13. pub const ANCHOR_CLI_VERSION: &str =
  14. "{ git = { version = \"0.29.0\", \"https://github.com/coral-xyz/anchor.git\", rev = \"0f60909\" }";
  15. pub const TS_ANCHOR_VERSION: &str = "0.29.1";
  16. /// Create a component from the given name.
  17. pub fn create_component(name: &str) -> Result<()> {
  18. let program_path = Path::new("programs-ecs/components").join(name);
  19. let common_files = vec![
  20. (
  21. PathBuf::from("Cargo.toml".to_string()),
  22. workspace_manifest().to_string(),
  23. ),
  24. (program_path.join("Cargo.toml"), cargo_toml(name)),
  25. (program_path.join("Xargo.toml"), xargo_toml().to_string()),
  26. ] as Files;
  27. let template_files = create_component_template_simple(name, &program_path);
  28. anchor_cli::create_files(&[common_files, template_files].concat())
  29. }
  30. /// Create a system from the given name.
  31. pub(crate) fn create_system(name: &str) -> Result<()> {
  32. let program_path = Path::new("programs-ecs/systems").join(name);
  33. let common_files = vec![
  34. (
  35. PathBuf::from("Cargo.toml".to_string()),
  36. workspace_manifest().to_string(),
  37. ),
  38. (program_path.join("Cargo.toml"), cargo_toml_with_serde(name)),
  39. (program_path.join("Xargo.toml"), xargo_toml().to_string()),
  40. ] as Files;
  41. let template_files = create_system_template_simple(name, &program_path);
  42. anchor_cli::create_files(&[common_files, template_files].concat())
  43. }
  44. /// Create an anchor program
  45. pub fn create_program(name: &str, template: ProgramTemplate) -> Result<()> {
  46. let program_path = Path::new("programs").join(name);
  47. let common_files = vec![
  48. ("Cargo.toml".into(), workspace_manifest().into()),
  49. (program_path.join("Cargo.toml"), cargo_toml(name)),
  50. (program_path.join("Xargo.toml"), xargo_toml().into()),
  51. ];
  52. let template_files = match template {
  53. ProgramTemplate::Single => create_program_template_single(name, &program_path),
  54. ProgramTemplate::Multiple => create_program_template_multiple(name, &program_path),
  55. };
  56. create_files(&[common_files, template_files].concat())
  57. }
  58. /// Create a component which holds position data.
  59. fn create_component_template_simple(name: &str, program_path: &Path) -> Files {
  60. vec![(
  61. program_path.join("src").join("lib.rs"),
  62. format!(
  63. r#"use bolt_lang::*;
  64. declare_id!("{}");
  65. #[component]
  66. #[derive(Default)]
  67. pub struct {} {{
  68. pub x: i64,
  69. pub y: i64,
  70. pub z: i64,
  71. #[max_len(20)]
  72. pub description: String,
  73. }}
  74. "#,
  75. anchor_cli::rust_template::get_or_create_program_id(name),
  76. name.to_upper_camel_case(),
  77. ),
  78. )]
  79. }
  80. /// Create a system which operates on a Position component.
  81. fn create_system_template_simple(name: &str, program_path: &Path) -> Files {
  82. vec![(
  83. program_path.join("src").join("lib.rs"),
  84. format!(
  85. r#"use bolt_lang::*;
  86. use position::Position;
  87. declare_id!("{}");
  88. #[system]
  89. pub mod {} {{
  90. pub fn execute(ctx: Context<Components>, _args_p: Vec<u8>) -> Result<Components> {{
  91. let position = &mut ctx.accounts.position;
  92. position.x += 1;
  93. position.y += 1;
  94. Ok(ctx.accounts)
  95. }}
  96. #[system_input]
  97. pub struct Components {{
  98. pub position: Position,
  99. }}
  100. }}
  101. "#,
  102. anchor_cli::rust_template::get_or_create_program_id(name),
  103. name.to_snake_case(),
  104. ),
  105. )]
  106. }
  107. fn create_program_template_single(name: &str, program_path: &Path) -> Files {
  108. vec![(
  109. program_path.join("src").join("lib.rs"),
  110. format!(
  111. r#"use anchor_lang::prelude::*;
  112. declare_id!("{}");
  113. #[program]
  114. pub mod {} {{
  115. use super::*;
  116. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
  117. Ok(())
  118. }}
  119. }}
  120. #[derive(Accounts)]
  121. pub struct Initialize {{}}
  122. "#,
  123. get_or_create_program_id(name),
  124. name.to_snake_case(),
  125. ),
  126. )]
  127. }
  128. /// Create a program with multiple files for instructions, state...
  129. fn create_program_template_multiple(name: &str, program_path: &Path) -> Files {
  130. let src_path = program_path.join("src");
  131. vec![
  132. (
  133. src_path.join("lib.rs"),
  134. format!(
  135. r#"pub mod constants;
  136. pub mod error;
  137. pub mod instructions;
  138. pub mod state;
  139. use anchor_lang::prelude::*;
  140. pub use constants::*;
  141. pub use instructions::*;
  142. pub use state::*;
  143. declare_id!("{}");
  144. #[program]
  145. pub mod {} {{
  146. use super::*;
  147. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
  148. initialize::handler(ctx)
  149. }}
  150. }}
  151. "#,
  152. get_or_create_program_id(name),
  153. name.to_snake_case(),
  154. ),
  155. ),
  156. (
  157. src_path.join("constants.rs"),
  158. r#"use anchor_lang::prelude::*;
  159. #[constant]
  160. pub const SEED: &str = "anchor";
  161. "#
  162. .into(),
  163. ),
  164. (
  165. src_path.join("error.rs"),
  166. r#"use anchor_lang::prelude::*;
  167. #[error_code]
  168. pub enum ErrorCode {
  169. #[msg("Custom error message")]
  170. CustomError,
  171. }
  172. "#
  173. .into(),
  174. ),
  175. (
  176. src_path.join("instructions").join("mod.rs"),
  177. r#"pub mod initialize;
  178. pub use initialize::*;
  179. "#
  180. .into(),
  181. ),
  182. (
  183. src_path.join("instructions").join("initialize.rs"),
  184. r#"use anchor_lang::prelude::*;
  185. #[derive(Accounts)]
  186. pub struct Initialize {}
  187. pub fn handler(ctx: Context<Initialize>) -> Result<()> {
  188. Ok(())
  189. }
  190. "#
  191. .into(),
  192. ),
  193. (src_path.join("state").join("mod.rs"), r#""#.into()),
  194. ]
  195. }
  196. const fn workspace_manifest() -> &'static str {
  197. r#"[workspace]
  198. members = [
  199. "programs/*",
  200. "programs-ecs/components/*",
  201. "programs-ecs/systems/*"
  202. ]
  203. resolver = "2"
  204. [profile.release]
  205. overflow-checks = true
  206. lto = "fat"
  207. codegen-units = 1
  208. [profile.release.build-override]
  209. opt-level = 3
  210. incremental = false
  211. codegen-units = 1
  212. "#
  213. }
  214. pub fn package_json(jest: bool) -> String {
  215. if jest {
  216. format!(
  217. r#"{{
  218. "scripts": {{
  219. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  220. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  221. }},
  222. "dependencies": {{
  223. "@magicblock-labs/anchor": "^{TS_ANCHOR_VERSION}"
  224. }},
  225. "devDependencies": {{
  226. "jest": "^29.0.3",
  227. "prettier": "^2.6.2"
  228. }}
  229. }}
  230. "#
  231. )
  232. } else {
  233. format!(
  234. r#"{{
  235. "scripts": {{
  236. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  237. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  238. }},
  239. "dependencies": {{
  240. "@magicblock-labs/anchor": "^{TS_ANCHOR_VERSION}"
  241. }},
  242. "devDependencies": {{
  243. "chai": "^4.3.4",
  244. "mocha": "^9.0.3",
  245. "prettier": "^2.6.2",
  246. "@metaplex-foundation/beet": "^0.7.1",
  247. "@metaplex-foundation/beet-solana": "^0.4.0",
  248. "@magicblock-labs/bolt-sdk": "latest"
  249. }}
  250. }}
  251. "#
  252. )
  253. }
  254. }
  255. pub fn ts_package_json(jest: bool) -> String {
  256. if jest {
  257. format!(
  258. r#"{{
  259. "scripts": {{
  260. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  261. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  262. }},
  263. "dependencies": {{
  264. "@magicblock-labs/anchor": "^{TS_ANCHOR_VERSION}"
  265. }},
  266. "devDependencies": {{
  267. "@types/bn.js": "^5.1.0",
  268. "@types/jest": "^29.0.3",
  269. "jest": "^29.0.3",
  270. "prettier": "^2.6.2",
  271. "ts-jest": "^29.0.2",
  272. "typescript": "^4.3.5",
  273. "@metaplex-foundation/beet": "^0.7.1",
  274. "@metaplex-foundation/beet-solana": "^0.4.0",
  275. "@magicblock-labs/bolt-sdk": "latest"
  276. }}
  277. }}
  278. "#
  279. )
  280. } else {
  281. format!(
  282. r#"{{
  283. "scripts": {{
  284. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  285. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  286. }},
  287. "dependencies": {{
  288. "@magicblock-labs/anchor": "^{TS_ANCHOR_VERSION}"
  289. }},
  290. "devDependencies": {{
  291. "chai": "^4.3.4",
  292. "mocha": "^9.0.3",
  293. "ts-mocha": "^10.0.0",
  294. "@types/bn.js": "^5.1.0",
  295. "@types/chai": "^4.3.0",
  296. "@types/mocha": "^9.0.0",
  297. "typescript": "^4.3.5",
  298. "prettier": "^2.6.2",
  299. "@metaplex-foundation/beet": "^0.7.1",
  300. "@metaplex-foundation/beet-solana": "^0.4.0",
  301. "@magicblock-labs/bolt-sdk": "latest"
  302. }}
  303. }}
  304. "#
  305. )
  306. }
  307. }
  308. pub fn mocha(name: &str) -> String {
  309. format!(
  310. r#"const anchor = require("@magicblock-labs/anchor");
  311. const boltSdk = require("@magicblock-labs/bolt-sdk");
  312. const {{
  313. InitializeNewWorld,
  314. }} = boltSdk;
  315. describe("{}", () => {{
  316. // Configure the client to use the local cluster.
  317. const provider = anchor.AnchorProvider.env();
  318. anchor.setProvider(provider);
  319. it("InitializeNewWorld", async () => {{
  320. const initNewWorld = await InitializeNewWorld({{
  321. payer: provider.wallet.publicKey,
  322. connection: provider.connection,
  323. }});
  324. const txSign = await provider.sendAndConfirm(initNewWorld.transaction);
  325. console.log(`Initialized a new world (ID=${{initNewWorld.worldPda}}). Initialization signature: ${{txSign}}`);
  326. }});
  327. }});
  328. }});
  329. "#,
  330. name,
  331. )
  332. }
  333. pub fn jest(name: &str) -> String {
  334. format!(
  335. r#"const anchor = require("@magicblock-labs/anchor");
  336. const boltSdk = require("@magicblock-labs/bolt-sdk");
  337. const {{
  338. InitializeNewWorld,
  339. }} = boltSdk;
  340. describe("{}", () => {{
  341. // Configure the client to use the local cluster.
  342. const provider = anchor.AnchorProvider.env();
  343. anchor.setProvider(provider);
  344. // Constants used to test the program.
  345. let worldPda: PublicKey;
  346. it("InitializeNewWorld", async () => {{
  347. const initNewWorld = await InitializeNewWorld({{
  348. payer: provider.wallet.publicKey,
  349. connection: provider.connection,
  350. }});
  351. const txSign = await provider.sendAndConfirm(initNewWorld.transaction);
  352. worldPda = initNewWorld.worldPda;
  353. console.log(`Initialized a new world (ID=${{worldPda}}). Initialization signature: ${{txSign}}`);
  354. }});
  355. }});
  356. "#,
  357. name,
  358. )
  359. }
  360. pub fn ts_mocha(name: &str) -> String {
  361. format!(
  362. r#"import * as anchor from "@magicblock-labs/anchor";
  363. import {{ Program }} from "@magicblock-labs/anchor";
  364. import {{ PublicKey }} from "@solana/web3.js";
  365. import {{ Position }} from "../target/types/position";
  366. import {{ Movement }} from "../target/types/movement";
  367. import {{
  368. InitializeNewWorld,
  369. AddEntity,
  370. InitializeComponent,
  371. ApplySystem,
  372. FindComponentPda,
  373. }} from "@magicblock-labs/bolt-sdk"
  374. import {{expect}} from "chai";
  375. describe("{}", () => {{
  376. // Configure the client to use the local cluster.
  377. const provider = anchor.AnchorProvider.env();
  378. anchor.setProvider(provider);
  379. // Constants used to test the program.
  380. let worldPda: PublicKey;
  381. let entityPda: PublicKey;
  382. const positionComponent = anchor.workspace.Position as Program<Position>;
  383. const systemMovement = anchor.workspace.Movement as Program<Movement>;
  384. it("InitializeNewWorld", async () => {{
  385. const initNewWorld = await InitializeNewWorld({{
  386. payer: provider.wallet.publicKey,
  387. connection: provider.connection,
  388. }});
  389. const txSign = await provider.sendAndConfirm(initNewWorld.transaction);
  390. worldPda = initNewWorld.worldPda;
  391. console.log(`Initialized a new world (ID=${{worldPda}}). Initialization signature: ${{txSign}}`);
  392. }});
  393. it("Add an entity", async () => {{
  394. const addEntity = await AddEntity({{
  395. payer: provider.wallet.publicKey,
  396. worldPda: worldPda,
  397. connection: provider.connection,
  398. }});
  399. const txSign = await provider.sendAndConfirm(addEntity.transaction);
  400. entityPda = addEntity.entityPda;
  401. console.log(`Initialized a new Entity (ID=${{addEntity.entityId}}). Initialization signature: ${{txSign}}`);
  402. }});
  403. it("Add a component", async () => {{
  404. const initComponent = await InitializeComponent({{
  405. payer: provider.wallet.publicKey,
  406. entityPda,
  407. componentId: positionComponent.programId,
  408. }});
  409. const txSign = await provider.sendAndConfirm(initComponent.transaction);
  410. console.log(`Initialized the grid component. Initialization signature: ${{txSign}}`);
  411. }});
  412. it("Apply a system", async () => {{
  413. const positionComponentPda = FindComponentPda(positionComponent.programId, entityPda);
  414. // Check that the component has been initialized and x is 0
  415. let positionData = await positionComponent.account.position.fetch(
  416. positionComponentPda
  417. );
  418. const applySystem = await ApplySystem({{
  419. authority: provider.wallet.publicKey,
  420. boltSystem: systemMovement.programId,
  421. entityPda,
  422. components: [positionComponent.programId],
  423. }});
  424. const txSign = await provider.sendAndConfirm(applySystem.transaction);
  425. console.log(`Applied a system. Signature: ${{txSign}}`);
  426. // Check that the system has been applied and x is > 0
  427. positionData = await positionComponent.account.position.fetch(
  428. positionComponentPda
  429. );
  430. expect(positionData.x.toNumber()).to.gt(0);
  431. }});
  432. }});
  433. "#,
  434. name.to_upper_camel_case(),
  435. )
  436. }
  437. fn cargo_toml(name: &str) -> String {
  438. format!(
  439. r#"[package]
  440. name = "{0}"
  441. version = "{2}"
  442. description = "Created with Bolt"
  443. edition = "2021"
  444. [lib]
  445. crate-type = ["cdylib", "lib"]
  446. name = "{1}"
  447. [features]
  448. no-entrypoint = []
  449. no-idl = []
  450. no-log-ix-name = []
  451. cpi = ["no-entrypoint"]
  452. default = []
  453. idl-build = ["anchor-lang/idl-build"]
  454. [dependencies]
  455. bolt-lang = "{2}"
  456. anchor-lang = {3}
  457. "#,
  458. name,
  459. name.to_snake_case(),
  460. VERSION,
  461. // Todo use stable version once new IDL standard is released
  462. //anchor_cli::VERSION,
  463. ANCHOR_CLI_VERSION
  464. )
  465. }
  466. /// TODO: Remove serde dependency
  467. fn cargo_toml_with_serde(name: &str) -> String {
  468. format!(
  469. r#"[package]
  470. name = "{0}"
  471. version = "{2}"
  472. description = "Created with Bolt"
  473. edition = "2021"
  474. [lib]
  475. crate-type = ["cdylib", "lib"]
  476. name = "{1}"
  477. [features]
  478. no-entrypoint = []
  479. no-idl = []
  480. no-log-ix-name = []
  481. cpi = ["no-entrypoint"]
  482. default = []
  483. idl-build = ["anchor-lang/idl-build"]
  484. [dependencies]
  485. bolt-lang = "{2}"
  486. anchor-lang = {3}
  487. serde = {{ version = "1.0", features = ["derive"] }}
  488. "#,
  489. name,
  490. name.to_snake_case(),
  491. VERSION,
  492. // Todo use stable version once new IDL standard is released
  493. //anchor_cli::VERSION,
  494. ANCHOR_CLI_VERSION
  495. )
  496. }
  497. fn xargo_toml() -> &'static str {
  498. r#"[target.bpfel-unknown-unknown.dependencies.std]
  499. features = []
  500. "#
  501. }
  502. pub fn git_ignore() -> &'static str {
  503. r#"
  504. .anchor
  505. .bolt
  506. .DS_Store
  507. target
  508. **/*.rs.bk
  509. node_modules
  510. test-ledger
  511. .yarn
  512. "#
  513. }
  514. pub fn prettier_ignore() -> &'static str {
  515. r#"
  516. .anchor
  517. .bolt
  518. .DS_Store
  519. target
  520. node_modules
  521. dist
  522. build
  523. test-ledger
  524. "#
  525. }
  526. pub fn registry_account() -> &'static str {
  527. r#"
  528. {
  529. "pubkey": "EHLkWwAT9oebVv9ht3mtqrvHhRVMKrt54tF3MfHTey2K",
  530. "account": {
  531. "lamports": 1002240,
  532. "data": [
  533. "L65u9ri2/NoCAAAAAAAAAA==",
  534. "base64"
  535. ],
  536. "owner": "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n",
  537. "executable": false,
  538. "rentEpoch": 18446744073709551615,
  539. "space": 16
  540. }
  541. }
  542. "#
  543. }
  544. /// Automatic generation of crates from the components idl
  545. pub fn component_type(idl: &Idl, component_id: &str) -> Result<String> {
  546. let component_account = idl
  547. .accounts
  548. .iter()
  549. .filter(|a| a.name.to_lowercase() != "Entity")
  550. .last();
  551. let component_account =
  552. component_account.ok_or_else(|| anyhow::anyhow!("Component account not found in IDL"))?;
  553. let type_def = &idl
  554. .types
  555. .iter()
  556. .rfind(|ty| ty.name == component_account.name);
  557. let type_def = match type_def {
  558. Some(ty) => ty,
  559. None => return Err(anyhow::anyhow!("Component type not found in IDL")),
  560. };
  561. let component_code = component_to_rust_code(type_def, component_id);
  562. let types_code = component_types_to_rust_code(&idl.types, &component_account.name);
  563. Ok(format!(
  564. r#"use bolt_lang::*;
  565. #[component_deserialize]
  566. #[derive(Clone, Copy)]
  567. {}
  568. {}
  569. "#,
  570. component_code, types_code
  571. ))
  572. }
  573. /// Convert the component type definition to rust code
  574. fn component_to_rust_code(component: &IdlTypeDef, component_id: &str) -> String {
  575. let mut code = String::new();
  576. // Add documentation comments, if any
  577. for doc in &component.docs {
  578. code += &format!("/// {}\n", doc);
  579. }
  580. // Handle generics
  581. let generics = {
  582. let generic_names: Vec<String> = component
  583. .generics
  584. .iter()
  585. .map(|gen| match gen {
  586. IdlTypeDefGeneric::Type { name } => name.clone(),
  587. IdlTypeDefGeneric::Const { name, .. } => name.clone(),
  588. })
  589. .collect();
  590. if generic_names.is_empty() {
  591. "".to_string()
  592. } else {
  593. format!("<{}>", generic_names.join(", "))
  594. }
  595. };
  596. let composite_name = format!("Component{}", component_id);
  597. if let IdlTypeDefTy::Struct { fields } = &component.ty {
  598. code += &format!("pub struct {}{} {{\n", composite_name, generics);
  599. code += &*component_fields_to_rust_code(fields);
  600. code += "}\n\n";
  601. code += &format!("pub use {} as {};", composite_name, component.name);
  602. }
  603. code
  604. }
  605. /// Code to expose the generated type, to be added to lib.rs
  606. pub fn component_type_import(component_id: &str) -> String {
  607. format!(
  608. r#"#[allow(non_snake_case)]
  609. mod component_{0};
  610. pub use component_{0}::*;
  611. "#,
  612. component_id,
  613. )
  614. }
  615. /// Convert fields to rust code
  616. fn component_fields_to_rust_code(fields: &Option<IdlDefinedFields>) -> String {
  617. let mut code = String::new();
  618. if let Some(fields) = fields {
  619. match fields {
  620. IdlDefinedFields::Named(named_fields) => {
  621. for field in named_fields {
  622. if field.name.to_lowercase() == "bolt_metadata" {
  623. continue;
  624. }
  625. for doc in &field.docs {
  626. code += &format!(" /// {}\n", doc);
  627. }
  628. let field_type = convert_idl_type_to_str(&field.ty);
  629. code += &format!(" pub {}: {},\n", field.name, field_type);
  630. }
  631. }
  632. IdlDefinedFields::Tuple(tuple_types) => {
  633. for (index, ty) in tuple_types.iter().enumerate() {
  634. let field_type = convert_idl_type_to_str(ty);
  635. code += &format!(" pub field_{}: {},\n", index, field_type);
  636. }
  637. }
  638. }
  639. }
  640. code
  641. }
  642. /// Map Idl type to rust type
  643. pub fn convert_idl_type_to_str(ty: &IdlType) -> String {
  644. match ty {
  645. IdlType::Bool => "bool".into(),
  646. IdlType::U8 => "u8".into(),
  647. IdlType::I8 => "i8".into(),
  648. IdlType::U16 => "u16".into(),
  649. IdlType::I16 => "i16".into(),
  650. IdlType::U32 => "u32".into(),
  651. IdlType::I32 => "i32".into(),
  652. IdlType::F32 => "f32".into(),
  653. IdlType::U64 => "u64".into(),
  654. IdlType::I64 => "i64".into(),
  655. IdlType::F64 => "f64".into(),
  656. IdlType::U128 => "u128".into(),
  657. IdlType::I128 => "i128".into(),
  658. IdlType::U256 => "u256".into(),
  659. IdlType::I256 => "i256".into(),
  660. IdlType::Bytes => "bytes".into(),
  661. IdlType::String => "String".into(),
  662. IdlType::Pubkey => "Pubkey".into(),
  663. IdlType::Option(ty) => format!("Option<{}>", convert_idl_type_to_str(ty)),
  664. IdlType::Vec(ty) => format!("Vec<{}>", convert_idl_type_to_str(ty)),
  665. IdlType::Array(ty, len) => format!(
  666. "[{}; {}]",
  667. convert_idl_type_to_str(ty),
  668. match len {
  669. IdlArrayLen::Generic(len) => len.into(),
  670. IdlArrayLen::Value(len) => len.to_string(),
  671. }
  672. ),
  673. IdlType::Defined { name, generics } => generics
  674. .iter()
  675. .map(|generic| match generic {
  676. IdlGenericArg::Type { ty } => convert_idl_type_to_str(ty),
  677. IdlGenericArg::Const { value } => value.into(),
  678. })
  679. .reduce(|mut acc, cur| {
  680. if !acc.is_empty() {
  681. acc.push(',');
  682. }
  683. acc.push_str(&cur);
  684. acc
  685. })
  686. .map(|generics| format!("{name}<{generics}>"))
  687. .unwrap_or(name.into()),
  688. IdlType::Generic(ty) => ty.into(),
  689. }
  690. }
  691. /// Convert the component types definition to rust code
  692. fn component_types_to_rust_code(types: &[IdlTypeDef], component_name: &str) -> String {
  693. types
  694. .iter()
  695. .filter(|ty| ty.name.to_lowercase() != "boltmetadata" && ty.name != component_name)
  696. .map(component_type_to_rust_code)
  697. .collect::<Vec<_>>()
  698. .join("\n")
  699. }
  700. /// Convert the component type definition to rust code
  701. fn component_type_to_rust_code(component_type: &IdlTypeDef) -> String {
  702. let mut code = String::new();
  703. // Add documentation comments, if any
  704. for doc in &component_type.docs {
  705. code += &format!("/// {}\n", doc);
  706. }
  707. // Handle generics
  708. let gen = &component_type.generics;
  709. let generics = {
  710. let generic_names: Vec<String> = gen
  711. .iter()
  712. .map(|gen| match gen {
  713. IdlTypeDefGeneric::Type { name } => name.clone(),
  714. IdlTypeDefGeneric::Const { name, .. } => name.clone(),
  715. })
  716. .collect();
  717. if generic_names.is_empty() {
  718. "".to_string()
  719. } else {
  720. format!("<{}>", generic_names.join(", "))
  721. }
  722. };
  723. if let IdlTypeDefTy::Struct { fields } = &component_type.ty {
  724. code += &format!(
  725. "#[component_deserialize]\n#[derive(Clone, Copy)]\npub struct {}{} {{\n",
  726. component_type.name, generics
  727. );
  728. code += &*component_fields_to_rust_code(fields);
  729. code += "}\n\n";
  730. }
  731. code
  732. }
  733. pub(crate) fn types_cargo_toml() -> String {
  734. let name = "bolt-types";
  735. format!(
  736. r#"[package]
  737. name = "{0}"
  738. version = "{2}"
  739. description = "Autogenerate types for the bolt language"
  740. edition = "2021"
  741. [lib]
  742. crate-type = ["cdylib", "lib"]
  743. name = "{1}"
  744. [dependencies]
  745. bolt-lang = "{2}"
  746. anchor-lang = {3}
  747. "#,
  748. name,
  749. name.to_snake_case(),
  750. VERSION,
  751. // TODO: use the stable version once the new IDL standard is released
  752. //anchor_cli::VERSION,
  753. ANCHOR_CLI_VERSION
  754. )
  755. }