rust_template.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. use crate::ANCHOR_VERSION;
  2. use crate::VERSION;
  3. use anchor_cli::Files;
  4. use anyhow::Result;
  5. use heck::{ToSnakeCase, ToUpperCamelCase};
  6. use std::path::{Path, PathBuf};
  7. /// Create a component from the given name.
  8. pub fn create_component(name: &str) -> Result<()> {
  9. let program_path = Path::new("programs-ecs/components").join(name);
  10. let common_files = vec![
  11. (
  12. PathBuf::from("Cargo.toml".to_string()),
  13. workspace_manifest().to_string(),
  14. ),
  15. (program_path.join("Cargo.toml"), cargo_toml(name)),
  16. (program_path.join("Xargo.toml"), xargo_toml().to_string()),
  17. ] as Files;
  18. let template_files = create_component_template_simple(name, &program_path);
  19. anchor_cli::create_files(&[common_files, template_files].concat())
  20. }
  21. /// Create a system from the given name.
  22. pub(crate) fn create_system(name: &str) -> Result<()> {
  23. let program_path = Path::new("programs-ecs/systems").join(name);
  24. let common_files = vec![
  25. (
  26. PathBuf::from("Cargo.toml".to_string()),
  27. workspace_manifest().to_string(),
  28. ),
  29. (program_path.join("Cargo.toml"), cargo_toml(name)),
  30. (program_path.join("Xargo.toml"), xargo_toml().to_string()),
  31. ] as Files;
  32. let template_files = create_system_template_simple(name, &program_path);
  33. anchor_cli::create_files(&[common_files, template_files].concat())
  34. }
  35. /// Create a component which holds position data.
  36. fn create_component_template_simple(name: &str, program_path: &Path) -> Files {
  37. vec![(
  38. program_path.join("src").join("lib.rs"),
  39. format!(
  40. r#"use bolt_lang::*;
  41. declare_id!("{}");
  42. #[component]
  43. pub struct {} {{
  44. pub x: i64,
  45. pub y: i64,
  46. pub z: i64,
  47. #[max_len(20)]
  48. pub description: String,
  49. }}
  50. "#,
  51. anchor_cli::rust_template::get_or_create_program_id(name),
  52. name.to_upper_camel_case(),
  53. ),
  54. )]
  55. }
  56. /// Create a system which operates on a Position component.
  57. fn create_system_template_simple(name: &str, program_path: &Path) -> Files {
  58. vec![(
  59. program_path.join("src").join("lib.rs"),
  60. format!(
  61. r#"use bolt_lang::*;
  62. declare_id!("{}");
  63. #[system]
  64. pub mod {} {{
  65. pub fn execute(ctx: Context<Component>, args: Vec<u8>) -> Result<Position> {{
  66. let mut position = Position::from_account_info(&ctx.accounts.position)?;
  67. position.x += 1;
  68. Ok(position)
  69. }}
  70. }}
  71. // Define the Account to parse from the component
  72. #[derive(Accounts)]
  73. pub struct Component<'info> {{
  74. /// CHECK: check that the component is the expected account
  75. pub position: AccountInfo<'info>,
  76. }}
  77. #[component_deserialize]
  78. pub struct Position {{
  79. pub x: i64,
  80. pub y: i64,
  81. pub z: i64,
  82. pub description: String,
  83. }}
  84. "#,
  85. anchor_cli::rust_template::get_or_create_program_id(name),
  86. name.to_snake_case(),
  87. ),
  88. )]
  89. }
  90. const fn workspace_manifest() -> &'static str {
  91. r#"[workspace]
  92. members = [
  93. "programs/*",
  94. "programs-ecs/components/*",
  95. "programs-ecs/systems/*"
  96. ]
  97. resolver = "2"
  98. [profile.release]
  99. overflow-checks = true
  100. lto = "fat"
  101. codegen-units = 1
  102. [profile.release.build-override]
  103. opt-level = 3
  104. incremental = false
  105. codegen-units = 1
  106. "#
  107. }
  108. pub fn package_json(jest: bool) -> String {
  109. if jest {
  110. format!(
  111. r#"{{
  112. "scripts": {{
  113. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  114. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  115. }},
  116. "dependencies": {{
  117. "@coral-xyz/anchor": "^{VERSION}"
  118. }},
  119. "devDependencies": {{
  120. "jest": "^29.0.3",
  121. "prettier": "^2.6.2"
  122. }}
  123. }}
  124. "#
  125. )
  126. } else {
  127. format!(
  128. r#"{{
  129. "scripts": {{
  130. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  131. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  132. }},
  133. "dependencies": {{
  134. "@coral-xyz/anchor": "^{VERSION}"
  135. }},
  136. "devDependencies": {{
  137. "chai": "^4.3.4",
  138. "mocha": "^9.0.3",
  139. "prettier": "^2.6.2",
  140. "@metaplex-foundation/beet": "^0.7.1",
  141. "@metaplex-foundation/beet-solana": "^0.4.0",
  142. "bolt-sdk": "latest"
  143. }}
  144. }}
  145. "#
  146. )
  147. }
  148. }
  149. pub fn ts_package_json(jest: bool) -> String {
  150. if jest {
  151. format!(
  152. r#"{{
  153. "scripts": {{
  154. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  155. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  156. }},
  157. "dependencies": {{
  158. "@coral-xyz/anchor": "^{ANCHOR_VERSION}"
  159. }},
  160. "devDependencies": {{
  161. "@types/bn.js": "^5.1.0",
  162. "@types/jest": "^29.0.3",
  163. "jest": "^29.0.3",
  164. "prettier": "^2.6.2",
  165. "ts-jest": "^29.0.2",
  166. "typescript": "^4.3.5",
  167. "@metaplex-foundation/beet": "^0.7.1",
  168. "@metaplex-foundation/beet-solana": "^0.4.0",
  169. "bolt-sdk": "latest"
  170. }}
  171. }}
  172. "#
  173. )
  174. } else {
  175. format!(
  176. r#"{{
  177. "scripts": {{
  178. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  179. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  180. }},
  181. "dependencies": {{
  182. "@coral-xyz/anchor": "^{ANCHOR_VERSION}"
  183. }},
  184. "devDependencies": {{
  185. "chai": "^4.3.4",
  186. "mocha": "^9.0.3",
  187. "ts-mocha": "^10.0.0",
  188. "@types/bn.js": "^5.1.0",
  189. "@types/chai": "^4.3.0",
  190. "@types/mocha": "^9.0.0",
  191. "typescript": "^4.3.5",
  192. "prettier": "^2.6.2",
  193. "@metaplex-foundation/beet": "^0.7.1",
  194. "@metaplex-foundation/beet-solana": "^0.4.0",
  195. "bolt-sdk": "latest"
  196. }}
  197. }}
  198. "#
  199. )
  200. }
  201. }
  202. pub fn mocha(name: &str) -> String {
  203. format!(
  204. r#"const anchor = require("@coral-xyz/anchor");
  205. const boltSdk = require("bolt-sdk");
  206. const {{
  207. createInitializeNewWorldInstruction,
  208. FindWorldPda,
  209. FindWorldRegistryPda,
  210. Registry,
  211. World
  212. }} = boltSdk;
  213. describe("{}", () => {{
  214. // Configure the client to use the local cluster.
  215. const provider = anchor.AnchorProvider.env();
  216. anchor.setProvider(provider);
  217. it("InitializeNewWorld", async () => {{
  218. const registry = await Registry.fromAccountAddress(provider.connection, registryPda);
  219. worldId = new anchor.BN(registry.worlds);
  220. worldPda = FindWorldPda(new anchor.BN(worldId))
  221. const initializeWorldIx = createInitializeNewWorldInstruction(
  222. {{
  223. world: worldPda,
  224. registry: registryPda,
  225. payer: provider.wallet.publicKey,
  226. }});
  227. const tx = new anchor.web3.Transaction().add(initializeWorldIx);
  228. const txSign = await provider.sendAndConfirm(tx);
  229. console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`);
  230. }});
  231. }});
  232. }});
  233. "#,
  234. name,
  235. )
  236. }
  237. pub fn jest(name: &str) -> String {
  238. format!(
  239. r#"const anchor = require("@coral-xyz/anchor");
  240. const boltSdk = require("bolt-sdk");
  241. const {{
  242. createInitializeNewWorldInstruction,
  243. FindWorldPda,
  244. FindWorldRegistryPda,
  245. Registry,
  246. World
  247. }} = boltSdk;
  248. describe("{}", () => {{
  249. // Configure the client to use the local cluster.
  250. const provider = anchor.AnchorProvider.env();
  251. anchor.setProvider(provider);
  252. // Constants used to test the program.
  253. const registryPda = FindWorldRegistryPda();
  254. let worldId: anchor.BN;
  255. let worldPda: PublicKey;
  256. it("InitializeNewWorld", async () => {{
  257. const registry = await Registry.fromAccountAddress(provider.connection, registryPda);
  258. worldId = new anchor.BN(registry.worlds);
  259. worldPda = FindWorldPda(new anchor.BN(worldId))
  260. const initializeWorldIx = createInitializeNewWorldInstruction(
  261. {{
  262. world: worldPda,
  263. registry: registryPda,
  264. payer: provider.wallet.publicKey,
  265. }});
  266. const tx = new anchor.web3.Transaction().add(initializeWorldIx);
  267. const txSign = await provider.sendAndConfirm(tx);
  268. console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`);
  269. }});
  270. }});
  271. "#,
  272. name,
  273. )
  274. }
  275. pub fn ts_mocha(name: &str) -> String {
  276. format!(
  277. r#"import * as anchor from "@coral-xyz/anchor";
  278. import {{ Program }} from "@coral-xyz/anchor";
  279. import {{ PublicKey }} from "@solana/web3.js";
  280. import {{ Position }} from "../target/types/position";
  281. import {{ Movement }} from "../target/types/movement";
  282. import {{
  283. createInitializeNewWorldInstruction,
  284. FindWorldPda,
  285. FindWorldRegistryPda,
  286. FindEntityPda,
  287. Registry,
  288. World,
  289. createAddEntityInstruction,
  290. createInitializeComponentInstruction,
  291. FindComponentPda, createApplyInstruction
  292. }} from "bolt-sdk"
  293. import {{expect}} from "chai";
  294. describe("{}", () => {{
  295. // Configure the client to use the local cluster.
  296. const provider = anchor.AnchorProvider.env();
  297. anchor.setProvider(provider);
  298. // Constants used to test the program.
  299. const registryPda = FindWorldRegistryPda();
  300. let worldId: anchor.BN;
  301. let worldPda: PublicKey;
  302. let entityPda: PublicKey;
  303. const positionComponent = anchor.workspace.Position as Program<Position>;
  304. const systemMovement = anchor.workspace.Movement as Program<Movement>;
  305. it("InitializeNewWorld", async () => {{
  306. const registry = await Registry.fromAccountAddress(provider.connection, registryPda);
  307. worldId = new anchor.BN(registry.worlds);
  308. worldPda = FindWorldPda(new anchor.BN(worldId))
  309. const initializeWorldIx = createInitializeNewWorldInstruction(
  310. {{
  311. world: worldPda,
  312. registry: registryPda,
  313. payer: provider.wallet.publicKey,
  314. }});
  315. const tx = new anchor.web3.Transaction().add(initializeWorldIx);
  316. const txSign = await provider.sendAndConfirm(tx);
  317. console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`);
  318. }});
  319. it("Add an entity", async () => {{
  320. const world = await World.fromAccountAddress(provider.connection, worldPda);
  321. const entityId = new anchor.BN(world.entities);
  322. entityPda = FindEntityPda(worldId, entityId);
  323. let createEntityIx = createAddEntityInstruction({{
  324. world: worldPda,
  325. payer: provider.wallet.publicKey,
  326. entity: entityPda,
  327. }});
  328. const tx = new anchor.web3.Transaction().add(createEntityIx);
  329. const txSign = await provider.sendAndConfirm(tx);
  330. console.log(`Initialized a new Entity (ID=${{worldId}}). Initialization signature: ${{txSign}}`);
  331. }});
  332. it("Add a component", async () => {{
  333. const positionComponentPda = FindComponentPda(positionComponent.programId, entityPda, "");
  334. let initComponentIx = createInitializeComponentInstruction({{
  335. payer: provider.wallet.publicKey,
  336. entity: entityPda,
  337. data: positionComponentPda,
  338. componentProgram: positionComponent.programId,
  339. }});
  340. const tx = new anchor.web3.Transaction().add(initComponentIx);
  341. const txSign = await provider.sendAndConfirm(tx);
  342. console.log(`Initialized a new component. Initialization signature: ${{txSign}}`);
  343. }});
  344. it("Apply a system", async () => {{
  345. const positionComponentPda = FindComponentPda(positionComponent.programId, entityPda, "");
  346. // Check that the component has been initialized and x is 0
  347. let positionData = await positionComponent.account.position.fetch(
  348. positionComponentPda
  349. );
  350. expect(positionData.x.toNumber()).to.eq(0);
  351. let applySystemIx = createApplyInstruction({{
  352. componentProgram: positionComponent.programId,
  353. boltSystem: systemMovement.programId,
  354. boltComponent: positionComponentPda,
  355. }}, {{args: new Uint8Array()}});
  356. const tx = new anchor.web3.Transaction().add(applySystemIx);
  357. await provider.sendAndConfirm(tx);
  358. // Check that the system has been applied and x is > 0
  359. positionData = await positionComponent.account.position.fetch(
  360. positionComponentPda
  361. );
  362. expect(positionData.x.toNumber()).to.gt(0);
  363. }});
  364. }});
  365. "#,
  366. name.to_upper_camel_case(),
  367. )
  368. }
  369. fn cargo_toml(name: &str) -> String {
  370. format!(
  371. r#"[package]
  372. name = "{0}"
  373. version = "0.1.0"
  374. description = "Created with Bolt"
  375. edition = "2021"
  376. [lib]
  377. crate-type = ["cdylib", "lib"]
  378. name = "{1}"
  379. [features]
  380. no-entrypoint = []
  381. no-idl = []
  382. no-log-ix-name = []
  383. cpi = ["no-entrypoint"]
  384. default = []
  385. idl-build = ["anchor-lang/idl-build"]
  386. [dependencies]
  387. bolt-lang = "{2}"
  388. anchor-lang = "{3}"
  389. "#,
  390. name,
  391. name.to_snake_case(),
  392. VERSION,
  393. anchor_cli::VERSION,
  394. )
  395. }
  396. fn xargo_toml() -> &'static str {
  397. r#"[target.bpfel-unknown-unknown.dependencies.std]
  398. features = []
  399. "#
  400. }
  401. pub fn git_ignore() -> &'static str {
  402. r#"
  403. .anchor
  404. .bolt
  405. .DS_Store
  406. target
  407. **/*.rs.bk
  408. node_modules
  409. test-ledger
  410. .yarn
  411. "#
  412. }
  413. pub fn prettier_ignore() -> &'static str {
  414. r#"
  415. .anchor
  416. .bolt
  417. .DS_Store
  418. target
  419. node_modules
  420. dist
  421. build
  422. test-ledger
  423. "#
  424. }