use crate::ANCHOR_VERSION; use crate::VERSION; use anchor_cli::Files; use anyhow::Result; use heck::{ToSnakeCase, ToUpperCamelCase}; use std::path::{Path, PathBuf}; /// Create a component from the given name. pub fn create_component(name: &str) -> Result<()> { let program_path = Path::new("programs-ecs/components").join(name); let common_files = vec![ ( PathBuf::from("Cargo.toml".to_string()), workspace_manifest().to_string(), ), (program_path.join("Cargo.toml"), cargo_toml(name)), (program_path.join("Xargo.toml"), xargo_toml().to_string()), ] as Files; let template_files = create_component_template_simple(name, &program_path); anchor_cli::create_files(&[common_files, template_files].concat()) } /// Create a system from the given name. pub(crate) fn create_system(name: &str) -> Result<()> { let program_path = Path::new("programs-ecs/systems").join(name); let common_files = vec![ ( PathBuf::from("Cargo.toml".to_string()), workspace_manifest().to_string(), ), (program_path.join("Cargo.toml"), cargo_toml(name)), (program_path.join("Xargo.toml"), xargo_toml().to_string()), ] as Files; let template_files = create_system_template_simple(name, &program_path); anchor_cli::create_files(&[common_files, template_files].concat()) } /// Create a component which holds position data. fn create_component_template_simple(name: &str, program_path: &Path) -> Files { vec![( program_path.join("src").join("lib.rs"), format!( r#"use bolt_lang::*; declare_id!("{}"); #[component] pub struct {} {{ pub x: i64, pub y: i64, pub z: i64, #[max_len(20)] pub description: String, }} "#, anchor_cli::rust_template::get_or_create_program_id(name), name.to_upper_camel_case(), ), )] } /// Create a system which operates on a Position component. fn create_system_template_simple(name: &str, program_path: &Path) -> Files { vec![( program_path.join("src").join("lib.rs"), format!( r#"use bolt_lang::*; declare_id!("{}"); #[system] pub mod {} {{ pub fn execute(ctx: Context, args: Vec) -> Result {{ let mut position = Position::from_account_info(&ctx.accounts.position)?; position.x += 1; Ok(position) }} }} // Define the Account to parse from the component #[derive(Accounts)] pub struct Component<'info> {{ /// CHECK: check that the component is the expected account pub position: AccountInfo<'info>, }} #[component_deserialize] pub struct Position {{ pub x: i64, pub y: i64, pub z: i64, pub description: String, }} "#, anchor_cli::rust_template::get_or_create_program_id(name), name.to_snake_case(), ), )] } const fn workspace_manifest() -> &'static str { r#"[workspace] members = [ "programs/*", "programs-ecs/components/*", "programs-ecs/systems/*" ] resolver = "2" [profile.release] overflow-checks = true lto = "fat" codegen-units = 1 [profile.release.build-override] opt-level = 3 incremental = false codegen-units = 1 "# } pub fn package_json(jest: bool) -> String { if jest { format!( r#"{{ "scripts": {{ "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w", "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check" }}, "dependencies": {{ "@coral-xyz/anchor": "^{VERSION}" }}, "devDependencies": {{ "jest": "^29.0.3", "prettier": "^2.6.2" }} }} "# ) } else { format!( r#"{{ "scripts": {{ "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w", "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check" }}, "dependencies": {{ "@coral-xyz/anchor": "^{VERSION}" }}, "devDependencies": {{ "chai": "^4.3.4", "mocha": "^9.0.3", "prettier": "^2.6.2", "@metaplex-foundation/beet": "^0.7.1", "@metaplex-foundation/beet-solana": "^0.4.0", "bolt-sdk": "latest" }} }} "# ) } } pub fn ts_package_json(jest: bool) -> String { if jest { format!( r#"{{ "scripts": {{ "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w", "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check" }}, "dependencies": {{ "@coral-xyz/anchor": "^{ANCHOR_VERSION}" }}, "devDependencies": {{ "@types/bn.js": "^5.1.0", "@types/jest": "^29.0.3", "jest": "^29.0.3", "prettier": "^2.6.2", "ts-jest": "^29.0.2", "typescript": "^4.3.5", "@metaplex-foundation/beet": "^0.7.1", "@metaplex-foundation/beet-solana": "^0.4.0", "bolt-sdk": "latest" }} }} "# ) } else { format!( r#"{{ "scripts": {{ "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w", "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check" }}, "dependencies": {{ "@coral-xyz/anchor": "^{ANCHOR_VERSION}" }}, "devDependencies": {{ "chai": "^4.3.4", "mocha": "^9.0.3", "ts-mocha": "^10.0.0", "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", "typescript": "^4.3.5", "prettier": "^2.6.2", "@metaplex-foundation/beet": "^0.7.1", "@metaplex-foundation/beet-solana": "^0.4.0", "bolt-sdk": "latest" }} }} "# ) } } pub fn mocha(name: &str) -> String { format!( r#"const anchor = require("@coral-xyz/anchor"); const boltSdk = require("bolt-sdk"); const {{ createInitializeNewWorldInstruction, FindWorldPda, FindWorldRegistryPda, Registry, World }} = boltSdk; describe("{}", () => {{ // Configure the client to use the local cluster. const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); it("InitializeNewWorld", async () => {{ const registry = await Registry.fromAccountAddress(provider.connection, registryPda); worldId = new anchor.BN(registry.worlds); worldPda = FindWorldPda(new anchor.BN(worldId)) const initializeWorldIx = createInitializeNewWorldInstruction( {{ world: worldPda, registry: registryPda, payer: provider.wallet.publicKey, }}); const tx = new anchor.web3.Transaction().add(initializeWorldIx); const txSign = await provider.sendAndConfirm(tx); console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`); }}); }}); }}); "#, name, ) } pub fn jest(name: &str) -> String { format!( r#"const anchor = require("@coral-xyz/anchor"); const boltSdk = require("bolt-sdk"); const {{ createInitializeNewWorldInstruction, FindWorldPda, FindWorldRegistryPda, Registry, World }} = boltSdk; describe("{}", () => {{ // Configure the client to use the local cluster. const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); // Constants used to test the program. const registryPda = FindWorldRegistryPda(); let worldId: anchor.BN; let worldPda: PublicKey; it("InitializeNewWorld", async () => {{ const registry = await Registry.fromAccountAddress(provider.connection, registryPda); worldId = new anchor.BN(registry.worlds); worldPda = FindWorldPda(new anchor.BN(worldId)) const initializeWorldIx = createInitializeNewWorldInstruction( {{ world: worldPda, registry: registryPda, payer: provider.wallet.publicKey, }}); const tx = new anchor.web3.Transaction().add(initializeWorldIx); const txSign = await provider.sendAndConfirm(tx); console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`); }}); }}); "#, name, ) } pub fn ts_mocha(name: &str) -> String { format!( r#"import * as anchor from "@coral-xyz/anchor"; import {{ Program }} from "@coral-xyz/anchor"; import {{ PublicKey }} from "@solana/web3.js"; import {{ Position }} from "../target/types/position"; import {{ Movement }} from "../target/types/movement"; import {{ createInitializeNewWorldInstruction, FindWorldPda, FindWorldRegistryPda, FindEntityPda, Registry, World, createAddEntityInstruction, createInitializeComponentInstruction, FindComponentPda, createApplyInstruction }} from "bolt-sdk" import {{expect}} from "chai"; describe("{}", () => {{ // Configure the client to use the local cluster. const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); // Constants used to test the program. const registryPda = FindWorldRegistryPda(); let worldId: anchor.BN; let worldPda: PublicKey; let entityPda: PublicKey; const positionComponent = anchor.workspace.Position as Program; const systemMovement = anchor.workspace.Movement as Program; it("InitializeNewWorld", async () => {{ const registry = await Registry.fromAccountAddress(provider.connection, registryPda); worldId = new anchor.BN(registry.worlds); worldPda = FindWorldPda(new anchor.BN(worldId)) const initializeWorldIx = createInitializeNewWorldInstruction( {{ world: worldPda, registry: registryPda, payer: provider.wallet.publicKey, }}); const tx = new anchor.web3.Transaction().add(initializeWorldIx); const txSign = await provider.sendAndConfirm(tx); console.log(`Initialized a new world (ID=${{worldId}}). Initialization signature: ${{txSign}}`); }}); it("Add an entity", async () => {{ const world = await World.fromAccountAddress(provider.connection, worldPda); const entityId = new anchor.BN(world.entities); entityPda = FindEntityPda(worldId, entityId); let createEntityIx = createAddEntityInstruction({{ world: worldPda, payer: provider.wallet.publicKey, entity: entityPda, }}); const tx = new anchor.web3.Transaction().add(createEntityIx); const txSign = await provider.sendAndConfirm(tx); console.log(`Initialized a new Entity (ID=${{worldId}}). Initialization signature: ${{txSign}}`); }}); it("Add a component", async () => {{ const positionComponentPda = FindComponentPda(positionComponent.programId, entityPda, ""); let initComponentIx = createInitializeComponentInstruction({{ payer: provider.wallet.publicKey, entity: entityPda, data: positionComponentPda, componentProgram: positionComponent.programId, }}); const tx = new anchor.web3.Transaction().add(initComponentIx); const txSign = await provider.sendAndConfirm(tx); console.log(`Initialized a new component. Initialization signature: ${{txSign}}`); }}); it("Apply a system", async () => {{ const positionComponentPda = FindComponentPda(positionComponent.programId, entityPda, ""); // Check that the component has been initialized and x is 0 let positionData = await positionComponent.account.position.fetch( positionComponentPda ); expect(positionData.x.toNumber()).to.eq(0); let applySystemIx = createApplyInstruction({{ componentProgram: positionComponent.programId, boltSystem: systemMovement.programId, boltComponent: positionComponentPda, }}, {{args: new Uint8Array()}}); const tx = new anchor.web3.Transaction().add(applySystemIx); await provider.sendAndConfirm(tx); // Check that the system has been applied and x is > 0 positionData = await positionComponent.account.position.fetch( positionComponentPda ); expect(positionData.x.toNumber()).to.gt(0); }}); }}); "#, name.to_upper_camel_case(), ) } fn cargo_toml(name: &str) -> String { format!( r#"[package] name = "{0}" version = "0.1.0" description = "Created with Bolt" edition = "2021" [lib] crate-type = ["cdylib", "lib"] name = "{1}" [features] no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] default = [] idl-build = ["anchor-lang/idl-build"] [dependencies] bolt-lang = "{2}" anchor-lang = "{3}" "#, name, name.to_snake_case(), VERSION, anchor_cli::VERSION, ) } fn xargo_toml() -> &'static str { r#"[target.bpfel-unknown-unknown.dependencies.std] features = [] "# } pub fn git_ignore() -> &'static str { r#" .anchor .bolt .DS_Store target **/*.rs.bk node_modules test-ledger .yarn "# } pub fn prettier_ignore() -> &'static str { r#" .anchor .bolt .DS_Store target node_modules dist build test-ledger "# }