1use {
2 crate::{
3 config::ProgramWorkspace, create_files, override_or_create_files, AbsolutePath, Files,
4 PackageManager, VERSION,
5 },
6 anyhow::Result,
7 clap::{Parser, ValueEnum},
8 heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase},
9 solana_keypair::{read_keypair_file, write_keypair_file, Keypair},
10 solana_pubkey::Pubkey,
11 solana_signer::Signer,
12 std::{
13 fmt::Write as _,
14 fs::{self, File},
15 io::Write as _,
16 path::Path,
17 process::Stdio,
18 },
19};
20
21const ANCHOR_MSRV: &str = "1.89.0";
22
23#[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum, AbsolutePath)]
25pub enum ProgramTemplate {
26 Single,
28 #[default]
30 Multiple,
31}
32
33pub fn create_program(
35 name: &str,
36 template: ProgramTemplate,
37 test_template: Option<&TestTemplate>,
38) -> Result<()> {
39 let program_path = Path::new("programs").join(name);
40 let lib_rs_path = program_path.join("src").join("lib.rs");
41 let common_files = vec![
42 ("Cargo.toml".into(), workspace_manifest()),
43 ("rust-toolchain.toml".into(), rust_toolchain_toml()),
44 (
45 program_path.join("Cargo.toml"),
46 cargo_toml(name, test_template),
47 ),
48 (lib_rs_path.clone(), "".into()),
52 ];
54
55 create_files(&common_files)?;
56
57 let target_path = crate::target_dir()?;
58
59 fs::remove_file(&lib_rs_path)?;
61
62 let template_files = match template {
63 ProgramTemplate::Single => {
64 println!(
65 "Note: Using single-file template. For better code organization and \
66 maintainability, consider using --template multiple (default)."
67 );
68 create_program_template_single(name, &program_path, target_path)
69 }
70 ProgramTemplate::Multiple => {
71 create_program_template_multiple(name, &program_path, target_path)
72 }
73 };
74
75 create_files(&template_files)
76}
77
78fn rust_toolchain_toml() -> String {
80 format!(
81 r#"[toolchain]
82channel = "{ANCHOR_MSRV}"
83components = ["rustfmt","clippy"]
84profile = "minimal"
85"#
86 )
87}
88
89fn create_program_template_single(name: &str, program_path: &Path, target_path: &Path) -> Files {
91 vec![(
92 program_path.join("src").join("lib.rs"),
93 format!(
94 r#"use anchor_lang::prelude::*;
95
96declare_id!("{}");
97
98#[program]
99pub mod {} {{
100 use super::*;
101
102 pub fn initialize(ctx: &mut Context<Initialize>) -> Result<()> {{
103 ctx.accounts.counter.count = 0;
104 ctx.accounts.counter.authority = *ctx.accounts.payer.address();
105 msg!("Counter initialized");
106 Ok(())
107 }}
108}}
109
110pub mod state {{
111 use super::*;
112
113 #[account]
114 pub struct Counter {{
115 pub count: u64,
116 pub authority: Address,
117 }}
118}}
119
120use state::Counter;
121
122#[derive(Accounts)]
123pub struct Initialize {{
124 #[account(mut)]
125 pub payer: Signer,
126 #[account(init, payer = payer)]
127 pub counter: Account<Counter>,
128 pub system_program: Program<System>,
129}}
130"#,
131 get_or_create_program_id(name, target_path),
132 name.to_snake_case(),
133 ),
134 )]
135}
136
137fn create_program_template_multiple(name: &str, program_path: &Path, target_path: &Path) -> Files {
139 let src_path = program_path.join("src");
140 vec![
141 (
142 src_path.join("lib.rs"),
143 format!(
144 r#"pub mod constants;
145pub mod error;
146pub mod instructions;
147pub mod state;
148
149use anchor_lang::prelude::*;
150
151pub use instructions::*;
152
153declare_id!("{}");
154
155#[program]
156pub mod {} {{
157 use super::*;
158
159 pub fn initialize(ctx: &mut Context<Initialize>) -> Result<()> {{
160 initialize::handler(ctx)
161 }}
162}}
163"#,
164 get_or_create_program_id(name, target_path),
165 name.to_snake_case(),
166 ),
167 ),
168 (
169 src_path.join("constants.rs"),
170 r#"use anchor_lang::prelude::*;
171
172#[constant]
173pub const SEED: &str = "anchor";
174"#
175 .into(),
176 ),
177 (
178 src_path.join("error.rs"),
179 r#"use anchor_lang::prelude::*;
180
181#[error_code]
182pub enum ErrorCode {
183 #[msg("Custom error message")]
184 CustomError,
185}
186"#
187 .into(),
188 ),
189 (
190 src_path.join("instructions.rs"),
191 r#"pub mod initialize;
192
193pub use initialize::*;
194"#
195 .into(),
196 ),
197 (
198 src_path.join("instructions").join("initialize.rs"),
199 r#"use anchor_lang::prelude::*;
200
201use crate::state::Counter;
202
203#[derive(Accounts)]
204pub struct Initialize {
205 #[account(mut)]
206 pub payer: Signer,
207 #[account(init, payer = payer)]
208 pub counter: Account<Counter>,
209 pub system_program: Program<System>,
210}
211
212pub fn handler(ctx: &mut Context<Initialize>) -> Result<()> {
213 ctx.accounts.counter.count = 0;
214 ctx.accounts.counter.authority = *ctx.accounts.payer.address();
215 msg!("Counter initialized");
216 Ok(())
217}
218"#
219 .into(),
220 ),
221 (
222 src_path.join("state.rs"),
223 r#"use anchor_lang::prelude::*;
224
225#[account]
226pub struct Counter {
227 pub count: u64,
228 pub authority: Address,
229}
230"#
231 .into(),
232 ),
233 ]
234}
235
236fn workspace_manifest() -> String {
237 format!(
238 r#"[workspace]
239members = [
240 "programs/*"
241]
242resolver = "2"
243
244[workspace.package]
245edition = "2021"
246rust-version = "{ANCHOR_MSRV}"
247
248[profile.release]
249overflow-checks = true
250lto = "fat"
251codegen-units = 1
252[profile.release.build-override]
253opt-level = 3
254incremental = false
255codegen-units = 1
256"#
257 )
258}
259
260fn cargo_toml(name: &str, test_template: Option<&TestTemplate>) -> String {
261 let template_features = match test_template {
266 Some(TestTemplate::Mollusk) => r#"test-sbf = []"#,
267 Some(TestTemplate::Litesvm) => r#"profile = ["anchor-v2-testing/profile"]"#,
268 _ => "",
269 };
270 let dev_dependencies = match test_template {
271 Some(TestTemplate::Mollusk) => {
272 r#"
273[dev-dependencies]
274mollusk-svm = "~0.10"
275solana-account = "3"
276solana-pubkey = "4"
277solana-sdk-ids = "3"
278bytemuck = "1"
279"#
280 }
281 Some(TestTemplate::Litesvm) => {
282 r#"
283[dev-dependencies]
284anchor-v2-testing = { git = "https://github.com/otter-sec/anchor.git", branch = "anchor-next" }
285"#
286 }
287 _ => "",
288 };
289
290 format!(
291 r#"[package]
292name = "{0}"
293version = "0.1.0"
294description = "Created with Anchor"
295edition.workspace = true
296rust-version.workspace = true
297
298[lib]
299crate-type = ["cdylib", "lib"]
300name = "{1}"
301
302[features]
303default = []
304cpi = ["no-entrypoint"]
305no-entrypoint = []
306no-log-ix-name = []
307idl-build = []
308{2}
309
310[dependencies]
311# Once anchor-lang is published to crates.io, swap to: anchor-lang = "{3}"
312anchor-lang = {{ git = "https://github.com/otter-sec/anchor.git", branch = "anchor-next" }}
313solana-program-log = {{ version = "1.1", features = ["macro"] }}
314wincode = {{ version = "0.5", features = ["derive"] }}
315{4}
316
317[lints.rust]
318unexpected_cfgs = {{ level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }}
319"#,
320 name,
321 name.to_snake_case(),
322 template_features,
323 VERSION,
324 dev_dependencies,
325 )
326}
327
328pub fn get_or_create_program_id(name: &str, target_path: impl AsRef<Path>) -> Pubkey {
330 let keypair_path = target_path
331 .as_ref()
332 .join("deploy")
333 .join(format!("{}-keypair.json", name.to_snake_case()));
334
335 read_keypair_file(&keypair_path)
336 .unwrap_or_else(|_| {
337 let keypair = Keypair::new();
338 write_keypair_file(&keypair, keypair_path).expect("Unable to create program keypair");
339 keypair
340 })
341 .pubkey()
342}
343
344pub fn deploy_js_script_host(cluster_url: &str, script_path: &str) -> String {
345 format!(
346 r#"
347const anchor = require('@anchor-lang/core');
348
349// Deploy script defined by the user.
350const userScript = require("{script_path}");
351
352async function main() {{
353 const connection = new anchor.web3.Connection(
354 "{cluster_url}",
355 anchor.AnchorProvider.defaultOptions().commitment
356 );
357 const wallet = anchor.Wallet.local();
358 const provider = new anchor.AnchorProvider(connection, wallet);
359
360 // Run the user's deploy script.
361 userScript(provider);
362}}
363main();
364"#,
365 )
366}
367
368pub fn deploy_ts_script_host(cluster_url: &str, script_path: &str) -> String {
369 format!(
370 r#"import * as anchor from '@anchor-lang/core';
371
372// Deploy script defined by the user.
373const userScript = require("{script_path}");
374
375async function main() {{
376 const connection = new anchor.web3.Connection(
377 "{cluster_url}",
378 anchor.AnchorProvider.defaultOptions().commitment
379 );
380 const wallet = anchor.Wallet.local();
381 const provider = new anchor.AnchorProvider(connection, wallet);
382
383 // Run the user's deploy script.
384 userScript(provider);
385}}
386main();
387"#,
388 )
389}
390
391pub fn deploy_script() -> &'static str {
392 r#"// Migrations are an early feature. Currently, they're nothing more than this
393// single deploy script that's invoked from the CLI, injecting a provider
394// configured from the workspace's Anchor.toml.
395
396const anchor = require("@anchor-lang/core");
397
398module.exports = async function (provider) {
399 // Configure client to use the provider.
400 anchor.setProvider(provider);
401
402 // Add your deploy script here.
403};
404"#
405}
406
407pub fn ts_deploy_script() -> &'static str {
408 r#"// Migrations are an early feature. Currently, they're nothing more than this
409// single deploy script that's invoked from the CLI, injecting a provider
410// configured from the workspace's Anchor.toml.
411
412import * as anchor from "@anchor-lang/core";
413
414module.exports = async function (provider: anchor.AnchorProvider) {
415 // Configure client to use the provider.
416 anchor.setProvider(provider);
417
418 // Add your deploy script here.
419};
420"#
421}
422
423pub fn mocha(name: &str) -> String {
424 format!(
425 r#"const anchor = require("@anchor-lang/core");
426
427describe("{}", () => {{
428 // Configure the client to use the local cluster.
429 anchor.setProvider(anchor.AnchorProvider.env());
430
431 it("Is initialized!", async () => {{
432 // Add your test here.
433 const program = anchor.workspace.{};
434 const counter = anchor.web3.Keypair.generate();
435 const tx = await program.methods
436 .initialize()
437 .accounts({{ counter: counter.publicKey }})
438 .signers([counter])
439 .rpc();
440 console.log("Your transaction signature", tx);
441 }});
442}});
443"#,
444 name,
445 name.to_lower_camel_case(),
446 )
447}
448
449pub fn js_jest(name: &str) -> String {
450 format!(
451 r#"const anchor = require("@anchor-lang/core");
452
453describe("{}", () => {{
454 // Configure the client to use the local cluster.
455 anchor.setProvider(anchor.AnchorProvider.env());
456
457 it("Is initialized!", async () => {{
458 // Add your test here.
459 const program = anchor.workspace.{};
460 const counter = anchor.web3.Keypair.generate();
461 const tx = await program.methods
462 .initialize()
463 .accounts({{ counter: counter.publicKey }})
464 .signers([counter])
465 .rpc();
466 console.log("Your transaction signature", tx);
467 }});
468}});
469"#,
470 name,
471 name.to_lower_camel_case(),
472 )
473}
474
475pub fn package_json(jest: bool, license: String) -> String {
478 if jest {
479 format!(
480 r#"{{
481 "license": "{license}",
482 "scripts": {{
483 "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
484 "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
485 }},
486 "dependencies": {{
487 "@anchor-lang/core": "^1.0.0"
488 }},
489 "devDependencies": {{
490 "jest": "^30.3.0",
491 "prettier": "^3.8.3"
492 }},
493 "overrides": {{
494 "uuid": "^9.0.1"
495 }},
496 "resolutions": {{
497 "uuid": "^9.0.1"
498 }},
499 "pnpm": {{
500 "overrides": {{
501 "uuid": "^9.0.1"
502 }}
503 }}
504}}
505 "#
506 )
507 } else {
508 format!(
509 r#"{{
510 "license": "{license}",
511 "scripts": {{
512 "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
513 "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
514 }},
515 "dependencies": {{
516 "@anchor-lang/core": "^1.0.0"
517 }},
518 "devDependencies": {{
519 "chai": "^4.5.0",
520 "mocha": "^11.7.5",
521 "prettier": "^3.8.3"
522 }}
523}}
524"#
525 )
526 }
527}
528
529pub fn ts_package_json(jest: bool, license: String) -> String {
531 if jest {
532 format!(
533 r#"{{
534 "license": "{license}",
535 "scripts": {{
536 "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
537 "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
538 }},
539 "dependencies": {{
540 "@anchor-lang/core": "^1.0.0"
541 }},
542 "devDependencies": {{
543 "@types/bn.js": "^5.2.0",
544 "@types/jest": "^30.0.0",
545 "jest": "^30.3.0",
546 "prettier": "^3.8.3",
547 "ts-jest": "^29.4.9",
548 "typescript": "^5.9.3"
549 }},
550 "overrides": {{
551 "uuid": "^9.0.1"
552 }},
553 "resolutions": {{
554 "uuid": "^9.0.1"
555 }},
556 "pnpm": {{
557 "overrides": {{
558 "uuid": "^9.0.1"
559 }}
560 }}
561}}
562"#
563 )
564 } else {
565 format!(
566 r#"{{
567 "license": "{license}",
568 "scripts": {{
569 "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
570 "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
571 }},
572 "dependencies": {{
573 "@anchor-lang/core": "^1.0.0"
574 }},
575 "devDependencies": {{
576 "chai": "^4.5.0",
577 "mocha": "^11.7.5",
578 "ts-mocha": "^11.1.0",
579 "ts-node": "^10.9.2",
580 "@types/bn.js": "^5.2.0",
581 "@types/chai": "^4.3.0",
582 "@types/mocha": "^10.0.10",
583 "@types/node": "^25.6.0",
584 "typescript": "^5.9.3",
585 "prettier": "^3.8.3"
586 }}
587}}
588"#
589 )
590 }
591}
592
593pub fn ts_mocha(name: &str) -> String {
594 format!(
595 r#"import * as anchor from "@anchor-lang/core";
596import {{ Program }} from "@anchor-lang/core";
597import {{ {} }} from "../target/types/{}";
598
599describe("{}", () => {{
600 // Configure the client to use the local cluster.
601 anchor.setProvider(anchor.AnchorProvider.env());
602
603 const program = anchor.workspace.{} as Program<{}>;
604
605 it("Is initialized!", async () => {{
606 // Add your test here.
607 const counter = anchor.web3.Keypair.generate();
608 const tx = await program.methods
609 .initialize()
610 .accounts({{ counter: counter.publicKey }})
611 .signers([counter])
612 .rpc();
613 console.log("Your transaction signature", tx);
614 }});
615}});
616"#,
617 name.to_pascal_case(),
618 name.to_snake_case(),
619 name,
620 name.to_lower_camel_case(),
621 name.to_pascal_case(),
622 )
623}
624
625pub fn ts_jest(name: &str) -> String {
626 format!(
627 r#"import * as anchor from "@anchor-lang/core";
628import {{ Program }} from "@anchor-lang/core";
629import {{ {} }} from "../target/types/{}";
630
631describe("{}", () => {{
632 // Configure the client to use the local cluster.
633 anchor.setProvider(anchor.AnchorProvider.env());
634
635 const program = anchor.workspace.{} as Program<{}>;
636
637 it("Is initialized!", async () => {{
638 // Add your test here.
639 const counter = anchor.web3.Keypair.generate();
640 const tx = await program.methods
641 .initialize()
642 .accounts({{ counter: counter.publicKey }})
643 .signers([counter])
644 .rpc();
645 console.log("Your transaction signature", tx);
646 }});
647}});
648"#,
649 name.to_pascal_case(),
650 name.to_snake_case(),
651 name,
652 name.to_lower_camel_case(),
653 name.to_pascal_case(),
654 )
655}
656
657pub fn ts_config(jest: bool) -> &'static str {
658 if jest {
659 r#"{
660 "compilerOptions": {
661 "types": ["jest"],
662 "typeRoots": ["./node_modules/@types"],
663 "lib": ["es2015"],
664 "module": "commonjs",
665 "target": "es6",
666 "esModuleInterop": true
667 }
668}
669"#
670 } else {
671 r#"{
672 "compilerOptions": {
673 "types": ["mocha", "chai"],
674 "typeRoots": ["./node_modules/@types"],
675 "lib": ["es2015"],
676 "module": "commonjs",
677 "target": "es6",
678 "esModuleInterop": true
679 }
680}
681"#
682 }
683}
684
685pub fn git_ignore() -> &'static str {
686 r#".anchor
687.DS_Store
688target
689**/*.rs.bk
690node_modules
691test-ledger
692.yarn
693.surfpool
694"#
695}
696
697pub fn prettier_ignore() -> &'static str {
698 r#".anchor
699.DS_Store
700target
701node_modules
702dist
703build
704test-ledger
705"#
706}
707
708pub fn node_shell(
709 cluster_url: &str,
710 wallet_path: &str,
711 programs: Vec<ProgramWorkspace>,
712) -> Result<String> {
713 let mut eval_string = format!(
714 r#"
715const anchor = require('@anchor-lang/core');
716const web3 = anchor.web3;
717const PublicKey = anchor.web3.PublicKey;
718const Keypair = anchor.web3.Keypair;
719
720const __wallet = new anchor.Wallet(
721 Keypair.fromSecretKey(
722 Buffer.from(
723 JSON.parse(
724 require('fs').readFileSync(
725 "{wallet_path}",
726 {{
727 encoding: "utf-8",
728 }},
729 ),
730 ),
731 ),
732 ),
733);
734const __connection = new web3.Connection("{cluster_url}", "processed");
735const provider = new anchor.AnchorProvider(__connection, __wallet, {{
736 commitment: "processed",
737 preflightcommitment: "processed",
738}});
739anchor.setProvider(provider);
740"#,
741 );
742
743 for program in programs {
744 write!(
745 &mut eval_string,
746 r#"
747anchor.workspace.{} = new anchor.Program({}, provider);
748"#,
749 program.name.to_lower_camel_case(),
750 serde_json::to_string(&program.idl)?,
751 )?;
752 }
753
754 Ok(eval_string)
755}
756
757#[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum, AbsolutePath)]
759pub enum TestTemplate {
760 Mocha,
762 Jest,
764 Rust,
766 Mollusk,
768 #[default]
770 Litesvm,
771}
772
773impl TestTemplate {
774 pub fn get_test_script(&self, js: bool, pkg_manager: &PackageManager) -> String {
775 let pkg_manager_exec_cmd = match pkg_manager {
776 PackageManager::Yarn => "yarn run",
777 PackageManager::NPM => "npx",
778 PackageManager::PNPM => "pnpm exec",
779 PackageManager::Bun => "bunx",
780 };
781
782 match &self {
783 Self::Mocha => {
784 if js {
785 format!("{pkg_manager_exec_cmd} mocha -t 1000000 tests/")
786 } else {
787 format!(
788 r#"{pkg_manager_exec_cmd} ts-mocha -p ./tsconfig.json -t 1000000 "tests/**/*.ts""#
789 )
790 }
791 }
792 Self::Jest => {
793 if js {
794 format!("{pkg_manager_exec_cmd} jest")
795 } else {
796 format!("{pkg_manager_exec_cmd} jest --preset ts-jest")
797 }
798 }
799 Self::Rust | Self::Litesvm => "cargo test".to_owned(),
800 Self::Mollusk => "cargo test-sbf".to_owned(),
801 }
802 }
803
804 pub fn create_test_files(&self, project_name: &str, js: bool, program_id: &str) -> Result<()> {
805 match self {
806 Self::Mocha => {
807 fs::create_dir_all("tests")?;
809
810 if js {
811 let mut test = File::create(format!("tests/{}.js", &project_name))?;
812 test.write_all(mocha(project_name).as_bytes())?;
813 } else {
814 let mut mocha = File::create(format!("tests/{}.ts", &project_name))?;
815 mocha.write_all(ts_mocha(project_name).as_bytes())?;
816 }
817 }
818 Self::Jest => {
819 fs::create_dir_all("tests")?;
821
822 if js {
823 let mut test = File::create(format!("tests/{}.test.js", &project_name))?;
824 test.write_all(js_jest(project_name).as_bytes())?;
825 } else {
826 let mut test = File::create(format!("tests/{}.test.ts", &project_name))?;
827 test.write_all(ts_jest(project_name).as_bytes())?;
828 }
829 }
830 Self::Rust => {
831 let exit = std::process::Command::new("cargo")
833 .arg("new")
834 .arg("--vcs")
835 .arg("none")
836 .arg("--lib")
837 .arg("tests")
838 .stderr(Stdio::inherit())
839 .output()
840 .map_err(|e| anyhow::format_err!("{}", e))?;
841 if !exit.status.success() {
842 eprintln!("'cargo new --lib tests' failed");
843 std::process::exit(exit.status.code().unwrap_or(1));
844 }
845
846 let mut files = Vec::new();
847 let tests_path = Path::new("tests");
848 files.extend(vec![(
849 tests_path.join("Cargo.toml"),
850 tests_cargo_toml(project_name),
851 )]);
852 files.extend(create_program_template_rust_test(
853 project_name,
854 tests_path,
855 program_id,
856 ));
857 override_or_create_files(&files)?;
858 }
859 Self::Mollusk => {
860 let tests_path_str = format!("programs/{}/tests", &project_name);
862 let tests_path = Path::new(&tests_path_str);
863 fs::create_dir_all(tests_path)?;
864
865 let mut files = Vec::new();
866 files.extend(create_program_template_mollusk_test(
867 project_name,
868 tests_path,
869 ));
870 override_or_create_files(&files)?;
871 }
872
873 Self::Litesvm => {
874 let tests_path_str = format!("programs/{}/tests", &project_name);
875 let tests_path = Path::new(&tests_path_str);
876 fs::create_dir_all(tests_path)?;
877 let mut files = Vec::new();
878 files.extend(create_program_template_litesvm_test(
879 project_name,
880 tests_path,
881 ));
882 override_or_create_files(&files)?;
883 }
884 }
885
886 Ok(())
887 }
888}
889
890pub fn tests_cargo_toml(name: &str) -> String {
891 format!(
892 r#"[package]
893name = "tests"
894version = "0.1.0"
895description = "Created with Anchor"
896edition = "2021"
897rust-version = "{ANCHOR_MSRV}"
898
899[dependencies]
900# Once anchor-client v2 is published to crates.io, swap to: anchor-client = "{VERSION}"
901anchor-client = {{ git = "https://github.com/otter-sec/anchor.git", branch = "anchor-next" }}
902{name} = {{ version = "0.1.0", path = "../programs/{name}" }}
903solana-keypair = "3.0.0"
904solana-pubkey = "3.0.0"
905solana-sdk-ids = "3"
906solana-signer = "3"
907"#
908 )
909}
910
911fn create_program_template_rust_test(name: &str, tests_path: &Path, program_id: &str) -> Files {
913 let src_path = tests_path.join("src");
914 vec![
915 (
916 src_path.join("lib.rs"),
917 r#"#[cfg(test)]
918mod test_initialize;
919"#
920 .into(),
921 ),
922 (
923 src_path.join("test_initialize.rs"),
924 format!(
925 r#"use anchor_client::{{
926 CommitmentConfig,
927 Client, Cluster,
928}};
929use solana_keypair::{{read_keypair_file, Keypair}};
930use solana_pubkey::Pubkey;
931use solana_signer::Signer;
932
933#[test]
934fn test_initialize() {{
935 let program_id = "{0}";
936 let anchor_wallet = std::env::var("ANCHOR_WALLET").unwrap();
937 let payer = read_keypair_file(&anchor_wallet).unwrap();
938 let counter = Keypair::new();
939
940 let client = Client::new_with_options(Cluster::Localnet, &payer, CommitmentConfig::confirmed());
941 let program_id = Pubkey::try_from(program_id).unwrap();
942 let program = client.program(program_id).unwrap();
943
944 let tx = program
945 .request()
946 .accounts({1}::accounts::Initialize {{
947 payer: payer.pubkey(),
948 counter: counter.pubkey(),
949 system_program: solana_sdk_ids::system_program::id(),
950 }})
951 .args({1}::instruction::Initialize {{}})
952 .signer(&counter)
953 .send()
954 .expect("");
955
956 println!("Your transaction signature {{}}", tx);
957}}
958"#,
959 program_id,
960 name.to_snake_case(),
961 ),
962 ),
963 ]
964}
965
966fn create_program_template_mollusk_test(name: &str, tests_path: &Path) -> Files {
968 vec![(
969 tests_path.join("test_initialize.rs"),
970 format!(
971 r#"#![cfg(feature = "test-sbf")]
972
973use {{
974 anchor_lang::{{
975 accounts::Account, solana_program::instruction::Instruction, InstructionData, Space,
976 ToAccountMetas,
977 }},
978 mollusk_svm::{{program::keyed_account_for_system_program, result::Check, Mollusk}},
979 solana_account::Account as SolanaAccount,
980 solana_pubkey::Pubkey,
981}};
982
983#[test]
984fn test_initialize() {{
985 let program_id = {0}::id();
986 let mollusk = Mollusk::new(&program_id, "{0}");
987
988 let payer = Pubkey::new_unique();
989 let counter = Pubkey::new_unique();
990
991 let instruction = Instruction::new_with_bytes(
992 program_id,
993 &{0}::instruction::Initialize {{}}.data(),
994 {0}::accounts::Initialize {{
995 payer,
996 counter,
997 system_program: solana_sdk_ids::system_program::id(),
998 }}
999 .to_account_metas(None),
1000 );
1001
1002 let counter_space = <Account<{0}::state::Counter> as Space>::INIT_SPACE;
1003 let accounts = vec![
1004 (
1005 payer,
1006 SolanaAccount::new(1_000_000_000, 0, &solana_sdk_ids::system_program::id()),
1007 ),
1008 (counter, SolanaAccount::default()),
1009 keyed_account_for_system_program(),
1010 ];
1011
1012 let result = mollusk.process_and_validate_instruction(
1013 &instruction,
1014 &accounts,
1015 &[Check::success()],
1016 );
1017
1018 let counter_account = result
1019 .resulting_accounts
1020 .iter()
1021 .find(|(pk, _)| *pk == counter)
1022 .map(|(_, a)| a)
1023 .expect("counter account");
1024 assert_eq!(counter_account.data.len(), counter_space);
1025 let counter_state: &{0}::state::Counter = bytemuck::from_bytes(&counter_account.data[8..]);
1026 assert_eq!(counter_state.count, 0);
1027 assert_eq!(counter_state.authority, payer);
1028}}
1029"#,
1030 name.to_snake_case(),
1031 ),
1032 )]
1033}
1034
1035fn create_program_template_litesvm_test(name: &str, tests_path: &Path) -> Files {
1037 vec![(
1038 tests_path.join("test_initialize.rs"),
1039 format!(
1040 r#"
1041use {{
1042 anchor_lang::{{
1043 accounts::Account, bytemuck, programs::System,
1044 solana_program::instruction::Instruction, Id, InstructionData, Space, ToAccountMetas,
1045 }},
1046 anchor_v2_testing::{{Keypair, LiteSVM, Message, Signer, VersionedMessage, VersionedTransaction}},
1047}};
1048
1049#[test]
1050fn test_initialize() {{
1051 let program_id = {0}::id();
1052 let payer = Keypair::new();
1053 let counter = Keypair::new();
1054
1055 // `svm()` is `LiteSVM::new()` by default. When this crate is built
1056 // with `--features profile` (which `anchor test --profile` and
1057 // `anchor debugger` set automatically), it also installs the
1058 // register-tracing callback that writes per-test SBF traces under
1059 // `target/anchor-v2-profile/`. The cfg switch lives inside
1060 // `anchor-v2-testing` so test code stays clean either way.
1061 let mut svm = anchor_v2_testing::svm();
1062 let bytes = include_bytes!("../../../target/deploy/{0}.so");
1063 svm.add_program(program_id, bytes).unwrap();
1064 svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
1065
1066 let instruction = Instruction::new_with_bytes(
1067 program_id,
1068 &{0}::instruction::Initialize {{}}.data(),
1069 {0}::accounts::Initialize {{
1070 payer: payer.pubkey(),
1071 counter: counter.pubkey(),
1072 system_program: System::id(),
1073 }}
1074 .to_account_metas(None),
1075 );
1076
1077 let blockhash = svm.latest_blockhash();
1078 let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);
1079 let tx = VersionedTransaction::try_new(
1080 VersionedMessage::Legacy(msg),
1081 &[&payer, &counter],
1082 )
1083 .unwrap();
1084
1085 let res = svm.send_transaction(tx);
1086 assert!(res.is_ok(), "send_transaction failed: {{:?}}", res);
1087
1088 // Verify the counter account was initialized. Size comes from the same
1089 // `Space::INIT_SPACE` expression the `init` constraint allocates with,
1090 // so the assertion doesn't rot if `Counter` gains fields. The payload
1091 // tail is a `Pod` struct, so we cast directly and read fields by name
1092 // instead of hand-slicing bytes.
1093 let account = svm.get_account(&counter.pubkey()).expect("counter account");
1094 assert_eq!(account.data.len(), <Account<{0}::state::Counter> as Space>::INIT_SPACE);
1095 let counter_state: &{0}::state::Counter = bytemuck::from_bytes(&account.data[8..]);
1096 assert_eq!(counter_state.count, 0);
1097 assert_eq!(counter_state.authority, payer.pubkey());
1098}}
1099"#,
1100 name.to_snake_case(),
1101 ),
1102 )]
1103}