rust_template.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. use crate::ANCHOR_VERSION;
  2. use crate::VERSION;
  3. use anchor_cli::Files;
  4. use anchor_syn::idl::types::{
  5. Idl, IdlDefinedTypeArg, IdlField, IdlType, IdlTypeDefinition, IdlTypeDefinitionTy,
  6. };
  7. use anyhow::Result;
  8. use heck::{ToSnakeCase, ToUpperCamelCase};
  9. use std::path::{Path, PathBuf};
  10. /// Create a component from the given name.
  11. pub fn create_component(name: &str) -> Result<()> {
  12. let program_path = Path::new("programs-ecs/components").join(name);
  13. let common_files = vec![
  14. (
  15. PathBuf::from("Cargo.toml".to_string()),
  16. workspace_manifest().to_string(),
  17. ),
  18. (program_path.join("Cargo.toml"), cargo_toml(name)),
  19. (program_path.join("Xargo.toml"), xargo_toml().to_string()),
  20. ] as Files;
  21. let template_files = create_component_template_simple(name, &program_path);
  22. anchor_cli::create_files(&[common_files, template_files].concat())
  23. }
  24. /// Create a system from the given name.
  25. pub(crate) fn create_system(name: &str) -> Result<()> {
  26. let program_path = Path::new("programs-ecs/systems").join(name);
  27. let common_files = vec![
  28. (
  29. PathBuf::from("Cargo.toml".to_string()),
  30. workspace_manifest().to_string(),
  31. ),
  32. (program_path.join("Cargo.toml"), cargo_toml(name)),
  33. (program_path.join("Xargo.toml"), xargo_toml().to_string()),
  34. ] as Files;
  35. let template_files = create_system_template_simple(name, &program_path);
  36. anchor_cli::create_files(&[common_files, template_files].concat())
  37. }
  38. /// Create a component which holds position data.
  39. fn create_component_template_simple(name: &str, program_path: &Path) -> Files {
  40. vec![(
  41. program_path.join("src").join("lib.rs"),
  42. format!(
  43. r#"use bolt_lang::*;
  44. declare_id!("{}");
  45. #[component]
  46. pub struct {} {{
  47. pub x: i64,
  48. pub y: i64,
  49. pub z: i64,
  50. #[max_len(20)]
  51. pub description: String,
  52. }}
  53. "#,
  54. anchor_cli::rust_template::get_or_create_program_id(name),
  55. name.to_upper_camel_case(),
  56. ),
  57. )]
  58. }
  59. /// Create a system which operates on a Position component.
  60. fn create_system_template_simple(name: &str, program_path: &Path) -> Files {
  61. vec![(
  62. program_path.join("src").join("lib.rs"),
  63. format!(
  64. r#"use bolt_lang::*;
  65. use position::Position;
  66. declare_id!("{}");
  67. #[system]
  68. pub mod {} {{
  69. pub fn execute(ctx: Context<Components>, _args_p: Vec<u8>) -> Result<Components> {{
  70. let position = &mut ctx.accounts.position;
  71. position.x += 1;
  72. position.y += 1;
  73. Ok(ctx.accounts)
  74. }}
  75. #[system_input]
  76. pub struct Components {{
  77. pub position: Position,
  78. }}
  79. }}
  80. "#,
  81. anchor_cli::rust_template::get_or_create_program_id(name),
  82. name.to_snake_case(),
  83. ),
  84. )]
  85. }
  86. const fn workspace_manifest() -> &'static str {
  87. r#"[workspace]
  88. members = [
  89. "programs/*",
  90. "programs-ecs/components/*",
  91. "programs-ecs/systems/*"
  92. ]
  93. resolver = "2"
  94. [profile.release]
  95. overflow-checks = true
  96. lto = "fat"
  97. codegen-units = 1
  98. [profile.release.build-override]
  99. opt-level = 3
  100. incremental = false
  101. codegen-units = 1
  102. "#
  103. }
  104. pub fn package_json(jest: bool) -> String {
  105. if jest {
  106. format!(
  107. r#"{{
  108. "scripts": {{
  109. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  110. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  111. }},
  112. "dependencies": {{
  113. "@coral-xyz/anchor": "^{VERSION}"
  114. }},
  115. "devDependencies": {{
  116. "jest": "^29.0.3",
  117. "prettier": "^2.6.2"
  118. }}
  119. }}
  120. "#
  121. )
  122. } else {
  123. format!(
  124. r#"{{
  125. "scripts": {{
  126. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  127. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  128. }},
  129. "dependencies": {{
  130. "@coral-xyz/anchor": "^{VERSION}"
  131. }},
  132. "devDependencies": {{
  133. "chai": "^4.3.4",
  134. "mocha": "^9.0.3",
  135. "prettier": "^2.6.2",
  136. "@metaplex-foundation/beet": "^0.7.1",
  137. "@metaplex-foundation/beet-solana": "^0.4.0",
  138. "@magicblock-labs/bolt-sdk": "latest"
  139. }}
  140. }}
  141. "#
  142. )
  143. }
  144. }
  145. pub fn ts_package_json(jest: bool) -> String {
  146. if jest {
  147. format!(
  148. r#"{{
  149. "scripts": {{
  150. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  151. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  152. }},
  153. "dependencies": {{
  154. "@coral-xyz/anchor": "^{ANCHOR_VERSION}"
  155. }},
  156. "devDependencies": {{
  157. "@types/bn.js": "^5.1.0",
  158. "@types/jest": "^29.0.3",
  159. "jest": "^29.0.3",
  160. "prettier": "^2.6.2",
  161. "ts-jest": "^29.0.2",
  162. "typescript": "^4.3.5",
  163. "@metaplex-foundation/beet": "^0.7.1",
  164. "@metaplex-foundation/beet-solana": "^0.4.0",
  165. "@magicblock-labs/bolt-sdk": "latest"
  166. }}
  167. }}
  168. "#
  169. )
  170. } else {
  171. format!(
  172. r#"{{
  173. "scripts": {{
  174. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  175. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  176. }},
  177. "dependencies": {{
  178. "@coral-xyz/anchor": "^{ANCHOR_VERSION}"
  179. }},
  180. "devDependencies": {{
  181. "chai": "^4.3.4",
  182. "mocha": "^9.0.3",
  183. "ts-mocha": "^10.0.0",
  184. "@types/bn.js": "^5.1.0",
  185. "@types/chai": "^4.3.0",
  186. "@types/mocha": "^9.0.0",
  187. "typescript": "^4.3.5",
  188. "prettier": "^2.6.2",
  189. "@metaplex-foundation/beet": "^0.7.1",
  190. "@metaplex-foundation/beet-solana": "^0.4.0",
  191. "@magicblock-labs/bolt-sdk": "latest"
  192. }}
  193. }}
  194. "#
  195. )
  196. }
  197. }
  198. pub fn mocha(name: &str) -> String {
  199. format!(
  200. r#"const anchor = require("@coral-xyz/anchor");
  201. const boltSdk = require("@magicblock-labs/bolt-sdk");
  202. const {{
  203. createInitializeNewWorldInstruction,
  204. FindWorldPda,
  205. FindWorldRegistryPda,
  206. Registry,
  207. World
  208. }} = boltSdk;
  209. describe("{}", () => {{
  210. // Configure the client to use the local cluster.
  211. const provider = anchor.AnchorProvider.env();
  212. anchor.setProvider(provider);
  213. it("InitializeNewWorld", async () => {{
  214. const registry = await Registry.fromAccountAddress(provider.connection, registryPda);
  215. worldId = new anchor.BN(registry.worlds);
  216. worldPda = FindWorldPda(new anchor.BN(worldId))
  217. const initializeWorldIx = createInitializeNewWorldInstruction(
  218. {{
  219. world: worldPda,
  220. registry: registryPda,
  221. payer: provider.wallet.publicKey,
  222. }});
  223. const tx = new anchor.web3.Transaction().add(initializeWorldIx);
  224. const txSign = await provider.sendAndConfirm(tx);
  225. console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`);
  226. }});
  227. }});
  228. }});
  229. "#,
  230. name,
  231. )
  232. }
  233. pub fn jest(name: &str) -> String {
  234. format!(
  235. r#"const anchor = require("@coral-xyz/anchor");
  236. const boltSdk = require("@magicblock-labs/bolt-sdk");
  237. const {{
  238. createInitializeNewWorldInstruction,
  239. FindWorldPda,
  240. FindWorldRegistryPda,
  241. Registry,
  242. World
  243. }} = boltSdk;
  244. describe("{}", () => {{
  245. // Configure the client to use the local cluster.
  246. const provider = anchor.AnchorProvider.env();
  247. anchor.setProvider(provider);
  248. // Constants used to test the program.
  249. const registryPda = FindWorldRegistryPda();
  250. let worldId: anchor.BN;
  251. let worldPda: PublicKey;
  252. it("InitializeNewWorld", async () => {{
  253. const registry = await Registry.fromAccountAddress(provider.connection, registryPda);
  254. worldId = new anchor.BN(registry.worlds);
  255. worldPda = FindWorldPda(new anchor.BN(worldId))
  256. const initializeWorldIx = createInitializeNewWorldInstruction(
  257. {{
  258. world: worldPda,
  259. registry: registryPda,
  260. payer: provider.wallet.publicKey,
  261. }});
  262. const tx = new anchor.web3.Transaction().add(initializeWorldIx);
  263. const txSign = await provider.sendAndConfirm(tx);
  264. console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`);
  265. }});
  266. }});
  267. "#,
  268. name,
  269. )
  270. }
  271. pub fn ts_mocha(name: &str) -> String {
  272. format!(
  273. r#"import * as anchor from "@coral-xyz/anchor";
  274. import {{ Program }} from "@coral-xyz/anchor";
  275. import {{ PublicKey }} from "@solana/web3.js";
  276. import {{ Position }} from "../target/types/position";
  277. import {{ Movement }} from "../target/types/movement";
  278. import {{
  279. createInitializeNewWorldInstruction,
  280. FindWorldPda,
  281. FindWorldRegistryPda,
  282. FindEntityPda,
  283. Registry,
  284. World,
  285. createAddEntityInstruction,
  286. createInitializeComponentInstruction,
  287. FindComponentPda, createApplyInstruction
  288. }} from "@magicblock-labs/bolt-sdk"
  289. import {{expect}} from "chai";
  290. describe("{}", () => {{
  291. // Configure the client to use the local cluster.
  292. const provider = anchor.AnchorProvider.env();
  293. anchor.setProvider(provider);
  294. // Constants used to test the program.
  295. const registryPda = FindWorldRegistryPda();
  296. let worldId: anchor.BN;
  297. let worldPda: PublicKey;
  298. let entityPda: PublicKey;
  299. const positionComponent = anchor.workspace.Position as Program<Position>;
  300. const systemMovement = anchor.workspace.Movement as Program<Movement>;
  301. it("InitializeNewWorld", async () => {{
  302. const registry = await Registry.fromAccountAddress(provider.connection, registryPda);
  303. worldId = new anchor.BN(registry.worlds);
  304. worldPda = FindWorldPda(new anchor.BN(worldId))
  305. const initializeWorldIx = createInitializeNewWorldInstruction(
  306. {{
  307. world: worldPda,
  308. registry: registryPda,
  309. payer: provider.wallet.publicKey,
  310. }});
  311. const tx = new anchor.web3.Transaction().add(initializeWorldIx);
  312. const txSign = await provider.sendAndConfirm(tx);
  313. console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`);
  314. }});
  315. it("Add an entity", async () => {{
  316. const world = await World.fromAccountAddress(provider.connection, worldPda);
  317. const entityId = new anchor.BN(world.entities);
  318. entityPda = FindEntityPda(worldId, entityId);
  319. let createEntityIx = createAddEntityInstruction({{
  320. world: worldPda,
  321. payer: provider.wallet.publicKey,
  322. entity: entityPda,
  323. }});
  324. const tx = new anchor.web3.Transaction().add(createEntityIx);
  325. const txSign = await provider.sendAndConfirm(tx);
  326. console.log(`Initialized a new Entity (ID=${{worldId}}). Initialization signature: ${{txSign}}`);
  327. }});
  328. it("Add a component", async () => {{
  329. const positionComponentPda = FindComponentPda(positionComponent.programId, entityPda, "");
  330. let initComponentIx = createInitializeComponentInstruction({{
  331. payer: provider.wallet.publicKey,
  332. entity: entityPda,
  333. data: positionComponentPda,
  334. componentProgram: positionComponent.programId,
  335. }});
  336. const tx = new anchor.web3.Transaction().add(initComponentIx);
  337. const txSign = await provider.sendAndConfirm(tx);
  338. console.log(`Initialized a new component. Initialization signature: ${{txSign}}`);
  339. }});
  340. it("Apply a system", async () => {{
  341. const positionComponentPda = FindComponentPda(positionComponent.programId, entityPda, "");
  342. // Check that the component has been initialized and x is 0
  343. let positionData = await positionComponent.account.position.fetch(
  344. positionComponentPda
  345. );
  346. expect(positionData.x.toNumber()).to.eq(0);
  347. let applySystemIx = createApplyInstruction({{
  348. componentProgram: positionComponent.programId,
  349. boltSystem: systemMovement.programId,
  350. boltComponent: positionComponentPda,
  351. authority: provider.wallet.publicKey,
  352. }}, {{args: new Uint8Array()}});
  353. const tx = new anchor.web3.Transaction().add(applySystemIx);
  354. await provider.sendAndConfirm(tx);
  355. // Check that the system has been applied and x is > 0
  356. positionData = await positionComponent.account.position.fetch(
  357. positionComponentPda
  358. );
  359. expect(positionData.x.toNumber()).to.gt(0);
  360. }});
  361. }});
  362. "#,
  363. name.to_upper_camel_case(),
  364. )
  365. }
  366. fn cargo_toml(name: &str) -> String {
  367. format!(
  368. r#"[package]
  369. name = "{0}"
  370. version = "{2}"
  371. description = "Created with Bolt"
  372. edition = "2021"
  373. [lib]
  374. crate-type = ["cdylib", "lib"]
  375. name = "{1}"
  376. [features]
  377. no-entrypoint = []
  378. no-idl = []
  379. no-log-ix-name = []
  380. cpi = ["no-entrypoint"]
  381. default = []
  382. idl-build = ["anchor-lang/idl-build"]
  383. [dependencies]
  384. bolt-lang = "{2}"
  385. anchor-lang = "{3}"
  386. "#,
  387. name,
  388. name.to_snake_case(),
  389. VERSION,
  390. anchor_cli::VERSION,
  391. )
  392. }
  393. fn xargo_toml() -> &'static str {
  394. r#"[target.bpfel-unknown-unknown.dependencies.std]
  395. features = []
  396. "#
  397. }
  398. pub fn git_ignore() -> &'static str {
  399. r#"
  400. .anchor
  401. .bolt
  402. .DS_Store
  403. target
  404. **/*.rs.bk
  405. node_modules
  406. test-ledger
  407. .yarn
  408. "#
  409. }
  410. pub fn prettier_ignore() -> &'static str {
  411. r#"
  412. .anchor
  413. .bolt
  414. .DS_Store
  415. target
  416. node_modules
  417. dist
  418. build
  419. test-ledger
  420. "#
  421. }
  422. pub fn registry_account() -> &'static str {
  423. r#"
  424. {
  425. "pubkey": "EHLkWwAT9oebVv9ht3mtqrvHhRVMKrt54tF3MfHTey2K",
  426. "account": {
  427. "lamports": 1002240,
  428. "data": [
  429. "L65u9ri2/NoCAAAAAAAAAA==",
  430. "base64"
  431. ],
  432. "owner": "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n",
  433. "executable": false,
  434. "rentEpoch": 18446744073709551615,
  435. "space": 16
  436. }
  437. }
  438. "#
  439. }
  440. /// Automatic generation of crates from the components idl
  441. pub fn component_type(idl: &Idl, component_id: &str) -> Result<String> {
  442. let component_account = &idl.accounts[0];
  443. let component_code = component_to_rust_code(component_account, component_id);
  444. let types_code = component_types_to_rust_code(&idl.types);
  445. Ok(format!(
  446. r#"use bolt_lang::*;
  447. #[component_deserialize]
  448. #[derive(Clone, Copy)]
  449. {}
  450. {}
  451. "#,
  452. component_code, types_code
  453. ))
  454. }
  455. /// Convert the component type definition to rust code
  456. fn component_to_rust_code(component: &IdlTypeDefinition, component_id: &str) -> String {
  457. let mut code = String::new();
  458. // Add documentation comments, if any
  459. if let Some(docs) = &component.docs {
  460. for doc in docs {
  461. code += &format!("/// {}\n", doc);
  462. }
  463. }
  464. // Handle generics
  465. let generics = if let Some(gen) = &component.generics {
  466. format!("<{}>", gen.join(", "))
  467. } else {
  468. String::new()
  469. };
  470. let composite_name = format!("Component{}", component_id);
  471. if let IdlTypeDefinitionTy::Struct { fields } = &component.ty {
  472. code += &format!("pub struct {}{} {{\n", composite_name, generics);
  473. code += &*component_fields_to_rust_code(fields);
  474. code += "}\n\n";
  475. code += &format!("pub use {} as {};", composite_name, component.name);
  476. }
  477. code
  478. }
  479. /// Code to expose the generated type, to be added to lib.rs
  480. pub fn component_type_import(component_id: &str) -> String {
  481. format!(
  482. r#"#[allow(non_snake_case)]
  483. mod component_{0};
  484. pub use component_{0}::*;
  485. "#,
  486. component_id,
  487. )
  488. }
  489. /// Convert fields to rust code
  490. fn component_fields_to_rust_code(fields: &[IdlField]) -> String {
  491. let mut code = String::new();
  492. for field in fields {
  493. // Skip BoltMetadata field, as it is added automatically
  494. if field.name.to_lowercase() == "boltmetadata" {
  495. continue;
  496. }
  497. if let Some(docs) = &field.docs {
  498. for doc in docs {
  499. code += &format!(" /// {}\n", doc);
  500. }
  501. }
  502. let field_type = idl_type_to_rust_type(&field.ty);
  503. code += &format!(" pub {}: {},\n", field.name, field_type);
  504. }
  505. code
  506. }
  507. /// Map Idl type to rust type
  508. fn idl_type_to_rust_type(idl_type: &IdlType) -> String {
  509. match idl_type {
  510. IdlType::Bool => "bool".to_string(),
  511. IdlType::U8 => "u8".to_string(),
  512. IdlType::I8 => "i8".to_string(),
  513. IdlType::U16 => "u16".to_string(),
  514. IdlType::I16 => "i16".to_string(),
  515. IdlType::U32 => "u32".to_string(),
  516. IdlType::I32 => "i32".to_string(),
  517. IdlType::F32 => "f32".to_string(),
  518. IdlType::U64 => "u64".to_string(),
  519. IdlType::I64 => "i64".to_string(),
  520. IdlType::F64 => "f64".to_string(),
  521. IdlType::U128 => "u128".to_string(),
  522. IdlType::I128 => "i128".to_string(),
  523. IdlType::U256 => "U256".to_string(),
  524. IdlType::I256 => "I256".to_string(),
  525. IdlType::Bytes => "Vec<u8>".to_string(),
  526. IdlType::String => "String".to_string(),
  527. IdlType::PublicKey => "PublicKey".to_string(),
  528. IdlType::Defined(name) => name.clone(),
  529. IdlType::Option(ty) => format!("Option<{}>", idl_type_to_rust_type(ty)),
  530. IdlType::Vec(ty) => format!("Vec<{}>", idl_type_to_rust_type(ty)),
  531. IdlType::Array(ty, size) => format!("[{}; {}]", idl_type_to_rust_type(ty), size),
  532. IdlType::GenericLenArray(ty, _) => format!("Vec<{}>", idl_type_to_rust_type(ty)),
  533. IdlType::Generic(name) => name.clone(),
  534. IdlType::DefinedWithTypeArgs { name, args } => {
  535. let args_str = args
  536. .iter()
  537. .map(idl_defined_type_arg_to_rust_type)
  538. .collect::<Vec<_>>()
  539. .join(", ");
  540. format!("{}<{}>", name, args_str)
  541. }
  542. }
  543. }
  544. /// Map type args
  545. fn idl_defined_type_arg_to_rust_type(arg: &IdlDefinedTypeArg) -> String {
  546. match arg {
  547. IdlDefinedTypeArg::Generic(name) => name.clone(),
  548. IdlDefinedTypeArg::Value(value) => value.clone(),
  549. IdlDefinedTypeArg::Type(ty) => idl_type_to_rust_type(ty),
  550. }
  551. }
  552. /// Convert the component types definition to rust code
  553. fn component_types_to_rust_code(types: &[IdlTypeDefinition]) -> String {
  554. types
  555. .iter()
  556. // Skip BoltMetadata type, as it is added automatically
  557. .filter(|ty| ty.name.to_lowercase() != "boltmetadata")
  558. .map(component_type_to_rust_code)
  559. .collect::<Vec<_>>()
  560. .join("\n")
  561. }
  562. /// Convert the component type definition to rust code
  563. fn component_type_to_rust_code(component_type: &IdlTypeDefinition) -> String {
  564. let mut code = String::new();
  565. // Add documentation comments, if any
  566. if let Some(docs) = &component_type.docs {
  567. for doc in docs {
  568. code += &format!("/// {}\n", doc);
  569. }
  570. }
  571. // Handle generics
  572. let generics = if let Some(gen) = &component_type.generics {
  573. format!("<{}>", gen.join(", "))
  574. } else {
  575. String::new()
  576. };
  577. if let IdlTypeDefinitionTy::Struct { fields } = &component_type.ty {
  578. code += &format!(
  579. "#[component_deserialize]\n#[derive(Clone, Copy)]\npub struct {}{} {{\n",
  580. component_type.name, generics
  581. );
  582. code += &*component_fields_to_rust_code(fields);
  583. code += "}\n\n";
  584. }
  585. code
  586. }
  587. pub(crate) fn types_cargo_toml() -> String {
  588. let name = "bolt-types";
  589. format!(
  590. r#"[package]
  591. name = "{0}"
  592. version = "{2}"
  593. description = "Autogenerate types for the bolt language"
  594. edition = "2021"
  595. [lib]
  596. crate-type = ["cdylib", "lib"]
  597. name = "{1}"
  598. [dependencies]
  599. bolt-lang = "{2}"
  600. anchor-lang = "{3}"
  601. "#,
  602. name,
  603. name.to_snake_case(),
  604. VERSION,
  605. anchor_cli::VERSION,
  606. )
  607. }