rust_template.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. use crate::VERSION;
  2. use crate::{config::ProgramWorkspace, create_files, Files};
  3. use anchor_syn::idl::types::Idl;
  4. use anyhow::Result;
  5. use clap::{Parser, ValueEnum};
  6. use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
  7. use solana_sdk::{
  8. pubkey::Pubkey,
  9. signature::{read_keypair_file, write_keypair_file, Keypair},
  10. signer::Signer,
  11. };
  12. use std::{fmt::Write, path::Path};
  13. /// Program initialization template
  14. #[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum)]
  15. pub enum ProgramTemplate {
  16. /// Program with a single `lib.rs` file
  17. #[default]
  18. Single,
  19. /// Program with multiple files for instructions, state...
  20. Multiple,
  21. }
  22. /// Create a program from the given name and template.
  23. pub fn create_program(name: &str, template: ProgramTemplate) -> Result<()> {
  24. let program_path = Path::new("programs").join(name);
  25. let common_files = vec![
  26. ("Cargo.toml".into(), workspace_manifest().into()),
  27. (program_path.join("Cargo.toml"), cargo_toml(name)),
  28. (program_path.join("Xargo.toml"), xargo_toml().into()),
  29. ];
  30. let template_files = match template {
  31. ProgramTemplate::Single => create_program_template_single(name, &program_path),
  32. ProgramTemplate::Multiple => create_program_template_multiple(name, &program_path),
  33. };
  34. create_files(&[common_files, template_files].concat())
  35. }
  36. /// Create a program with a single `lib.rs` file.
  37. fn create_program_template_single(name: &str, program_path: &Path) -> Files {
  38. vec![(
  39. program_path.join("src").join("lib.rs"),
  40. format!(
  41. r#"use anchor_lang::prelude::*;
  42. declare_id!("{}");
  43. #[program]
  44. pub mod {} {{
  45. use super::*;
  46. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
  47. Ok(())
  48. }}
  49. }}
  50. #[derive(Accounts)]
  51. pub struct Initialize {{}}
  52. "#,
  53. get_or_create_program_id(name),
  54. name.to_snake_case(),
  55. ),
  56. )]
  57. }
  58. /// Create a program with multiple files for instructions, state...
  59. fn create_program_template_multiple(name: &str, program_path: &Path) -> Files {
  60. let src_path = program_path.join("src");
  61. vec![
  62. (
  63. src_path.join("lib.rs"),
  64. format!(
  65. r#"pub mod constants;
  66. pub mod error;
  67. pub mod instructions;
  68. pub mod state;
  69. use anchor_lang::prelude::*;
  70. pub use constants::*;
  71. pub use instructions::*;
  72. pub use state::*;
  73. declare_id!("{}");
  74. #[program]
  75. pub mod {} {{
  76. use super::*;
  77. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
  78. initialize::handler(ctx)
  79. }}
  80. }}
  81. "#,
  82. get_or_create_program_id(name),
  83. name.to_snake_case(),
  84. ),
  85. ),
  86. (
  87. src_path.join("constants.rs"),
  88. r#"use anchor_lang::prelude::*;
  89. #[constant]
  90. pub const SEED: &str = "anchor";
  91. "#
  92. .into(),
  93. ),
  94. (
  95. src_path.join("error.rs"),
  96. r#"use anchor_lang::prelude::*;
  97. #[error_code]
  98. pub enum ErrorCode {
  99. #[msg("Custom error message")]
  100. CustomError,
  101. }
  102. "#
  103. .into(),
  104. ),
  105. (
  106. src_path.join("instructions").join("mod.rs"),
  107. r#"pub mod initialize;
  108. pub use initialize::*;
  109. "#
  110. .into(),
  111. ),
  112. (
  113. src_path.join("instructions").join("initialize.rs"),
  114. r#"use anchor_lang::prelude::*;
  115. #[derive(Accounts)]
  116. pub struct Initialize {}
  117. pub fn handler(ctx: Context<Initialize>) -> Result<()> {
  118. Ok(())
  119. }
  120. "#
  121. .into(),
  122. ),
  123. (src_path.join("state").join("mod.rs"), r#""#.into()),
  124. ]
  125. }
  126. const fn workspace_manifest() -> &'static str {
  127. r#"[workspace]
  128. members = [
  129. "programs/*"
  130. ]
  131. [profile.release]
  132. overflow-checks = true
  133. lto = "fat"
  134. codegen-units = 1
  135. [profile.release.build-override]
  136. opt-level = 3
  137. incremental = false
  138. codegen-units = 1
  139. "#
  140. }
  141. fn cargo_toml(name: &str) -> String {
  142. format!(
  143. r#"[package]
  144. name = "{0}"
  145. version = "0.1.0"
  146. description = "Created with Anchor"
  147. edition = "2021"
  148. [lib]
  149. crate-type = ["cdylib", "lib"]
  150. name = "{1}"
  151. [features]
  152. no-entrypoint = []
  153. no-idl = []
  154. no-log-ix-name = []
  155. cpi = ["no-entrypoint"]
  156. default = []
  157. [dependencies]
  158. anchor-lang = "{2}"
  159. "#,
  160. name,
  161. name.to_snake_case(),
  162. VERSION,
  163. )
  164. }
  165. fn xargo_toml() -> &'static str {
  166. r#"[target.bpfel-unknown-unknown.dependencies.std]
  167. features = []
  168. "#
  169. }
  170. /// Read the program keypair file or create a new one if it doesn't exist.
  171. pub fn get_or_create_program_id(name: &str) -> Pubkey {
  172. let keypair_path = Path::new("target")
  173. .join("deploy")
  174. .join(format!("{}-keypair.json", name.to_snake_case()));
  175. read_keypair_file(&keypair_path)
  176. .unwrap_or_else(|_| {
  177. let keypair = Keypair::new();
  178. write_keypair_file(&keypair, keypair_path).expect("Unable to create program keypair");
  179. keypair
  180. })
  181. .pubkey()
  182. }
  183. pub fn credentials(token: &str) -> String {
  184. format!(
  185. r#"[registry]
  186. token = "{token}"
  187. "#
  188. )
  189. }
  190. pub fn idl_ts(idl: &Idl) -> Result<String> {
  191. let mut idl = idl.clone();
  192. for acc in idl.accounts.iter_mut() {
  193. acc.name = acc.name.to_lower_camel_case();
  194. }
  195. let idl_json = serde_json::to_string_pretty(&idl)?;
  196. Ok(format!(
  197. r#"export type {} = {};
  198. export const IDL: {} = {};
  199. "#,
  200. idl.name.to_upper_camel_case(),
  201. idl_json,
  202. idl.name.to_upper_camel_case(),
  203. idl_json
  204. ))
  205. }
  206. pub fn deploy_js_script_host(cluster_url: &str, script_path: &str) -> String {
  207. format!(
  208. r#"
  209. const anchor = require('@coral-xyz/anchor');
  210. // Deploy script defined by the user.
  211. const userScript = require("{script_path}");
  212. async function main() {{
  213. const url = "{cluster_url}";
  214. const preflightCommitment = 'recent';
  215. const connection = new anchor.web3.Connection(url, preflightCommitment);
  216. const wallet = anchor.Wallet.local();
  217. const provider = new anchor.AnchorProvider(connection, wallet, {{
  218. preflightCommitment,
  219. commitment: 'recent',
  220. }});
  221. // Run the user's deploy script.
  222. userScript(provider);
  223. }}
  224. main();
  225. "#,
  226. )
  227. }
  228. pub fn deploy_ts_script_host(cluster_url: &str, script_path: &str) -> String {
  229. format!(
  230. r#"import * as anchor from '@coral-xyz/anchor';
  231. // Deploy script defined by the user.
  232. const userScript = require("{script_path}");
  233. async function main() {{
  234. const url = "{cluster_url}";
  235. const preflightCommitment = 'recent';
  236. const connection = new anchor.web3.Connection(url, preflightCommitment);
  237. const wallet = anchor.Wallet.local();
  238. const provider = new anchor.AnchorProvider(connection, wallet, {{
  239. preflightCommitment,
  240. commitment: 'recent',
  241. }});
  242. // Run the user's deploy script.
  243. userScript(provider);
  244. }}
  245. main();
  246. "#,
  247. )
  248. }
  249. pub fn deploy_script() -> &'static str {
  250. r#"// Migrations are an early feature. Currently, they're nothing more than this
  251. // single deploy script that's invoked from the CLI, injecting a provider
  252. // configured from the workspace's Anchor.toml.
  253. const anchor = require("@coral-xyz/anchor");
  254. module.exports = async function (provider) {
  255. // Configure client to use the provider.
  256. anchor.setProvider(provider);
  257. // Add your deploy script here.
  258. };
  259. "#
  260. }
  261. pub fn ts_deploy_script() -> &'static str {
  262. r#"// Migrations are an early feature. Currently, they're nothing more than this
  263. // single deploy script that's invoked from the CLI, injecting a provider
  264. // configured from the workspace's Anchor.toml.
  265. const anchor = require("@coral-xyz/anchor");
  266. module.exports = async function (provider) {
  267. // Configure client to use the provider.
  268. anchor.setProvider(provider);
  269. // Add your deploy script here.
  270. };
  271. "#
  272. }
  273. pub fn mocha(name: &str) -> String {
  274. format!(
  275. r#"const anchor = require("@coral-xyz/anchor");
  276. describe("{}", () => {{
  277. // Configure the client to use the local cluster.
  278. anchor.setProvider(anchor.AnchorProvider.env());
  279. it("Is initialized!", async () => {{
  280. // Add your test here.
  281. const program = anchor.workspace.{};
  282. const tx = await program.methods.initialize().rpc();
  283. console.log("Your transaction signature", tx);
  284. }});
  285. }});
  286. "#,
  287. name,
  288. name.to_upper_camel_case(),
  289. )
  290. }
  291. pub fn jest(name: &str) -> String {
  292. format!(
  293. r#"const anchor = require("@coral-xyz/anchor");
  294. describe("{}", () => {{
  295. // Configure the client to use the local cluster.
  296. anchor.setProvider(anchor.AnchorProvider.env());
  297. it("Is initialized!", async () => {{
  298. // Add your test here.
  299. const program = anchor.workspace.{};
  300. const tx = await program.methods.initialize().rpc();
  301. console.log("Your transaction signature", tx);
  302. }});
  303. }});
  304. "#,
  305. name,
  306. name.to_upper_camel_case(),
  307. )
  308. }
  309. pub fn package_json(jest: bool) -> String {
  310. if jest {
  311. format!(
  312. r#"{{
  313. "scripts": {{
  314. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  315. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  316. }},
  317. "dependencies": {{
  318. "@coral-xyz/anchor": "^{VERSION}"
  319. }},
  320. "devDependencies": {{
  321. "jest": "^29.0.3",
  322. "prettier": "^2.6.2"
  323. }}
  324. }}
  325. "#
  326. )
  327. } else {
  328. format!(
  329. r#"{{
  330. "scripts": {{
  331. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  332. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  333. }},
  334. "dependencies": {{
  335. "@coral-xyz/anchor": "^{VERSION}"
  336. }},
  337. "devDependencies": {{
  338. "chai": "^4.3.4",
  339. "mocha": "^9.0.3",
  340. "prettier": "^2.6.2"
  341. }}
  342. }}
  343. "#
  344. )
  345. }
  346. }
  347. pub fn ts_package_json(jest: bool) -> String {
  348. if jest {
  349. format!(
  350. r#"{{
  351. "scripts": {{
  352. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  353. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  354. }},
  355. "dependencies": {{
  356. "@coral-xyz/anchor": "^{VERSION}"
  357. }},
  358. "devDependencies": {{
  359. "@types/bn.js": "^5.1.0",
  360. "@types/jest": "^29.0.3",
  361. "jest": "^29.0.3",
  362. "prettier": "^2.6.2",
  363. "ts-jest": "^29.0.2",
  364. "typescript": "^4.3.5"
  365. }}
  366. }}
  367. "#
  368. )
  369. } else {
  370. format!(
  371. r#"{{
  372. "scripts": {{
  373. "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
  374. "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
  375. }},
  376. "dependencies": {{
  377. "@coral-xyz/anchor": "^{VERSION}"
  378. }},
  379. "devDependencies": {{
  380. "chai": "^4.3.4",
  381. "mocha": "^9.0.3",
  382. "ts-mocha": "^10.0.0",
  383. "@types/bn.js": "^5.1.0",
  384. "@types/chai": "^4.3.0",
  385. "@types/mocha": "^9.0.0",
  386. "typescript": "^4.3.5",
  387. "prettier": "^2.6.2"
  388. }}
  389. }}
  390. "#
  391. )
  392. }
  393. }
  394. pub fn ts_mocha(name: &str) -> String {
  395. format!(
  396. r#"import * as anchor from "@coral-xyz/anchor";
  397. import {{ Program }} from "@coral-xyz/anchor";
  398. import {{ {} }} from "../target/types/{}";
  399. describe("{}", () => {{
  400. // Configure the client to use the local cluster.
  401. anchor.setProvider(anchor.AnchorProvider.env());
  402. const program = anchor.workspace.{} as Program<{}>;
  403. it("Is initialized!", async () => {{
  404. // Add your test here.
  405. const tx = await program.methods.initialize().rpc();
  406. console.log("Your transaction signature", tx);
  407. }});
  408. }});
  409. "#,
  410. name.to_upper_camel_case(),
  411. name.to_snake_case(),
  412. name,
  413. name.to_upper_camel_case(),
  414. name.to_upper_camel_case(),
  415. )
  416. }
  417. pub fn ts_jest(name: &str) -> String {
  418. format!(
  419. r#"import * as anchor from "@coral-xyz/anchor";
  420. import {{ Program }} from "@coral-xyz/anchor";
  421. import {{ {} }} from "../target/types/{}";
  422. describe("{}", () => {{
  423. // Configure the client to use the local cluster.
  424. anchor.setProvider(anchor.AnchorProvider.env());
  425. const program = anchor.workspace.{} as Program<{}>;
  426. it("Is initialized!", async () => {{
  427. // Add your test here.
  428. const tx = await program.methods.initialize().rpc();
  429. console.log("Your transaction signature", tx);
  430. }});
  431. }});
  432. "#,
  433. name.to_upper_camel_case(),
  434. name.to_snake_case(),
  435. name,
  436. name.to_upper_camel_case(),
  437. name.to_upper_camel_case(),
  438. )
  439. }
  440. pub fn ts_config(jest: bool) -> &'static str {
  441. if jest {
  442. r#"{
  443. "compilerOptions": {
  444. "types": ["jest"],
  445. "typeRoots": ["./node_modules/@types"],
  446. "lib": ["es2015"],
  447. "module": "commonjs",
  448. "target": "es6",
  449. "esModuleInterop": true
  450. }
  451. }
  452. "#
  453. } else {
  454. r#"{
  455. "compilerOptions": {
  456. "types": ["mocha", "chai"],
  457. "typeRoots": ["./node_modules/@types"],
  458. "lib": ["es2015"],
  459. "module": "commonjs",
  460. "target": "es6",
  461. "esModuleInterop": true
  462. }
  463. }
  464. "#
  465. }
  466. }
  467. pub fn git_ignore() -> &'static str {
  468. r#"
  469. .anchor
  470. .DS_Store
  471. target
  472. **/*.rs.bk
  473. node_modules
  474. test-ledger
  475. .yarn
  476. "#
  477. }
  478. pub fn prettier_ignore() -> &'static str {
  479. r#"
  480. .anchor
  481. .DS_Store
  482. target
  483. node_modules
  484. dist
  485. build
  486. test-ledger
  487. "#
  488. }
  489. pub fn node_shell(
  490. cluster_url: &str,
  491. wallet_path: &str,
  492. programs: Vec<ProgramWorkspace>,
  493. ) -> Result<String> {
  494. let mut eval_string = format!(
  495. r#"
  496. const anchor = require('@coral-xyz/anchor');
  497. const web3 = anchor.web3;
  498. const PublicKey = anchor.web3.PublicKey;
  499. const Keypair = anchor.web3.Keypair;
  500. const __wallet = new anchor.Wallet(
  501. Keypair.fromSecretKey(
  502. Buffer.from(
  503. JSON.parse(
  504. require('fs').readFileSync(
  505. "{wallet_path}",
  506. {{
  507. encoding: "utf-8",
  508. }},
  509. ),
  510. ),
  511. ),
  512. ),
  513. );
  514. const __connection = new web3.Connection("{cluster_url}", "processed");
  515. const provider = new anchor.AnchorProvider(__connection, __wallet, {{
  516. commitment: "processed",
  517. preflightcommitment: "processed",
  518. }});
  519. anchor.setProvider(provider);
  520. "#,
  521. );
  522. for program in programs {
  523. write!(
  524. &mut eval_string,
  525. r#"
  526. anchor.workspace.{} = new anchor.Program({}, new PublicKey("{}"), provider);
  527. "#,
  528. program.name.to_upper_camel_case(),
  529. serde_json::to_string(&program.idl)?,
  530. program.program_id
  531. )?;
  532. }
  533. Ok(eval_string)
  534. }