Skip to main content

anchor_cli/
template.rs

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";
22const ANCHOR_V2_TEMPLATE_VERSION: &str = "2.0.0";
23
24/// Anchor template version to generate.
25#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Parser, ValueEnum, AbsolutePath)]
26pub enum AnchorVersion {
27    /// Generate Anchor v1 templates.
28    #[default]
29    V1,
30    /// Generate Anchor v2 templates.
31    V2,
32}
33
34/// Program initialization template
35#[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum, AbsolutePath)]
36pub enum ProgramTemplate {
37    /// Program with a single `lib.rs` file (not recommended for production)
38    Single,
39    /// Program with multiple files for instructions, state... (recommended)
40    #[default]
41    Multiple,
42}
43
44/// Create a program from the given name and template.
45pub fn create_program(
46    name: &str,
47    template: ProgramTemplate,
48    test_template: Option<&TestTemplate>,
49    anchor_version: AnchorVersion,
50) -> Result<()> {
51    let program_path = Path::new("programs").join(name);
52    let lib_rs_path = program_path.join("src").join("lib.rs");
53    let common_files = vec![
54        ("Cargo.toml".into(), workspace_manifest()),
55        ("rust-toolchain.toml".into(), rust_toolchain_toml()),
56        (
57            program_path.join("Cargo.toml"),
58            cargo_toml(name, test_template, anchor_version),
59        ),
60        // One of the create_program_template_* functions will write the full
61        // lib.rs, but we need an empty stub for now so cargo won't throw an
62        // error when asking it where the `target` dir is located.
63        (lib_rs_path.clone(), "".into()),
64        // Note: Xargo.toml is no longer needed for modern Solana builds using SBF.
65    ];
66
67    create_files(&common_files)?;
68
69    let target_path = crate::target_dir()?;
70
71    // Remove the stub version
72    fs::remove_file(&lib_rs_path)?;
73
74    let template_files = match template {
75        ProgramTemplate::Single => {
76            println!(
77                "Note: Using single-file template. For better code organization and \
78                 maintainability, consider using --template multiple (default)."
79            );
80            create_program_template_single(name, &program_path, target_path, anchor_version)
81        }
82        ProgramTemplate::Multiple => {
83            create_program_template_multiple(name, &program_path, target_path, anchor_version)
84        }
85    };
86
87    create_files(&template_files)
88}
89
90/// Helper to create a rust-toolchain.toml at the workspace root
91fn rust_toolchain_toml() -> String {
92    format!(
93        r#"[toolchain]
94channel = "{ANCHOR_MSRV}"
95components = ["rustfmt","clippy"]
96profile = "minimal"
97"#
98    )
99}
100
101/// Create a program with a single `lib.rs` file.
102fn create_program_template_single(
103    name: &str,
104    program_path: &Path,
105    target_path: &Path,
106    anchor_version: AnchorVersion,
107) -> Files {
108    match anchor_version {
109        AnchorVersion::V1 => create_program_template_single_v1(name, program_path, target_path),
110        AnchorVersion::V2 => create_program_template_single_v2(name, program_path, target_path),
111    }
112}
113
114fn create_program_template_single_v1(name: &str, program_path: &Path, target_path: &Path) -> Files {
115    vec![(
116        program_path.join("src").join("lib.rs"),
117        format!(
118            r#"use anchor_lang::prelude::*;
119
120declare_id!("{}");
121
122#[program]
123pub mod {} {{
124    use super::*;
125
126    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
127        ctx.accounts.counter.count = 0;
128        ctx.accounts.counter.authority = ctx.accounts.payer.key();
129
130        let cpi_accounts = anchor_lang::system_program::Transfer {{
131            from: ctx.accounts.payer.to_account_info(),
132            to: ctx.accounts.counter.to_account_info(),
133        }};
134        let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
135        anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
136
137        msg!("Hello, world! Counter initialized");
138        Ok(())
139    }}
140
141    pub fn increment(ctx: Context<Increment>) -> Result<()> {{
142        require_keys_eq!(
143            ctx.accounts.counter.authority,
144            ctx.accounts.authority.key(),
145            ErrorCode::Unauthorized,
146        );
147        require!(
148            ctx.accounts.counter.count < MAX_COUNT,
149            ErrorCode::CounterOverflow,
150        );
151
152        ctx.accounts.counter.count += 1;
153        msg!("Hello, world! Counter is now {{}}", ctx.accounts.counter.count);
154        Ok(())
155    }}
156}}
157
158#[derive(Accounts)]
159pub struct Initialize<'info> {{
160    #[account(mut)]
161    pub payer: Signer<'info>,
162    #[account(
163        init,
164        payer = payer,
165        space = 8 + Counter::INIT_SPACE,
166        seeds = [COUNTER_SEED],
167        bump
168    )]
169    pub counter: Account<'info, Counter>,
170    pub system_program: Program<'info, System>,
171}}
172
173#[derive(Accounts)]
174pub struct Increment<'info> {{
175    #[account(mut, seeds = [COUNTER_SEED], bump)]
176    pub counter: Account<'info, Counter>,
177    pub authority: Signer<'info>,
178}}
179
180pub mod constants {{
181    use super::*;
182
183    #[constant]
184    pub const COUNTER_SEED: &[u8] = b"counter";
185
186    #[constant]
187    pub const HELLO_WORLD_LAMPORTS: u64 = 1;
188
189    #[constant]
190    pub const MAX_COUNT: u64 = 10;
191}}
192
193pub mod error {{
194    use super::*;
195
196    #[error_code]
197    pub enum ErrorCode {{
198        #[msg("Only the counter authority can update this counter")]
199        Unauthorized,
200        #[msg("Counter has reached the maximum value")]
201        CounterOverflow,
202    }}
203}}
204
205pub mod state {{
206    use super::*;
207
208    #[account]
209    #[derive(InitSpace)]
210    pub struct Counter {{
211        pub count: u64,
212        pub authority: Pubkey,
213    }}
214}}
215
216use constants::*;
217use error::ErrorCode;
218use state::Counter;
219"#,
220            get_or_create_program_id(name, target_path),
221            name.to_snake_case(),
222        ),
223    )]
224}
225
226fn create_program_template_single_v2(name: &str, program_path: &Path, target_path: &Path) -> Files {
227    vec![(
228        program_path.join("src").join("lib.rs"),
229        format!(
230            r#"use anchor_lang_v2::prelude::*;
231
232declare_id!("{}");
233
234#[program]
235pub mod {} {{
236    use super::*;
237
238    pub fn initialize(ctx: &mut Context<Initialize>) -> Result<()> {{
239        ctx.accounts.counter.count = 0;
240        ctx.accounts.counter.authority = *ctx.accounts.payer.address();
241        msg!("Counter initialized");
242        Ok(())
243    }}
244}}
245
246pub mod state {{
247    use super::*;
248
249    #[account]
250    pub struct Counter {{
251        pub count: u64,
252        pub authority: Address,
253    }}
254}}
255
256use state::Counter;
257
258#[derive(Accounts)]
259pub struct Initialize {{
260    #[account(mut)]
261    pub payer: Signer,
262    #[account(init, payer = payer)]
263    pub counter: Account<Counter>,
264    pub system_program: Program<System>,
265}}
266"#,
267            get_or_create_program_id(name, target_path),
268            name.to_snake_case(),
269        ),
270    )]
271}
272
273/// Create a program with multiple files for instructions, state...
274fn create_program_template_multiple(
275    name: &str,
276    program_path: &Path,
277    target_path: &Path,
278    anchor_version: AnchorVersion,
279) -> Files {
280    match anchor_version {
281        AnchorVersion::V1 => create_program_template_multiple_v1(name, program_path, target_path),
282        AnchorVersion::V2 => create_program_template_multiple_v2(name, program_path, target_path),
283    }
284}
285
286fn create_program_template_multiple_v1(
287    name: &str,
288    program_path: &Path,
289    target_path: &Path,
290) -> Files {
291    let src_path = program_path.join("src");
292    vec![
293        (
294            src_path.join("lib.rs"),
295            format!(
296                r#"pub mod constants;
297pub mod error;
298pub mod instructions;
299pub mod state;
300
301use anchor_lang::prelude::*;
302
303pub use constants::*;
304pub use instructions::*;
305pub use state::*;
306
307declare_id!("{}");
308
309#[program]
310pub mod {} {{
311    use super::*;
312
313    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
314        crate::instructions::initialize::handle_initialize(ctx)
315    }}
316
317    pub fn increment(ctx: Context<Increment>) -> Result<()> {{
318        crate::instructions::increment::handle_increment(ctx)
319    }}
320}}
321"#,
322                get_or_create_program_id(name, target_path),
323                name.to_snake_case(),
324            ),
325        ),
326        (
327            src_path.join("constants.rs"),
328            r#"use anchor_lang::prelude::*;
329
330#[constant]
331pub const COUNTER_SEED: &[u8] = b"counter";
332
333#[constant]
334pub const HELLO_WORLD_LAMPORTS: u64 = 1;
335
336#[constant]
337pub const MAX_COUNT: u64 = 10;
338"#
339            .into(),
340        ),
341        (
342            src_path.join("error.rs"),
343            r#"use anchor_lang::prelude::*;
344
345#[error_code]
346pub enum ErrorCode {
347    #[msg("Only the counter authority can update this counter")]
348    Unauthorized,
349    #[msg("Counter has reached the maximum value")]
350    CounterOverflow,
351}
352"#
353            .into(),
354        ),
355        (
356            src_path.join("instructions.rs"),
357            r#"pub mod initialize;
358pub mod increment;
359
360pub use initialize::*;
361pub use increment::*;
362"#
363            .into(),
364        ),
365        (
366            src_path.join("instructions").join("initialize.rs"),
367            r#"use anchor_lang::prelude::*;
368
369use crate::{constants::*, state::Counter};
370
371#[derive(Accounts)]
372pub struct Initialize<'info> {
373    #[account(mut)]
374    pub payer: Signer<'info>,
375    #[account(
376        init,
377        payer = payer,
378        space = 8 + Counter::INIT_SPACE,
379        seeds = [COUNTER_SEED],
380        bump
381    )]
382    pub counter: Account<'info, Counter>,
383    pub system_program: Program<'info, System>,
384}
385
386pub fn handle_initialize(ctx: Context<Initialize>) -> Result<()> {
387    ctx.accounts.counter.count = 0;
388    ctx.accounts.counter.authority = ctx.accounts.payer.key();
389
390    let cpi_accounts = anchor_lang::system_program::Transfer {
391        from: ctx.accounts.payer.to_account_info(),
392        to: ctx.accounts.counter.to_account_info(),
393    };
394    let cpi_ctx = CpiContext::new(anchor_lang::system_program::ID, cpi_accounts);
395    anchor_lang::system_program::transfer(cpi_ctx, HELLO_WORLD_LAMPORTS)?;
396
397    msg!("Hello, world! Counter initialized");
398    Ok(())
399}
400"#
401            .into(),
402        ),
403        (
404            src_path.join("instructions").join("increment.rs"),
405            r#"use anchor_lang::prelude::*;
406
407use crate::{constants::*, error::ErrorCode, state::Counter};
408
409#[derive(Accounts)]
410pub struct Increment<'info> {
411    #[account(mut, seeds = [COUNTER_SEED], bump)]
412    pub counter: Account<'info, Counter>,
413    pub authority: Signer<'info>,
414}
415
416pub fn handle_increment(ctx: Context<Increment>) -> Result<()> {
417    require_keys_eq!(
418        ctx.accounts.counter.authority,
419        ctx.accounts.authority.key(),
420        ErrorCode::Unauthorized,
421    );
422    require!(
423        ctx.accounts.counter.count < MAX_COUNT,
424        ErrorCode::CounterOverflow,
425    );
426
427    ctx.accounts.counter.count += 1;
428    msg!("Hello, world! Counter is now {}", ctx.accounts.counter.count);
429    Ok(())
430}
431"#
432            .into(),
433        ),
434        (
435            src_path.join("state.rs"),
436            r#"use anchor_lang::prelude::*;
437
438#[account]
439#[derive(InitSpace)]
440pub struct Counter {
441    pub count: u64,
442    pub authority: Pubkey,
443}
444"#
445            .into(),
446        ),
447    ]
448}
449
450fn create_program_template_multiple_v2(
451    name: &str,
452    program_path: &Path,
453    target_path: &Path,
454) -> Files {
455    let src_path = program_path.join("src");
456    vec![
457        (
458            src_path.join("lib.rs"),
459            format!(
460                r#"pub mod constants;
461pub mod error;
462pub mod instructions;
463pub mod state;
464
465use anchor_lang_v2::prelude::*;
466
467pub use instructions::*;
468
469declare_id!("{}");
470
471#[program]
472pub mod {} {{
473    use super::*;
474
475    pub fn initialize(ctx: &mut Context<Initialize>) -> Result<()> {{
476        initialize::handler(ctx)
477    }}
478}}
479"#,
480                get_or_create_program_id(name, target_path),
481                name.to_snake_case(),
482            ),
483        ),
484        (
485            src_path.join("constants.rs"),
486            r#"use anchor_lang_v2::prelude::*;
487
488#[constant]
489pub const SEED: &str = "anchor";
490"#
491            .into(),
492        ),
493        (
494            src_path.join("error.rs"),
495            r#"use anchor_lang_v2::prelude::*;
496
497#[error_code]
498pub enum ErrorCode {
499    #[msg("Custom error message")]
500    CustomError,
501}
502"#
503            .into(),
504        ),
505        (
506            src_path.join("instructions.rs"),
507            r#"pub mod initialize;
508
509pub use initialize::*;
510"#
511            .into(),
512        ),
513        (
514            src_path.join("instructions").join("initialize.rs"),
515            r#"use anchor_lang_v2::prelude::*;
516
517use crate::state::Counter;
518
519#[derive(Accounts)]
520pub struct Initialize {
521    #[account(mut)]
522    pub payer: Signer,
523    #[account(init, payer = payer)]
524    pub counter: Account<Counter>,
525    pub system_program: Program<System>,
526}
527
528pub fn handler(ctx: &mut Context<Initialize>) -> Result<()> {
529    ctx.accounts.counter.count = 0;
530    ctx.accounts.counter.authority = *ctx.accounts.payer.address();
531    msg!("Counter initialized");
532    Ok(())
533}
534"#
535            .into(),
536        ),
537        (
538            src_path.join("state.rs"),
539            r#"use anchor_lang_v2::prelude::*;
540
541#[account]
542pub struct Counter {
543    pub count: u64,
544    pub authority: Address,
545}
546"#
547            .into(),
548        ),
549    ]
550}
551
552fn workspace_manifest() -> String {
553    format!(
554        r#"[workspace]
555members = [
556    "programs/*"
557]
558resolver = "2"
559
560[workspace.package]
561edition = "2021"
562rust-version = "{ANCHOR_MSRV}"
563
564[profile.release]
565overflow-checks = true
566lto = "fat"
567codegen-units = 1
568[profile.release.build-override]
569opt-level = 3
570incremental = false
571codegen-units = 1
572"#
573    )
574}
575
576fn cargo_toml(
577    name: &str,
578    test_template: Option<&TestTemplate>,
579    anchor_version: AnchorVersion,
580) -> String {
581    match anchor_version {
582        AnchorVersion::V1 => cargo_toml_v1(name, test_template),
583        AnchorVersion::V2 => cargo_toml_v2(name, test_template),
584    }
585}
586
587fn cargo_toml_v1(name: &str, test_template: Option<&TestTemplate>) -> String {
588    let template_features = match test_template {
589        Some(TestTemplate::Mollusk) => r#"test-sbf = []"#,
590        _ => "",
591    };
592    let dev_dependencies = match test_template {
593        Some(TestTemplate::Mollusk) => {
594            r#"
595[dev-dependencies]
596mollusk-svm = "~0.10"
597solana-account = "3"
598solana-pubkey = "3"
599solana-sdk-ids = "3"
600"#
601        }
602        Some(TestTemplate::Litesvm) => {
603            r#"
604[dev-dependencies]
605litesvm = "0.10.0"
606solana-message = "3.0.1"
607solana-transaction = "3.0.2"
608solana-signer = "3.0.0"
609solana-keypair = "3.0.1"
610"#
611        }
612        _ => "",
613    };
614
615    format!(
616        r#"[package]
617name = "{0}"
618version = "0.1.0"
619description = "Created with Anchor"
620edition.workspace = true
621rust-version.workspace = true
622
623[lib]
624crate-type = ["cdylib", "lib"]
625name = "{1}"
626
627[features]
628default = []
629cpi = ["no-entrypoint"]
630no-entrypoint = []
631no-log-ix-name = []
632idl-build = ["anchor-lang/idl-build"]
633anchor-debug = []
634custom-heap = []
635custom-panic = []
636{2}
637
638[dependencies]
639anchor-lang = "{3}"
640{4}
641
642[lints.rust]
643unexpected_cfgs = {{ level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }}
644"#,
645        name,
646        name.to_snake_case(),
647        template_features,
648        VERSION,
649        dev_dependencies,
650    )
651}
652
653fn cargo_toml_v2(name: &str, test_template: Option<&TestTemplate>) -> String {
654    // Template-specific features carried into the emitted `[features]` block:
655    //   - Mollusk: `test-sbf` for host-mode integration tests.
656    //   - LiteSVM: `profile` forwards to `anchor-v2-testing/profile`, the
657    //     register-tracing hook that `anchor test --profile` activates.
658    let template_features = match test_template {
659        Some(TestTemplate::Mollusk) => r#"test-sbf = []"#,
660        Some(TestTemplate::Litesvm) => r#"profile = ["anchor-v2-testing/profile"]"#,
661        _ => "",
662    };
663    let dev_dependencies = match test_template {
664        Some(TestTemplate::Mollusk) => {
665            r#"
666[dev-dependencies]
667mollusk-svm = "~0.10"
668solana-account = "3"
669solana-pubkey = "4"
670solana-sdk-ids = "3"
671bytemuck = "1"
672"#
673        }
674        Some(TestTemplate::Litesvm) => {
675            r#"
676[dev-dependencies]
677anchor-v2-testing = { git = "https://github.com/solana-foundation/anchor.git", branch = "anchor-next" }
678"#
679        }
680        _ => "",
681    };
682
683    format!(
684        r#"[package]
685name = "{0}"
686version = "0.1.0"
687description = "Created with Anchor"
688edition.workspace = true
689rust-version.workspace = true
690
691[lib]
692crate-type = ["cdylib", "lib"]
693name = "{1}"
694
695[features]
696default = []
697cpi = ["no-entrypoint"]
698no-entrypoint = []
699no-log-ix-name = []
700idl-build = []
701{2}
702
703[dependencies]
704# Once anchor-lang-v2 is published to crates.io, swap to: anchor-lang-v2 = "{3}"
705anchor-lang-v2 = {{ git = "https://github.com/solana-foundation/anchor.git", branch = "anchor-next" }}
706solana-program-log = {{ version = "1.1", features = ["macro"] }}
707wincode = {{ version = "0.5", features = ["derive"] }}
708{4}
709
710[lints.rust]
711unexpected_cfgs = {{ level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }}
712"#,
713        name,
714        name.to_snake_case(),
715        template_features,
716        ANCHOR_V2_TEMPLATE_VERSION,
717        dev_dependencies,
718    )
719}
720
721/// Read the program keypair file or create a new one if it doesn't exist.
722pub fn get_or_create_program_id(name: &str, target_path: impl AsRef<Path>) -> Pubkey {
723    let keypair_path = target_path
724        .as_ref()
725        .join("deploy")
726        .join(format!("{}-keypair.json", name.to_snake_case()));
727
728    read_keypair_file(&keypair_path)
729        .unwrap_or_else(|_| {
730            let keypair = Keypair::new();
731            write_keypair_file(&keypair, keypair_path).expect("Unable to create program keypair");
732            keypair
733        })
734        .pubkey()
735}
736
737pub fn deploy_js_script_host(cluster_url: &str, script_path: &str) -> String {
738    format!(
739        r#"
740const anchor = require('@anchor-lang/core');
741
742// Deploy script defined by the user.
743const userScript = require("{script_path}");
744
745async function main() {{
746    const connection = new anchor.web3.Connection(
747      "{cluster_url}",
748      anchor.AnchorProvider.defaultOptions().commitment
749    );
750    const wallet = anchor.Wallet.local();
751    const provider = new anchor.AnchorProvider(connection, wallet);
752
753    // Run the user's deploy script.
754    userScript(provider);
755}}
756main();
757"#,
758    )
759}
760
761pub fn deploy_ts_script_host(cluster_url: &str, script_path: &str) -> String {
762    format!(
763        r#"import * as anchor from '@anchor-lang/core';
764
765// Deploy script defined by the user.
766const userScript = require("{script_path}");
767
768async function main() {{
769    const connection = new anchor.web3.Connection(
770      "{cluster_url}",
771      anchor.AnchorProvider.defaultOptions().commitment
772    );
773    const wallet = anchor.Wallet.local();
774    const provider = new anchor.AnchorProvider(connection, wallet);
775
776    // Run the user's deploy script.
777    userScript(provider);
778}}
779main();
780"#,
781    )
782}
783
784pub fn deploy_script() -> &'static str {
785    r#"// Migrations are an early feature. Currently, they're nothing more than this
786// single deploy script that's invoked from the CLI, injecting a provider
787// configured from the workspace's Anchor.toml.
788
789const anchor = require("@anchor-lang/core");
790
791module.exports = async function (provider) {
792  // Configure client to use the provider.
793  anchor.setProvider(provider);
794
795  // Add your deploy script here.
796};
797"#
798}
799
800pub fn ts_deploy_script() -> &'static str {
801    r#"// Migrations are an early feature. Currently, they're nothing more than this
802// single deploy script that's invoked from the CLI, injecting a provider
803// configured from the workspace's Anchor.toml.
804
805import * as anchor from "@anchor-lang/core";
806
807module.exports = async function (provider: anchor.AnchorProvider) {
808  // Configure client to use the provider.
809  anchor.setProvider(provider);
810
811  // Add your deploy script here.
812};
813"#
814}
815
816pub fn mocha(name: &str, anchor_version: AnchorVersion) -> String {
817    match anchor_version {
818        AnchorVersion::V1 => mocha_v1(name),
819        AnchorVersion::V2 => mocha_v2(name),
820    }
821}
822
823fn mocha_v1(name: &str) -> String {
824    format!(
825        r#"const anchor = require("@anchor-lang/core");
826
827describe("{}", () => {{
828  // Configure the client to use the local cluster.
829  anchor.setProvider(anchor.AnchorProvider.env());
830
831  it("Initializes and increments a counter", async () => {{
832    const program = anchor.workspace.{};
833    const [counter] = anchor.web3.PublicKey.findProgramAddressSync(
834      [Buffer.from("counter")],
835      program.programId
836    );
837
838    const initializeTx = await program.methods
839      .initialize()
840      .accountsPartial({{ counter }})
841      .rpc();
842    console.log("Initialize transaction signature", initializeTx);
843
844    const incrementTx = await program.methods
845      .increment()
846      .accountsPartial({{ counter }})
847      .rpc();
848    console.log("Increment transaction signature", incrementTx);
849  }});
850}});
851"#,
852        name,
853        name.to_lower_camel_case(),
854    )
855}
856
857fn mocha_v2(name: &str) -> String {
858    format!(
859        r#"const anchor = require("@anchor-lang/core");
860
861describe("{}", () => {{
862  // Configure the client to use the local cluster.
863  anchor.setProvider(anchor.AnchorProvider.env());
864
865  it("Is initialized!", async () => {{
866    // Add your test here.
867    const program = anchor.workspace.{};
868    const counter = anchor.web3.Keypair.generate();
869    const tx = await program.methods
870      .initialize()
871      .accounts({{ counter: counter.publicKey }})
872      .signers([counter])
873      .rpc();
874    console.log("Your transaction signature", tx);
875  }});
876}});
877"#,
878        name,
879        name.to_lower_camel_case(),
880    )
881}
882
883pub fn js_jest(name: &str, anchor_version: AnchorVersion) -> String {
884    match anchor_version {
885        AnchorVersion::V1 => js_jest_v1(name),
886        AnchorVersion::V2 => js_jest_v2(name),
887    }
888}
889
890fn js_jest_v1(name: &str) -> String {
891    format!(
892        r#"const anchor = require("@anchor-lang/core");
893
894describe("{}", () => {{
895  // Configure the client to use the local cluster.
896  anchor.setProvider(anchor.AnchorProvider.env());
897
898  it("Initializes and increments a counter", async () => {{
899    const program = anchor.workspace.{};
900    const [counter] = anchor.web3.PublicKey.findProgramAddressSync(
901      [Buffer.from("counter")],
902      program.programId
903    );
904
905    const initializeTx = await program.methods
906      .initialize()
907      .accountsPartial({{ counter }})
908      .rpc();
909    console.log("Initialize transaction signature", initializeTx);
910
911    const incrementTx = await program.methods
912      .increment()
913      .accountsPartial({{ counter }})
914      .rpc();
915    console.log("Increment transaction signature", incrementTx);
916  }});
917}});
918"#,
919        name,
920        name.to_lower_camel_case(),
921    )
922}
923
924fn js_jest_v2(name: &str) -> String {
925    format!(
926        r#"const anchor = require("@anchor-lang/core");
927
928describe("{}", () => {{
929  // Configure the client to use the local cluster.
930  anchor.setProvider(anchor.AnchorProvider.env());
931
932  it("Is initialized!", async () => {{
933    // Add your test here.
934    const program = anchor.workspace.{};
935    const counter = anchor.web3.Keypair.generate();
936    const tx = await program.methods
937      .initialize()
938      .accounts({{ counter: counter.publicKey }})
939      .signers([counter])
940      .rpc();
941    console.log("Your transaction signature", tx);
942  }});
943}});
944"#,
945        name,
946        name.to_lower_camel_case(),
947    )
948}
949
950pub fn package_json(jest: bool, license: String, anchor_version: AnchorVersion) -> String {
951    match anchor_version {
952        AnchorVersion::V1 => package_json_v1(jest, license),
953        AnchorVersion::V2 => package_json_v2(jest, license),
954    }
955}
956
957fn package_json_v1(jest: bool, license: String) -> String {
958    if jest {
959        format!(
960            r#"{{
961  "license": "{license}",
962  "scripts": {{
963    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
964    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
965  }},
966  "dependencies": {{
967    "@anchor-lang/core": "^{VERSION}"
968  }},
969  "devDependencies": {{
970    "jest": "^29.0.3",
971    "prettier": "^2.6.2"
972  }},
973  "overrides": {{
974    "uuid": "^9.0.1"
975  }},
976  "resolutions": {{
977    "uuid": "^9.0.1"
978  }},
979  "pnpm": {{
980    "overrides": {{
981      "uuid": "^9.0.1"
982    }}
983  }}
984}}
985    "#
986        )
987    } else {
988        format!(
989            r#"{{
990  "license": "{license}",
991  "scripts": {{
992    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
993    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
994  }},
995  "dependencies": {{
996    "@anchor-lang/core": "^{VERSION}"
997  }},
998  "devDependencies": {{
999    "chai": "^4.3.4",
1000    "mocha": "^9.0.3",
1001    "prettier": "^2.6.2"
1002  }}
1003}}
1004"#
1005        )
1006    }
1007}
1008
1009// Pinned at `^1.0.0` because 2.0.0 isn't on npm yet.
1010fn package_json_v2(jest: bool, license: String) -> String {
1011    if jest {
1012        format!(
1013            r#"{{
1014  "license": "{license}",
1015  "scripts": {{
1016    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
1017    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
1018  }},
1019  "dependencies": {{
1020    "@anchor-lang/core": "^1.0.0"
1021  }},
1022  "devDependencies": {{
1023    "jest": "^30.3.0",
1024    "prettier": "^3.8.3"
1025  }},
1026  "overrides": {{
1027    "uuid": "^9.0.1"
1028  }},
1029  "resolutions": {{
1030    "uuid": "^9.0.1"
1031  }},
1032  "pnpm": {{
1033    "overrides": {{
1034      "uuid": "^9.0.1"
1035    }}
1036  }}
1037}}
1038    "#
1039        )
1040    } else {
1041        format!(
1042            r#"{{
1043  "license": "{license}",
1044  "scripts": {{
1045    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
1046    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
1047  }},
1048  "dependencies": {{
1049    "@anchor-lang/core": "^1.0.0"
1050  }},
1051  "devDependencies": {{
1052    "chai": "^4.5.0",
1053    "mocha": "^11.7.5",
1054    "prettier": "^3.8.3"
1055  }}
1056}}
1057"#
1058        )
1059    }
1060}
1061
1062pub fn ts_package_json(jest: bool, license: String, anchor_version: AnchorVersion) -> String {
1063    match anchor_version {
1064        AnchorVersion::V1 => ts_package_json_v1(jest, license),
1065        AnchorVersion::V2 => ts_package_json_v2(jest, license),
1066    }
1067}
1068
1069fn ts_package_json_v1(jest: bool, license: String) -> String {
1070    if jest {
1071        format!(
1072            r#"{{
1073  "license": "{license}",
1074  "scripts": {{
1075    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
1076    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
1077  }},
1078  "dependencies": {{
1079    "@anchor-lang/core": "^{VERSION}"
1080  }},
1081  "devDependencies": {{
1082    "@types/bn.js": "^5.1.0",
1083    "@types/jest": "^29.0.3",
1084    "jest": "^29.0.3",
1085    "prettier": "^2.6.2",
1086    "ts-jest": "^29.0.2",
1087    "typescript": "^5.7.3"
1088  }},
1089  "overrides": {{
1090    "uuid": "^9.0.1"
1091  }},
1092  "resolutions": {{
1093    "uuid": "^9.0.1"
1094  }},
1095  "pnpm": {{
1096    "overrides": {{
1097      "uuid": "^9.0.1"
1098    }}
1099  }}
1100}}
1101"#
1102        )
1103    } else {
1104        format!(
1105            r#"{{
1106  "license": "{license}",
1107  "scripts": {{
1108    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
1109    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
1110  }},
1111  "dependencies": {{
1112    "@anchor-lang/core": "^{VERSION}"
1113  }},
1114  "devDependencies": {{
1115    "chai": "^4.3.4",
1116    "mocha": "^9.0.3",
1117    "ts-mocha": "^10.0.0",
1118    "@types/bn.js": "^5.1.0",
1119    "@types/chai": "^4.3.0",
1120    "@types/mocha": "^9.0.0",
1121    "typescript": "^5.7.3",
1122    "prettier": "^2.6.2"
1123  }}
1124}}
1125"#
1126        )
1127    }
1128}
1129
1130// Pinned at `^1.0.0` because 2.0.0 isn't on npm yet.
1131fn ts_package_json_v2(jest: bool, license: String) -> String {
1132    if jest {
1133        format!(
1134            r#"{{
1135  "license": "{license}",
1136  "scripts": {{
1137    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
1138    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
1139  }},
1140  "dependencies": {{
1141    "@anchor-lang/core": "^1.0.0"
1142  }},
1143  "devDependencies": {{
1144    "@types/bn.js": "^5.2.0",
1145    "@types/jest": "^30.0.0",
1146    "jest": "^30.3.0",
1147    "prettier": "^3.8.3",
1148    "ts-jest": "^29.4.9",
1149    "typescript": "^5.9.3"
1150  }},
1151  "overrides": {{
1152    "uuid": "^9.0.1"
1153  }},
1154  "resolutions": {{
1155    "uuid": "^9.0.1"
1156  }},
1157  "pnpm": {{
1158    "overrides": {{
1159      "uuid": "^9.0.1"
1160    }}
1161  }}
1162}}
1163"#
1164        )
1165    } else {
1166        format!(
1167            r#"{{
1168  "license": "{license}",
1169  "scripts": {{
1170    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
1171    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
1172  }},
1173  "dependencies": {{
1174    "@anchor-lang/core": "^1.0.0"
1175  }},
1176  "devDependencies": {{
1177    "chai": "^4.5.0",
1178    "mocha": "^11.7.5",
1179    "ts-mocha": "^11.1.0",
1180    "ts-node": "^10.9.2",
1181    "@types/bn.js": "^5.2.0",
1182    "@types/chai": "^4.3.0",
1183    "@types/mocha": "^10.0.10",
1184    "@types/node": "^25.6.0",
1185    "typescript": "^5.9.3",
1186    "prettier": "^3.8.3"
1187  }}
1188}}
1189"#
1190        )
1191    }
1192}
1193
1194pub fn ts_mocha(name: &str, anchor_version: AnchorVersion) -> String {
1195    match anchor_version {
1196        AnchorVersion::V1 => ts_mocha_v1(name),
1197        AnchorVersion::V2 => ts_mocha_v2(name),
1198    }
1199}
1200
1201fn ts_mocha_v1(name: &str) -> String {
1202    format!(
1203        r#"import * as anchor from "@anchor-lang/core";
1204import {{ Program }} from "@anchor-lang/core";
1205import {{ {} }} from "../target/types/{}";
1206
1207describe("{}", () => {{
1208  // Configure the client to use the local cluster.
1209  anchor.setProvider(anchor.AnchorProvider.env());
1210
1211  const program = anchor.workspace.{} as Program<{}>;
1212
1213  it("Initializes and increments a counter", async () => {{
1214    const [counter] = anchor.web3.PublicKey.findProgramAddressSync(
1215      [Buffer.from("counter")],
1216      program.programId
1217    );
1218
1219    const initializeTx = await program.methods
1220      .initialize()
1221      .accountsPartial({{ counter }})
1222      .rpc();
1223    console.log("Initialize transaction signature", initializeTx);
1224
1225    const incrementTx = await program.methods
1226      .increment()
1227      .accountsPartial({{ counter }})
1228      .rpc();
1229    console.log("Increment transaction signature", incrementTx);
1230  }});
1231}});
1232"#,
1233        name.to_pascal_case(),
1234        name.to_snake_case(),
1235        name,
1236        name.to_lower_camel_case(),
1237        name.to_pascal_case(),
1238    )
1239}
1240
1241fn ts_mocha_v2(name: &str) -> String {
1242    format!(
1243        r#"import * as anchor from "@anchor-lang/core";
1244import {{ Program }} from "@anchor-lang/core";
1245import {{ {} }} from "../target/types/{}";
1246
1247describe("{}", () => {{
1248  // Configure the client to use the local cluster.
1249  anchor.setProvider(anchor.AnchorProvider.env());
1250
1251  const program = anchor.workspace.{} as Program<{}>;
1252
1253  it("Is initialized!", async () => {{
1254    // Add your test here.
1255    const counter = anchor.web3.Keypair.generate();
1256    const tx = await program.methods
1257      .initialize()
1258      .accounts({{ counter: counter.publicKey }})
1259      .signers([counter])
1260      .rpc();
1261    console.log("Your transaction signature", tx);
1262  }});
1263}});
1264"#,
1265        name.to_pascal_case(),
1266        name.to_snake_case(),
1267        name,
1268        name.to_lower_camel_case(),
1269        name.to_pascal_case(),
1270    )
1271}
1272
1273pub fn ts_jest(name: &str, anchor_version: AnchorVersion) -> String {
1274    match anchor_version {
1275        AnchorVersion::V1 => ts_jest_v1(name),
1276        AnchorVersion::V2 => ts_jest_v2(name),
1277    }
1278}
1279
1280fn ts_jest_v1(name: &str) -> String {
1281    format!(
1282        r#"import * as anchor from "@anchor-lang/core";
1283import {{ Program }} from "@anchor-lang/core";
1284import {{ {} }} from "../target/types/{}";
1285
1286describe("{}", () => {{
1287  // Configure the client to use the local cluster.
1288  anchor.setProvider(anchor.AnchorProvider.env());
1289
1290  const program = anchor.workspace.{} as Program<{}>;
1291
1292  it("Initializes and increments a counter", async () => {{
1293    const [counter] = anchor.web3.PublicKey.findProgramAddressSync(
1294      [Buffer.from("counter")],
1295      program.programId
1296    );
1297
1298    const initializeTx = await program.methods
1299      .initialize()
1300      .accountsPartial({{ counter }})
1301      .rpc();
1302    console.log("Initialize transaction signature", initializeTx);
1303
1304    const incrementTx = await program.methods
1305      .increment()
1306      .accountsPartial({{ counter }})
1307      .rpc();
1308    console.log("Increment transaction signature", incrementTx);
1309  }});
1310}});
1311"#,
1312        name.to_pascal_case(),
1313        name.to_snake_case(),
1314        name,
1315        name.to_lower_camel_case(),
1316        name.to_pascal_case(),
1317    )
1318}
1319
1320fn ts_jest_v2(name: &str) -> String {
1321    format!(
1322        r#"import * as anchor from "@anchor-lang/core";
1323import {{ Program }} from "@anchor-lang/core";
1324import {{ {} }} from "../target/types/{}";
1325
1326describe("{}", () => {{
1327  // Configure the client to use the local cluster.
1328  anchor.setProvider(anchor.AnchorProvider.env());
1329
1330  const program = anchor.workspace.{} as Program<{}>;
1331
1332  it("Is initialized!", async () => {{
1333    // Add your test here.
1334    const counter = anchor.web3.Keypair.generate();
1335    const tx = await program.methods
1336      .initialize()
1337      .accounts({{ counter: counter.publicKey }})
1338      .signers([counter])
1339      .rpc();
1340    console.log("Your transaction signature", tx);
1341  }});
1342}});
1343"#,
1344        name.to_pascal_case(),
1345        name.to_snake_case(),
1346        name,
1347        name.to_lower_camel_case(),
1348        name.to_pascal_case(),
1349    )
1350}
1351
1352pub fn ts_config(jest: bool) -> &'static str {
1353    if jest {
1354        r#"{
1355  "compilerOptions": {
1356    "types": ["jest"],
1357    "typeRoots": ["./node_modules/@types"],
1358    "lib": ["es2015"],
1359    "module": "commonjs",
1360    "target": "es6",
1361    "esModuleInterop": true
1362  }
1363}
1364"#
1365    } else {
1366        r#"{
1367  "compilerOptions": {
1368    "types": ["mocha", "chai"],
1369    "typeRoots": ["./node_modules/@types"],
1370    "lib": ["es2015"],
1371    "module": "commonjs",
1372    "target": "es6",
1373    "esModuleInterop": true
1374  }
1375}
1376"#
1377    }
1378}
1379
1380pub fn git_ignore() -> &'static str {
1381    r#".anchor
1382.DS_Store
1383target
1384**/*.rs.bk
1385node_modules
1386test-ledger
1387.yarn
1388.surfpool
1389"#
1390}
1391
1392pub fn prettier_ignore() -> &'static str {
1393    r#".anchor
1394.DS_Store
1395target
1396node_modules
1397dist
1398build
1399test-ledger
1400"#
1401}
1402
1403pub fn node_shell(
1404    cluster_url: &str,
1405    wallet_path: &str,
1406    programs: Vec<ProgramWorkspace>,
1407) -> Result<String> {
1408    let mut eval_string = format!(
1409        r#"
1410const anchor = require('@anchor-lang/core');
1411const web3 = anchor.web3;
1412const PublicKey = anchor.web3.PublicKey;
1413const Keypair = anchor.web3.Keypair;
1414
1415const __wallet = new anchor.Wallet(
1416  Keypair.fromSecretKey(
1417    Buffer.from(
1418      JSON.parse(
1419        require('fs').readFileSync(
1420          "{wallet_path}",
1421          {{
1422            encoding: "utf-8",
1423          }},
1424        ),
1425      ),
1426    ),
1427  ),
1428);
1429const __connection = new web3.Connection("{cluster_url}", "processed");
1430const provider = new anchor.AnchorProvider(__connection, __wallet, {{
1431  commitment: "processed",
1432  preflightcommitment: "processed",
1433}});
1434anchor.setProvider(provider);
1435"#,
1436    );
1437
1438    for program in programs {
1439        write!(
1440            &mut eval_string,
1441            r#"
1442anchor.workspace.{} = new anchor.Program({}, provider);
1443"#,
1444            program.name.to_lower_camel_case(),
1445            serde_json::to_string(&program.idl)?,
1446        )?;
1447    }
1448
1449    Ok(eval_string)
1450}
1451
1452/// Test initialization template
1453#[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum, AbsolutePath)]
1454pub enum TestTemplate {
1455    /// Generate template for Mocha unit-test
1456    Mocha,
1457    /// Generate template for Jest unit-test
1458    Jest,
1459    /// Generate template for Rust unit-test
1460    Rust,
1461    /// Generate template for Mollusk Rust unit-test
1462    Mollusk,
1463    /// Generate template for LiteSVM rust unit-test
1464    #[default]
1465    Litesvm,
1466}
1467
1468impl TestTemplate {
1469    pub fn uses_node(&self) -> bool {
1470        matches!(self, Self::Mocha | Self::Jest)
1471    }
1472
1473    pub fn get_test_script(&self, js: bool, pkg_manager: Option<&PackageManager>) -> String {
1474        let pkg_manager_exec_cmd = match pkg_manager {
1475            Some(PackageManager::Yarn) => "yarn run",
1476            Some(PackageManager::NPM) => "npx",
1477            Some(PackageManager::PNPM) => "pnpm exec",
1478            Some(PackageManager::Bun) => "bunx",
1479            None => "",
1480        };
1481
1482        match &self {
1483            Self::Mocha => {
1484                if js {
1485                    format!("{pkg_manager_exec_cmd} mocha -t 1000000 tests/")
1486                } else {
1487                    format!(
1488                        r#"{pkg_manager_exec_cmd} ts-mocha -p ./tsconfig.json -t 1000000 "tests/**/*.ts""#
1489                    )
1490                }
1491            }
1492            Self::Jest => {
1493                if js {
1494                    format!("{pkg_manager_exec_cmd} jest")
1495                } else {
1496                    format!("{pkg_manager_exec_cmd} jest --preset ts-jest")
1497                }
1498            }
1499            Self::Rust | Self::Litesvm => "cargo test".to_owned(),
1500            Self::Mollusk => "cargo test-sbf".to_owned(),
1501        }
1502    }
1503
1504    pub fn create_test_files(
1505        &self,
1506        project_name: &str,
1507        js: bool,
1508        program_id: &str,
1509        anchor_version: AnchorVersion,
1510    ) -> Result<()> {
1511        match self {
1512            Self::Mocha => {
1513                // Build the test suite.
1514                fs::create_dir_all("tests")?;
1515
1516                if js {
1517                    let mut test = File::create(format!("tests/{}.js", &project_name))?;
1518                    test.write_all(mocha(project_name, anchor_version).as_bytes())?;
1519                } else {
1520                    let mut mocha = File::create(format!("tests/{}.ts", &project_name))?;
1521                    mocha.write_all(ts_mocha(project_name, anchor_version).as_bytes())?;
1522                }
1523            }
1524            Self::Jest => {
1525                // Build the test suite.
1526                fs::create_dir_all("tests")?;
1527
1528                if js {
1529                    let mut test = File::create(format!("tests/{}.test.js", &project_name))?;
1530                    test.write_all(js_jest(project_name, anchor_version).as_bytes())?;
1531                } else {
1532                    let mut test = File::create(format!("tests/{}.test.ts", &project_name))?;
1533                    test.write_all(ts_jest(project_name, anchor_version).as_bytes())?;
1534                }
1535            }
1536            Self::Rust => {
1537                // Do not initialize git repo
1538                let exit = std::process::Command::new("cargo")
1539                    .arg("new")
1540                    .arg("--vcs")
1541                    .arg("none")
1542                    .arg("--lib")
1543                    .arg("tests")
1544                    .stderr(Stdio::inherit())
1545                    .output()
1546                    .map_err(|e| anyhow::format_err!("{}", e))?;
1547                if !exit.status.success() {
1548                    eprintln!("'cargo new --lib tests' failed");
1549                    std::process::exit(exit.status.code().unwrap_or(1));
1550                }
1551
1552                let mut files = Vec::new();
1553                let tests_path = Path::new("tests");
1554                files.extend(vec![(
1555                    tests_path.join("Cargo.toml"),
1556                    tests_cargo_toml(project_name, anchor_version),
1557                )]);
1558                files.extend(create_program_template_rust_test(
1559                    project_name,
1560                    tests_path,
1561                    program_id,
1562                    anchor_version,
1563                ));
1564                override_or_create_files(&files)?;
1565            }
1566            Self::Mollusk => {
1567                // Build the test suite.
1568                let tests_path_str = format!("programs/{}/tests", &project_name);
1569                let tests_path = Path::new(&tests_path_str);
1570                fs::create_dir_all(tests_path)?;
1571
1572                let mut files = Vec::new();
1573                files.extend(create_program_template_mollusk_test(
1574                    project_name,
1575                    tests_path,
1576                    anchor_version,
1577                ));
1578                override_or_create_files(&files)?;
1579            }
1580
1581            Self::Litesvm => {
1582                let tests_path_str = format!("programs/{}/tests", &project_name);
1583                let tests_path = Path::new(&tests_path_str);
1584                fs::create_dir_all(tests_path)?;
1585                let mut files = Vec::new();
1586                files.extend(create_program_template_litesvm_test(
1587                    project_name,
1588                    tests_path,
1589                    anchor_version,
1590                ));
1591                override_or_create_files(&files)?;
1592            }
1593        }
1594
1595        Ok(())
1596    }
1597}
1598
1599pub fn tests_cargo_toml(name: &str, anchor_version: AnchorVersion) -> String {
1600    match anchor_version {
1601        AnchorVersion::V1 => tests_cargo_toml_v1(name),
1602        AnchorVersion::V2 => tests_cargo_toml_v2(name),
1603    }
1604}
1605
1606fn tests_cargo_toml_v1(name: &str) -> String {
1607    format!(
1608        r#"[package]
1609name = "tests"
1610version = "0.1.0"
1611description = "Created with Anchor"
1612edition = "2021"
1613rust-version = "{ANCHOR_MSRV}"
1614
1615[dependencies]
1616anchor-client = "{VERSION}"
1617{name} = {{ version = "0.1.0", path = "../programs/{name}" }}
1618solana-keypair = "3.0.0"
1619solana-pubkey = "3.0.0"
1620solana-sdk-ids = "3"
1621solana-signer = "3"
1622"#
1623    )
1624}
1625
1626fn tests_cargo_toml_v2(name: &str) -> String {
1627    format!(
1628        r#"[package]
1629name = "tests"
1630version = "0.1.0"
1631description = "Created with Anchor"
1632edition = "2021"
1633rust-version = "{ANCHOR_MSRV}"
1634
1635[dependencies]
1636# Once anchor-client v2 is published to crates.io, swap to: anchor-client = "{ANCHOR_V2_TEMPLATE_VERSION}"
1637anchor-client = {{ git = "https://github.com/solana-foundation/anchor.git", branch = "anchor-next" }}
1638{name} = {{ version = "0.1.0", path = "../programs/{name}" }}
1639solana-keypair = "3.0.0"
1640solana-pubkey = "3.0.0"
1641solana-sdk-ids = "3"
1642solana-signer = "3"
1643"#
1644    )
1645}
1646
1647/// Generate template for Rust unit-test
1648fn create_program_template_rust_test(
1649    name: &str,
1650    tests_path: &Path,
1651    program_id: &str,
1652    anchor_version: AnchorVersion,
1653) -> Files {
1654    match anchor_version {
1655        AnchorVersion::V1 => create_program_template_rust_test_v1(name, tests_path, program_id),
1656        AnchorVersion::V2 => create_program_template_rust_test_v2(name, tests_path, program_id),
1657    }
1658}
1659
1660fn create_program_template_rust_test_v1(name: &str, tests_path: &Path, program_id: &str) -> Files {
1661    let src_path = tests_path.join("src");
1662    vec![
1663        (
1664            src_path.join("lib.rs"),
1665            r#"#[cfg(test)]
1666mod test_initialize;
1667"#
1668            .into(),
1669        ),
1670        (
1671            src_path.join("test_initialize.rs"),
1672            format!(
1673                r#"use anchor_client::{{
1674    CommitmentConfig,
1675    Client, Cluster,
1676}};
1677use solana_keypair::read_keypair_file;
1678use solana_pubkey::Pubkey;
1679use solana_signer::Signer;
1680
1681#[test]
1682fn test_initialize() {{
1683    let program_id = "{0}";
1684    let anchor_wallet = std::env::var("ANCHOR_WALLET").unwrap();
1685    let payer = read_keypair_file(&anchor_wallet).unwrap();
1686
1687    let client = Client::new_with_options(Cluster::Localnet, &payer, CommitmentConfig::confirmed());
1688    let program_id = Pubkey::try_from(program_id).unwrap();
1689    let program = client.program(program_id).unwrap();
1690    let counter = Pubkey::find_program_address(
1691        &[{1}::constants::COUNTER_SEED],
1692        &program_id,
1693    )
1694    .0;
1695
1696    let initialize_tx = program
1697        .request()
1698        .accounts({1}::accounts::Initialize {{
1699            payer: payer.pubkey(),
1700            counter,
1701            system_program: solana_sdk_ids::system_program::id(),
1702        }})
1703        .args({1}::instruction::Initialize {{}})
1704        .send()
1705        .expect("");
1706
1707    println!("Initialize transaction signature {{}}", initialize_tx);
1708
1709    let increment_tx = program
1710        .request()
1711        .accounts({1}::accounts::Increment {{
1712            counter,
1713            authority: payer.pubkey(),
1714        }})
1715        .args({1}::instruction::Increment {{}})
1716        .send()
1717        .expect("");
1718
1719    println!("Increment transaction signature {{}}", increment_tx);
1720}}
1721"#,
1722                program_id,
1723                name.to_snake_case(),
1724            ),
1725        ),
1726    ]
1727}
1728
1729fn create_program_template_rust_test_v2(name: &str, tests_path: &Path, program_id: &str) -> Files {
1730    let src_path = tests_path.join("src");
1731    vec![
1732        (
1733            src_path.join("lib.rs"),
1734            r#"#[cfg(test)]
1735mod test_initialize;
1736"#
1737            .into(),
1738        ),
1739        (
1740            src_path.join("test_initialize.rs"),
1741            format!(
1742                r#"use anchor_client::{{
1743    CommitmentConfig,
1744    Client, Cluster,
1745}};
1746use solana_keypair::{{read_keypair_file, Keypair}};
1747use solana_pubkey::Pubkey;
1748use solana_signer::Signer;
1749
1750#[test]
1751fn test_initialize() {{
1752    let program_id = "{0}";
1753    let anchor_wallet = std::env::var("ANCHOR_WALLET").unwrap();
1754    let payer = read_keypair_file(&anchor_wallet).unwrap();
1755    let counter = Keypair::new();
1756
1757    let client = Client::new_with_options(Cluster::Localnet, &payer, CommitmentConfig::confirmed());
1758    let program_id = Pubkey::try_from(program_id).unwrap();
1759    let program = client.program(program_id).unwrap();
1760
1761    let tx = program
1762        .request()
1763        .accounts({1}::accounts::Initialize {{
1764            payer: payer.pubkey(),
1765            counter: counter.pubkey(),
1766            system_program: solana_sdk_ids::system_program::id(),
1767        }})
1768        .args({1}::instruction::Initialize {{}})
1769        .signer(&counter)
1770        .send()
1771        .expect("");
1772
1773    println!("Your transaction signature {{}}", tx);
1774}}
1775"#,
1776                program_id,
1777                name.to_snake_case(),
1778            ),
1779        ),
1780    ]
1781}
1782
1783/// Generate template for Mollusk Rust unit-test
1784fn create_program_template_mollusk_test(
1785    name: &str,
1786    tests_path: &Path,
1787    anchor_version: AnchorVersion,
1788) -> Files {
1789    match anchor_version {
1790        AnchorVersion::V1 => create_program_template_mollusk_test_v1(name, tests_path),
1791        AnchorVersion::V2 => create_program_template_mollusk_test_v2(name, tests_path),
1792    }
1793}
1794
1795fn create_program_template_mollusk_test_v1(name: &str, tests_path: &Path) -> Files {
1796    vec![(
1797        tests_path.join("test_initialize.rs"),
1798        format!(
1799            r#"#![cfg(feature = "test-sbf")]
1800
1801use {{
1802    anchor_lang::{{
1803        solana_program::instruction::Instruction, AccountDeserialize, InstructionData,
1804        Space, ToAccountMetas,
1805    }},
1806    mollusk_svm::{{program::keyed_account_for_system_program, result::Check, Mollusk}},
1807    solana_account::Account as SolanaAccount,
1808    solana_pubkey::Pubkey,
1809}};
1810
1811#[test]
1812fn test_initialize() {{
1813    let program_id = {0}::id();
1814    let mollusk = Mollusk::new(&program_id, "{0}");
1815    let payer = Pubkey::new_unique();
1816    let counter = Pubkey::find_program_address(
1817        &[{0}::constants::COUNTER_SEED],
1818        &program_id,
1819    )
1820    .0;
1821
1822    let instruction = Instruction::new_with_bytes(
1823        program_id,
1824        &{0}::instruction::Initialize {{}}.data(),
1825        {0}::accounts::Initialize {{
1826            payer,
1827            counter,
1828            system_program: solana_sdk_ids::system_program::id(),
1829        }}
1830        .to_account_metas(None),
1831    );
1832
1833    let accounts = vec![
1834        (
1835            payer,
1836            SolanaAccount::new(1_000_000_000, 0, &solana_sdk_ids::system_program::id()),
1837        ),
1838        (counter, SolanaAccount::default()),
1839        keyed_account_for_system_program(),
1840    ];
1841
1842    let result = mollusk.process_and_validate_instruction(
1843        &instruction,
1844        &accounts,
1845        &[Check::success()],
1846    );
1847
1848    let payer_account = result
1849        .resulting_accounts
1850        .iter()
1851        .find(|(pk, _)| *pk == payer)
1852        .map(|(_, a)| a.clone())
1853        .expect("payer account");
1854    let counter_account = result
1855        .resulting_accounts
1856        .iter()
1857        .find(|(pk, _)| *pk == counter)
1858        .map(|(_, a)| a.clone())
1859        .expect("counter account");
1860    assert_eq!(
1861        counter_account.data.len(),
1862        8 + {0}::state::Counter::INIT_SPACE
1863    );
1864    let mut data: &[u8] = &counter_account.data;
1865    let counter_state = {0}::state::Counter::try_deserialize(&mut data).unwrap();
1866    assert_eq!(counter_state.count, 0);
1867    assert_eq!(counter_state.authority, payer);
1868
1869    let instruction = Instruction::new_with_bytes(
1870        program_id,
1871        &{0}::instruction::Increment {{}}.data(),
1872        {0}::accounts::Increment {{
1873            counter,
1874            authority: payer,
1875        }}
1876        .to_account_metas(None),
1877    );
1878    let accounts = vec![(counter, counter_account), (payer, payer_account)];
1879
1880    let result = mollusk.process_and_validate_instruction(
1881        &instruction,
1882        &accounts,
1883        &[Check::success()],
1884    );
1885
1886    let counter_account = result
1887        .resulting_accounts
1888        .iter()
1889        .find(|(pk, _)| *pk == counter)
1890        .map(|(_, a)| a)
1891        .expect("counter account");
1892    let mut data: &[u8] = &counter_account.data;
1893    let counter_state = {0}::state::Counter::try_deserialize(&mut data).unwrap();
1894    assert_eq!(counter_state.count, 1);
1895    assert_eq!(counter_state.authority, payer);
1896}}
1897"#,
1898            name.to_snake_case(),
1899        ),
1900    )]
1901}
1902
1903fn create_program_template_mollusk_test_v2(name: &str, tests_path: &Path) -> Files {
1904    vec![(
1905        tests_path.join("test_initialize.rs"),
1906        format!(
1907            r#"#![cfg(feature = "test-sbf")]
1908
1909use {{
1910    anchor_lang_v2::{{
1911        accounts::Account, solana_program::instruction::Instruction, InstructionData, Space,
1912        ToAccountMetas,
1913    }},
1914    mollusk_svm::{{program::keyed_account_for_system_program, result::Check, Mollusk}},
1915    solana_account::Account as SolanaAccount,
1916    solana_pubkey::Pubkey,
1917}};
1918
1919#[test]
1920fn test_initialize() {{
1921    let program_id = {0}::id();
1922    let mollusk = Mollusk::new(&program_id, "{0}");
1923
1924    let payer = Pubkey::new_unique();
1925    let counter = Pubkey::new_unique();
1926
1927    let instruction = Instruction::new_with_bytes(
1928        program_id,
1929        &{0}::instruction::Initialize {{}}.data(),
1930        {0}::accounts::Initialize {{
1931            payer,
1932            counter,
1933            system_program: solana_sdk_ids::system_program::id(),
1934        }}
1935        .to_account_metas(None),
1936    );
1937
1938    let counter_space = <Account<{0}::state::Counter> as Space>::INIT_SPACE;
1939    let accounts = vec![
1940        (
1941            payer,
1942            SolanaAccount::new(1_000_000_000, 0, &solana_sdk_ids::system_program::id()),
1943        ),
1944        (counter, SolanaAccount::default()),
1945        keyed_account_for_system_program(),
1946    ];
1947
1948    let result = mollusk.process_and_validate_instruction(
1949        &instruction,
1950        &accounts,
1951        &[Check::success()],
1952    );
1953
1954    let counter_account = result
1955        .resulting_accounts
1956        .iter()
1957        .find(|(pk, _)| *pk == counter)
1958        .map(|(_, a)| a)
1959        .expect("counter account");
1960    assert_eq!(counter_account.data.len(), counter_space);
1961    let counter_state: &{0}::state::Counter = bytemuck::from_bytes(&counter_account.data[8..]);
1962    assert_eq!(counter_state.count, 0);
1963    assert_eq!(counter_state.authority, payer);
1964}}
1965"#,
1966            name.to_snake_case(),
1967        ),
1968    )]
1969}
1970
1971/// Generate template for LiteSVM Rust unit-test
1972fn create_program_template_litesvm_test(
1973    name: &str,
1974    tests_path: &Path,
1975    anchor_version: AnchorVersion,
1976) -> Files {
1977    match anchor_version {
1978        AnchorVersion::V1 => create_program_template_litesvm_test_v1(name, tests_path),
1979        AnchorVersion::V2 => create_program_template_litesvm_test_v2(name, tests_path),
1980    }
1981}
1982
1983fn create_program_template_litesvm_test_v1(name: &str, tests_path: &Path) -> Files {
1984    vec![(
1985        tests_path.join("test_initialize.rs"),
1986        format!(
1987            r#"
1988use {{
1989    anchor_lang::{{
1990        prelude::Pubkey,
1991        solana_program::{{instruction::Instruction, system_program}},
1992        AccountDeserialize, InstructionData, ToAccountMetas,
1993    }},
1994    litesvm::LiteSVM,
1995    solana_keypair::Keypair,
1996    solana_message::{{Message, VersionedMessage}},
1997    solana_signer::Signer,
1998    solana_transaction::versioned::VersionedTransaction,
1999}};
2000
2001#[test]
2002fn test_initialize() {{
2003    let program_id = {0}::id();
2004    let payer = Keypair::new();
2005    let counter = Pubkey::find_program_address(
2006        &[{0}::constants::COUNTER_SEED],
2007        &program_id,
2008    )
2009    .0;
2010    let mut svm = LiteSVM::new();
2011    let bytes = include_bytes!(concat!(
2012        env!("CARGO_TARGET_TMPDIR"),
2013        "/../deploy/{0}.so"
2014    ));
2015    svm.add_program(program_id, bytes).unwrap();
2016    svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
2017
2018    let instruction = Instruction::new_with_bytes(
2019        program_id,
2020        &{0}::instruction::Initialize {{}}.data(),
2021        {0}::accounts::Initialize {{
2022            payer: payer.pubkey(),
2023            counter,
2024            system_program: system_program::ID,
2025        }}
2026        .to_account_metas(None),
2027    );
2028
2029    let blockhash = svm.latest_blockhash();
2030    let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);
2031    let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();
2032
2033    let res = svm.send_transaction(tx);
2034    assert!(res.is_ok());
2035
2036    let counter_account = svm.get_account(&counter).unwrap();
2037    let mut data: &[u8] = &counter_account.data;
2038    let counter_state = {0}::state::Counter::try_deserialize(&mut data).unwrap();
2039    assert_eq!(counter_state.count, 0);
2040    assert_eq!(counter_state.authority, payer.pubkey());
2041
2042    let instruction = Instruction::new_with_bytes(
2043        program_id,
2044        &{0}::instruction::Increment {{}}.data(),
2045        {0}::accounts::Increment {{
2046            counter,
2047            authority: payer.pubkey(),
2048        }}
2049        .to_account_metas(None),
2050    );
2051
2052    let blockhash = svm.latest_blockhash();
2053    let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);
2054    let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[&payer]).unwrap();
2055
2056    let res = svm.send_transaction(tx);
2057    assert!(res.is_ok());
2058
2059    let counter_account = svm.get_account(&counter).unwrap();
2060    let mut data: &[u8] = &counter_account.data;
2061    let counter_state = {0}::state::Counter::try_deserialize(&mut data).unwrap();
2062    assert_eq!(counter_state.count, 1);
2063    assert_eq!(counter_state.authority, payer.pubkey());
2064}}
2065"#,
2066            name.to_snake_case(),
2067        ),
2068    )]
2069}
2070
2071fn create_program_template_litesvm_test_v2(name: &str, tests_path: &Path) -> Files {
2072    vec![(
2073        tests_path.join("test_initialize.rs"),
2074        format!(
2075            r#"
2076use {{
2077    anchor_lang_v2::{{
2078        accounts::Account, bytemuck, programs::System,
2079        solana_program::instruction::Instruction, Id, InstructionData, Space, ToAccountMetas,
2080    }},
2081    anchor_v2_testing::{{Keypair, Message, Signer, VersionedMessage, VersionedTransaction}},
2082}};
2083
2084#[test]
2085fn test_initialize() {{
2086    let program_id = {0}::id();
2087    let payer = Keypair::new();
2088    let counter = Keypair::new();
2089
2090    // `svm()` is `LiteSVM::new()` by default. When this crate is built
2091    // with `--features profile` (which `anchor test --profile` and
2092    // `anchor debugger` set automatically), it also installs the
2093    // register-tracing callback that writes per-test SBF traces under
2094    // `target/anchor-v2-profile/`. The cfg switch lives inside
2095    // `anchor-v2-testing` so test code stays clean either way.
2096    let mut svm = anchor_v2_testing::svm();
2097    let bytes = include_bytes!("../../../target/deploy/{0}.so");
2098    svm.add_program(program_id, bytes).unwrap();
2099    svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
2100
2101    let instruction = Instruction::new_with_bytes(
2102        program_id,
2103        &{0}::instruction::Initialize {{}}.data(),
2104        {0}::accounts::Initialize {{
2105            payer: payer.pubkey(),
2106            counter: counter.pubkey(),
2107            system_program: System::id(),
2108        }}
2109        .to_account_metas(None),
2110    );
2111
2112    let blockhash = svm.latest_blockhash();
2113    let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);
2114    let tx = VersionedTransaction::try_new(
2115        VersionedMessage::Legacy(msg),
2116        &[&payer, &counter],
2117    )
2118    .unwrap();
2119
2120    let res = svm.send_transaction(tx);
2121    assert!(res.is_ok(), "send_transaction failed: {{:?}}", res);
2122
2123    // Verify the counter account was initialized. Size comes from the same
2124    // `Space::INIT_SPACE` expression the `init` constraint allocates with,
2125    // so the assertion doesn't rot if `Counter` gains fields. The payload
2126    // tail is a `Pod` struct, so we cast directly and read fields by name
2127    // instead of hand-slicing bytes.
2128    let account = svm.get_account(&counter.pubkey()).expect("counter account");
2129    assert_eq!(account.data.len(), <Account<{0}::state::Counter> as Space>::INIT_SPACE);
2130    let counter_state: &{0}::state::Counter = bytemuck::from_bytes(&account.data[8..]);
2131    assert_eq!(counter_state.count, 0);
2132    assert_eq!(counter_state.authority, payer.pubkey());
2133}}
2134"#,
2135            name.to_snake_case(),
2136        ),
2137    )]
2138}
2139
2140#[cfg(test)]
2141mod tests {
2142    use super::*;
2143
2144    #[test]
2145    fn v1_templates_keep_legacy_anchor_lang_shape() {
2146        let manifest = cargo_toml("counter", Some(&TestTemplate::Litesvm), AnchorVersion::V1);
2147        assert!(manifest.contains("anchor-lang ="));
2148        assert!(manifest.contains("litesvm = \"0.10.0\""));
2149        assert!(!manifest.contains("anchor-lang-v2"));
2150
2151        let test = ts_mocha("counter", AnchorVersion::V1);
2152        assert!(test.contains("[Buffer.from(\"counter\")]"));
2153        assert!(test.contains(".accountsPartial({ counter })"));
2154        assert!(!test.contains("counter: counter.publicKey"));
2155    }
2156
2157    #[test]
2158    fn v2_templates_use_anchor_next_counter_shape() {
2159        let manifest = cargo_toml("counter", Some(&TestTemplate::Litesvm), AnchorVersion::V2);
2160        assert!(manifest.contains("anchor-lang-v2 = { git = "));
2161        assert!(manifest.contains("profile = [\"anchor-v2-testing/profile\"]"));
2162        assert!(manifest.contains("anchor-v2-testing = { git = "));
2163
2164        let test = ts_mocha("counter", AnchorVersion::V2);
2165        assert!(test.contains("const counter = anchor.web3.Keypair.generate();"));
2166        assert!(test.contains("counter: counter.publicKey"));
2167    }
2168}