1use crate::config::{
2 get_default_ledger_path, AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride,
3 Manifest, PackageManager, ProgramArch, ProgramDeployment, ProgramWorkspace, ScriptsConfig,
4 TestValidator, WithPath, SHUTDOWN_WAIT, STARTUP_WAIT,
5};
6use anchor_client::Cluster;
7use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY};
8use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize, Discriminator};
9use anchor_lang_idl::convert::convert_idl;
10use anchor_lang_idl::types::{Idl, IdlArrayLen, IdlDefinedFields, IdlType, IdlTypeDefTy};
11use anyhow::{anyhow, Context, Result};
12use checks::{check_anchor_version, check_deps, check_idl_build_feature, check_overflow};
13use clap::{CommandFactory, Parser};
14use dirs::home_dir;
15use flate2::read::GzDecoder;
16use flate2::read::ZlibDecoder;
17use flate2::write::{GzEncoder, ZlibEncoder};
18use flate2::Compression;
19use heck::{ToKebabCase, ToLowerCamelCase, ToPascalCase, ToSnakeCase};
20use regex::{Regex, RegexBuilder};
21use reqwest::blocking::multipart::{Form, Part};
22use reqwest::blocking::Client;
23use rust_template::{ProgramTemplate, TestTemplate};
24use semver::{Version, VersionReq};
25use serde::Deserialize;
26use serde_json::{json, Map, Value as JsonValue};
27use solana_client::rpc_client::RpcClient;
28use solana_sdk::account_utils::StateMut;
29use solana_sdk::bpf_loader;
30use solana_sdk::bpf_loader_deprecated;
31use solana_sdk::bpf_loader_upgradeable::{self, UpgradeableLoaderState};
32use solana_sdk::commitment_config::CommitmentConfig;
33use solana_sdk::compute_budget::ComputeBudgetInstruction;
34use solana_sdk::instruction::{AccountMeta, Instruction};
35use solana_sdk::pubkey::Pubkey;
36use solana_sdk::signature::Keypair;
37use solana_sdk::signature::Signer;
38use solana_sdk::signer::EncodableKey;
39use solana_sdk::transaction::Transaction;
40use std::collections::BTreeMap;
41use std::collections::HashMap;
42use std::collections::HashSet;
43use std::ffi::OsString;
44use std::fs::{self, File};
45use std::io::prelude::*;
46use std::path::{Path, PathBuf};
47use std::process::{Child, Stdio};
48use std::str::FromStr;
49use std::string::ToString;
50use tar::Archive;
51
52mod checks;
53pub mod config;
54pub mod rust_template;
55pub mod solidity_template;
56
57pub const VERSION: &str = env!("CARGO_PKG_VERSION");
59pub const DOCKER_BUILDER_VERSION: &str = VERSION;
60
61#[derive(Debug, Parser)]
62#[clap(version = VERSION)]
63pub struct Opts {
64 #[clap(flatten)]
65 pub cfg_override: ConfigOverride,
66 #[clap(subcommand)]
67 pub command: Command,
68}
69
70#[derive(Debug, Parser)]
71pub enum Command {
72 Init {
74 name: String,
76 #[clap(short, long)]
78 javascript: bool,
79 #[clap(short, long)]
81 solidity: bool,
82 #[clap(long)]
84 no_install: bool,
85 #[clap(value_enum, long, default_value = "yarn")]
87 package_manager: PackageManager,
88 #[clap(long)]
90 no_git: bool,
91 #[clap(value_enum, short, long, default_value = "single")]
93 template: ProgramTemplate,
94 #[clap(value_enum, long, default_value = "mocha")]
96 test_template: TestTemplate,
97 #[clap(long, action)]
99 force: bool,
100 },
101 #[clap(name = "build", alias = "b")]
103 Build {
104 #[clap(long)]
106 skip_lint: bool,
107 #[clap(long)]
109 no_idl: bool,
110 #[clap(short, long)]
112 idl: Option<String>,
113 #[clap(short = 't', long)]
115 idl_ts: Option<String>,
116 #[clap(short, long)]
118 verifiable: bool,
119 #[clap(short, long)]
121 program_name: Option<String>,
122 #[clap(short, long)]
125 solana_version: Option<String>,
126 #[clap(short, long)]
128 docker_image: Option<String>,
129 #[clap(value_enum, short, long, default_value = "none")]
132 bootstrap: BootstrapMode,
133 #[clap(short, long, required = false)]
135 env: Vec<String>,
136 #[clap(required = false, last = true)]
138 cargo_args: Vec<String>,
139 #[clap(long)]
141 no_docs: bool,
142 #[clap(value_enum, long, default_value = "sbf")]
144 arch: ProgramArch,
145 },
146 Expand {
153 #[clap(short, long)]
155 program_name: Option<String>,
156 #[clap(required = false, last = true)]
158 cargo_args: Vec<String>,
159 },
160 Verify {
164 program_id: Pubkey,
166 #[clap(short, long)]
167 program_name: Option<String>,
168 #[clap(short, long)]
171 solana_version: Option<String>,
172 #[clap(short, long)]
174 docker_image: Option<String>,
175 #[clap(value_enum, short, long, default_value = "none")]
178 bootstrap: BootstrapMode,
179 #[clap(value_enum, long, default_value = "sbf")]
181 arch: ProgramArch,
182 #[clap(short, long, required = false)]
184 env: Vec<String>,
185 #[clap(required = false, last = true)]
187 cargo_args: Vec<String>,
188 #[clap(long, required = false)]
191 skip_build: bool,
192 },
193 #[clap(name = "test", alias = "t")]
194 Test {
196 #[clap(short, long)]
198 program_name: Option<String>,
199 #[clap(long)]
202 skip_deploy: bool,
203 #[clap(long)]
206 skip_lint: bool,
207 #[clap(long)]
210 skip_local_validator: bool,
211 #[clap(long)]
214 skip_build: bool,
215 #[clap(long)]
217 no_idl: bool,
218 #[clap(value_enum, long, default_value = "sbf")]
220 arch: ProgramArch,
221 #[clap(long)]
224 detach: bool,
225 #[clap(long)]
227 run: Vec<String>,
228 args: Vec<String>,
229 #[clap(short, long, required = false)]
231 env: Vec<String>,
232 #[clap(required = false, last = true)]
234 cargo_args: Vec<String>,
235 },
236 New {
238 name: String,
240 #[clap(short, long)]
242 solidity: bool,
243 #[clap(value_enum, short, long, default_value = "single")]
245 template: ProgramTemplate,
246 #[clap(long, action)]
248 force: bool,
249 },
250 Idl {
252 #[clap(subcommand)]
253 subcmd: IdlCommand,
254 },
255 Clean,
257 Deploy {
259 #[clap(short, long)]
261 program_name: Option<String>,
262 #[clap(long, requires = "program_name")]
264 program_keypair: Option<String>,
265 #[clap(short, long)]
267 verifiable: bool,
268 #[clap(required = false, last = true)]
270 solana_args: Vec<String>,
271 },
272 Migrate,
274 Upgrade {
278 #[clap(short, long)]
280 program_id: Pubkey,
281 program_filepath: String,
283 #[clap(long, default_value = "0")]
285 max_retries: u32,
286 #[clap(required = false, last = true)]
288 solana_args: Vec<String>,
289 },
290 #[cfg(feature = "dev")]
291 Airdrop {
293 #[clap(short, long)]
294 url: Option<String>,
295 },
296 Cluster {
298 #[clap(subcommand)]
299 subcmd: ClusterCommand,
300 },
301 Shell,
304 Run {
306 script: String,
308 #[clap(required = false, last = true)]
310 script_args: Vec<String>,
311 },
312 Login {
314 token: String,
316 },
317 Publish {
319 program: String,
321 #[clap(short, long, required = false)]
323 env: Vec<String>,
324 #[clap(required = false, last = true)]
326 cargo_args: Vec<String>,
327 #[clap(long)]
330 skip_build: bool,
331 #[clap(value_enum, long, default_value = "sbf")]
333 arch: ProgramArch,
334 },
335 Keys {
337 #[clap(subcommand)]
338 subcmd: KeysCommand,
339 },
340 Localnet {
342 #[clap(long)]
345 skip_build: bool,
346 #[clap(long)]
349 skip_deploy: bool,
350 #[clap(long)]
353 skip_lint: bool,
354 #[clap(value_enum, long, default_value = "sbf")]
356 arch: ProgramArch,
357 #[clap(short, long, required = false)]
359 env: Vec<String>,
360 #[clap(required = false, last = true)]
362 cargo_args: Vec<String>,
363 },
364 Account {
366 account_type: String,
368 address: Pubkey,
370 #[clap(long)]
372 idl: Option<String>,
373 },
374 Completions {
376 #[clap(value_enum)]
377 shell: clap_complete::Shell,
378 },
379}
380
381#[derive(Debug, Parser)]
382pub enum KeysCommand {
383 List,
385 Sync {
387 #[clap(short, long)]
389 program_name: Option<String>,
390 },
391}
392
393#[derive(Debug, Parser)]
394pub enum IdlCommand {
395 Init {
397 program_id: Pubkey,
398 #[clap(short, long)]
399 filepath: String,
400 #[clap(long)]
401 priority_fee: Option<u64>,
402 },
403 Close {
404 program_id: Pubkey,
405 #[clap(long)]
407 idl_address: Option<Pubkey>,
408 #[clap(long)]
411 print_only: bool,
412 #[clap(long)]
413 priority_fee: Option<u64>,
414 },
415 WriteBuffer {
418 program_id: Pubkey,
419 #[clap(short, long)]
420 filepath: String,
421 #[clap(long)]
422 priority_fee: Option<u64>,
423 },
424 SetBuffer {
426 program_id: Pubkey,
427 #[clap(short, long)]
429 buffer: Pubkey,
430 #[clap(long)]
433 print_only: bool,
434 #[clap(long)]
435 priority_fee: Option<u64>,
436 },
437 Upgrade {
440 program_id: Pubkey,
441 #[clap(short, long)]
442 filepath: String,
443 #[clap(long)]
444 priority_fee: Option<u64>,
445 },
446 SetAuthority {
448 address: Option<Pubkey>,
451 #[clap(short, long)]
453 program_id: Pubkey,
454 #[clap(short, long)]
456 new_authority: Pubkey,
457 #[clap(long)]
460 print_only: bool,
461 #[clap(long)]
462 priority_fee: Option<u64>,
463 },
464 EraseAuthority {
468 #[clap(short, long)]
469 program_id: Pubkey,
470 #[clap(long)]
471 priority_fee: Option<u64>,
472 },
473 Authority {
475 program_id: Pubkey,
477 },
478 #[clap(alias = "b")]
480 Build {
481 #[clap(short, long)]
483 program_name: Option<String>,
484 #[clap(short, long)]
486 out: Option<String>,
487 #[clap(short = 't', long)]
489 out_ts: Option<String>,
490 #[clap(long)]
492 no_docs: bool,
493 #[clap(long)]
495 skip_lint: bool,
496 #[clap(required = false, last = true)]
498 cargo_args: Vec<String>,
499 },
500 Fetch {
503 address: Pubkey,
504 #[clap(short, long)]
506 out: Option<String>,
507 },
508 Convert {
510 path: String,
512 #[clap(short, long)]
514 out: Option<String>,
515 #[clap(short, long)]
517 program_id: Option<Pubkey>,
518 },
519 Type {
521 path: String,
523 #[clap(short, long)]
525 out: Option<String>,
526 },
527}
528
529#[derive(Debug, Parser)]
530pub enum ClusterCommand {
531 List,
533}
534
535fn get_keypair(path: &str) -> Result<Keypair> {
536 solana_sdk::signature::read_keypair_file(path)
537 .map_err(|_| anyhow!("Unable to read keypair file ({path})"))
538}
539
540pub fn entry(opts: Opts) -> Result<()> {
541 let restore_cbs = override_toolchain(&opts.cfg_override)?;
542 let result = process_command(opts);
543 restore_toolchain(restore_cbs)?;
544
545 result
546}
547
548type RestoreToolchainCallbacks = Vec<Box<dyn FnOnce() -> Result<()>>>;
550
551fn override_toolchain(cfg_override: &ConfigOverride) -> Result<RestoreToolchainCallbacks> {
555 let mut restore_cbs: RestoreToolchainCallbacks = vec![];
556
557 let cfg = Config::discover(cfg_override)?;
558 if let Some(cfg) = cfg {
559 fn parse_version(text: &str) -> Option<String> {
560 Some(
561 Regex::new(r"(\d+\.\d+\.\S+)")
562 .unwrap()
563 .captures_iter(text)
564 .next()?
565 .get(0)?
566 .as_str()
567 .to_string(),
568 )
569 }
570
571 fn get_current_version(cmd_name: &str) -> Result<String> {
572 let output = std::process::Command::new(cmd_name)
573 .arg("--version")
574 .output()?;
575 if !output.status.success() {
576 return Err(anyhow!("Failed to run `{cmd_name} --version`"));
577 }
578
579 let output_version = std::str::from_utf8(&output.stdout)?;
580 parse_version(output_version)
581 .ok_or_else(|| anyhow!("Failed to parse the version of `{cmd_name}`"))
582 }
583
584 if let Some(solana_version) = &cfg.toolchain.solana_version {
585 let current_version = get_current_version("solana")?;
586 if solana_version != ¤t_version {
587 fn override_solana_version(version: String) -> Result<bool> {
591 let (cmd_name, domain) =
594 if Version::parse(&version)? < Version::parse("1.18.19")? {
595 ("solana-install", "solana.com")
596 } else {
597 ("agave-install", "anza.xyz")
598 };
599
600 if get_current_version(cmd_name).is_err() {
602 eprintln!(
609 "Command not installed: `{cmd_name}`. \
610 See https://github.com/anza-xyz/agave/wiki/Agave-Transition, \
611 installing..."
612 );
613 let install_script = std::process::Command::new("curl")
614 .args([
615 "-sSfL",
616 &format!("https://release.{domain}/v{version}/install"),
617 ])
618 .output()?;
619 let is_successful = std::process::Command::new("sh")
620 .args(["-c", std::str::from_utf8(&install_script.stdout)?])
621 .spawn()?
622 .wait_with_output()?
623 .status
624 .success();
625 if !is_successful {
626 return Err(anyhow!("Failed to install `{cmd_name}`"));
627 }
628 }
629
630 let output = std::process::Command::new(cmd_name).arg("list").output()?;
631 if !output.status.success() {
632 return Err(anyhow!("Failed to list installed `solana` versions"));
633 }
634
635 let is_installed = std::str::from_utf8(&output.stdout)?
637 .lines()
638 .filter_map(parse_version)
639 .any(|line_version| line_version == version);
640 let (stderr, stdout) = if is_installed {
641 (Stdio::null(), Stdio::null())
642 } else {
643 (Stdio::inherit(), Stdio::inherit())
644 };
645
646 std::process::Command::new(cmd_name)
647 .arg("init")
648 .arg(&version)
649 .stderr(stderr)
650 .stdout(stdout)
651 .spawn()?
652 .wait()
653 .map(|status| status.success())
654 .map_err(|err| anyhow!("Failed to run `{cmd_name}` command: {err}"))
655 }
656
657 match override_solana_version(solana_version.to_owned())? {
658 true => restore_cbs.push(Box::new(|| {
659 match override_solana_version(current_version)? {
660 true => Ok(()),
661 false => Err(anyhow!("Failed to restore `solana` version")),
662 }
663 })),
664 false => eprintln!(
665 "Failed to override `solana` version to {solana_version}, \
666 using {current_version} instead"
667 ),
668 }
669 }
670 }
671
672 if let Some(anchor_version) = &cfg.toolchain.anchor_version {
674 const ANCHOR_BINARY_PREFIX: &str = "anchor-";
676
677 let current_version = std::env::args()
680 .next()
681 .expect("First arg should exist")
682 .parse::<PathBuf>()?
683 .file_name()
684 .and_then(|name| name.to_str())
685 .expect("File name should be valid Unicode")
686 .split_once(ANCHOR_BINARY_PREFIX)
687 .map(|(_, version)| version)
688 .unwrap_or(VERSION)
689 .to_owned();
690 if anchor_version != ¤t_version {
691 let binary_path = home_dir()
692 .unwrap()
693 .join(".avm")
694 .join("bin")
695 .join(format!("{ANCHOR_BINARY_PREFIX}{anchor_version}"));
696
697 if !binary_path.exists() {
698 eprintln!(
699 "`anchor` {anchor_version} is not installed with `avm`. Installing...\n"
700 );
701
702 let exit_status = std::process::Command::new("avm")
703 .arg("install")
704 .arg(anchor_version)
705 .spawn()?
706 .wait()?;
707 if !exit_status.success() {
708 eprintln!(
709 "Failed to install `anchor` {anchor_version}, \
710 using {current_version} instead"
711 );
712
713 return Ok(restore_cbs);
714 }
715 }
716
717 let exit_code = std::process::Command::new(binary_path)
718 .args(std::env::args_os().skip(1))
719 .spawn()?
720 .wait()?
721 .code()
722 .unwrap_or(1);
723 restore_toolchain(restore_cbs)?;
724 std::process::exit(exit_code);
725 }
726 }
727 }
728
729 Ok(restore_cbs)
730}
731
732fn restore_toolchain(restore_cbs: RestoreToolchainCallbacks) -> Result<()> {
734 for restore_toolchain in restore_cbs {
735 if let Err(e) = restore_toolchain() {
736 eprintln!("Toolchain error: {e}");
737 }
738 }
739
740 Ok(())
741}
742
743fn get_npm_init_license() -> Result<String> {
745 let npm_init_license_output = std::process::Command::new("npm")
746 .arg("config")
747 .arg("get")
748 .arg("init-license")
749 .output()?;
750
751 if !npm_init_license_output.status.success() {
752 return Err(anyhow!("Failed to get npm init license"));
753 }
754
755 let license = String::from_utf8(npm_init_license_output.stdout)?;
756 Ok(license.trim().to_string())
757}
758
759fn process_command(opts: Opts) -> Result<()> {
760 match opts.command {
761 Command::Init {
762 name,
763 javascript,
764 solidity,
765 no_install,
766 package_manager,
767 no_git,
768 template,
769 test_template,
770 force,
771 } => init(
772 &opts.cfg_override,
773 name,
774 javascript,
775 solidity,
776 no_install,
777 package_manager,
778 no_git,
779 template,
780 test_template,
781 force,
782 ),
783 Command::New {
784 solidity,
785 name,
786 template,
787 force,
788 } => new(&opts.cfg_override, solidity, name, template, force),
789 Command::Build {
790 no_idl,
791 idl,
792 idl_ts,
793 verifiable,
794 program_name,
795 solana_version,
796 docker_image,
797 bootstrap,
798 cargo_args,
799 env,
800 skip_lint,
801 no_docs,
802 arch,
803 } => build(
804 &opts.cfg_override,
805 no_idl,
806 idl,
807 idl_ts,
808 verifiable,
809 skip_lint,
810 program_name,
811 solana_version,
812 docker_image,
813 bootstrap,
814 None,
815 None,
816 env,
817 cargo_args,
818 no_docs,
819 arch,
820 ),
821 Command::Verify {
822 program_id,
823 program_name,
824 solana_version,
825 docker_image,
826 bootstrap,
827 env,
828 cargo_args,
829 skip_build,
830 arch,
831 } => verify(
832 &opts.cfg_override,
833 program_id,
834 program_name,
835 solana_version,
836 docker_image,
837 bootstrap,
838 env,
839 cargo_args,
840 skip_build,
841 arch,
842 ),
843 Command::Clean => clean(&opts.cfg_override),
844 Command::Deploy {
845 program_name,
846 program_keypair,
847 verifiable,
848 solana_args,
849 } => deploy(
850 &opts.cfg_override,
851 program_name,
852 program_keypair,
853 verifiable,
854 solana_args,
855 ),
856 Command::Expand {
857 program_name,
858 cargo_args,
859 } => expand(&opts.cfg_override, program_name, &cargo_args),
860 Command::Upgrade {
861 program_id,
862 program_filepath,
863 max_retries,
864 solana_args,
865 } => upgrade(
866 &opts.cfg_override,
867 program_id,
868 program_filepath,
869 max_retries,
870 solana_args,
871 ),
872 Command::Idl { subcmd } => idl(&opts.cfg_override, subcmd),
873 Command::Migrate => migrate(&opts.cfg_override),
874 Command::Test {
875 program_name,
876 skip_deploy,
877 skip_local_validator,
878 skip_build,
879 no_idl,
880 detach,
881 run,
882 args,
883 env,
884 cargo_args,
885 skip_lint,
886 arch,
887 } => test(
888 &opts.cfg_override,
889 program_name,
890 skip_deploy,
891 skip_local_validator,
892 skip_build,
893 skip_lint,
894 no_idl,
895 detach,
896 run,
897 args,
898 env,
899 cargo_args,
900 arch,
901 ),
902 #[cfg(feature = "dev")]
903 Command::Airdrop { .. } => airdrop(&opts.cfg_override),
904 Command::Cluster { subcmd } => cluster(subcmd),
905 Command::Shell => shell(&opts.cfg_override),
906 Command::Run {
907 script,
908 script_args,
909 } => run(&opts.cfg_override, script, script_args),
910 Command::Login { token } => login(&opts.cfg_override, token),
911 Command::Publish {
912 program,
913 env,
914 cargo_args,
915 skip_build,
916 arch,
917 } => publish(
918 &opts.cfg_override,
919 program,
920 env,
921 cargo_args,
922 skip_build,
923 arch,
924 ),
925 Command::Keys { subcmd } => keys(&opts.cfg_override, subcmd),
926 Command::Localnet {
927 skip_build,
928 skip_deploy,
929 skip_lint,
930 env,
931 cargo_args,
932 arch,
933 } => localnet(
934 &opts.cfg_override,
935 skip_build,
936 skip_deploy,
937 skip_lint,
938 env,
939 cargo_args,
940 arch,
941 ),
942 Command::Account {
943 account_type,
944 address,
945 idl,
946 } => account(&opts.cfg_override, account_type, address, idl),
947 Command::Completions { shell } => {
948 clap_complete::generate(
949 shell,
950 &mut Opts::command(),
951 "anchor",
952 &mut std::io::stdout(),
953 );
954 Ok(())
955 }
956 }
957}
958
959#[allow(clippy::too_many_arguments)]
960fn init(
961 cfg_override: &ConfigOverride,
962 name: String,
963 javascript: bool,
964 solidity: bool,
965 no_install: bool,
966 package_manager: PackageManager,
967 no_git: bool,
968 template: ProgramTemplate,
969 test_template: TestTemplate,
970 force: bool,
971) -> Result<()> {
972 if !force && Config::discover(cfg_override)?.is_some() {
973 return Err(anyhow!("Workspace already initialized"));
974 }
975
976 let rust_name = name.to_snake_case();
978 let project_name = if name == rust_name {
979 rust_name.clone()
980 } else {
981 name.to_kebab_case()
982 };
983
984 let extra_keywords = ["async", "await", "try"];
987 if syn::parse_str::<syn::Ident>(&rust_name).is_err()
989 || extra_keywords.contains(&rust_name.as_str())
990 {
991 return Err(anyhow!(
992 "Anchor workspace name must be a valid Rust identifier. It may not be a Rust reserved word, start with a digit, or include certain disallowed characters. See https://doc.rust-lang.org/reference/identifiers.html for more detail.",
993 ));
994 }
995
996 if force {
997 fs::create_dir_all(&project_name)?;
998 } else {
999 fs::create_dir(&project_name)?;
1000 }
1001 std::env::set_current_dir(&project_name)?;
1002 fs::create_dir_all("app")?;
1003
1004 let mut cfg = Config::default();
1005
1006 let test_script = test_template.get_test_script(javascript, &package_manager);
1007 cfg.scripts.insert("test".to_owned(), test_script);
1008
1009 let package_manager_cmd = package_manager.to_string();
1010 cfg.toolchain.package_manager = Some(package_manager);
1011
1012 let mut localnet = BTreeMap::new();
1013 let program_id = rust_template::get_or_create_program_id(&rust_name);
1014 localnet.insert(
1015 rust_name,
1016 ProgramDeployment {
1017 address: program_id,
1018 path: None,
1019 idl: None,
1020 },
1021 );
1022 cfg.programs.insert(Cluster::Localnet, localnet);
1023 let toml = cfg.to_string();
1024 fs::write("Anchor.toml", toml)?;
1025
1026 fs::write(".gitignore", rust_template::git_ignore())?;
1028
1029 fs::write(".prettierignore", rust_template::prettier_ignore())?;
1031
1032 if force {
1034 fs::remove_dir_all(
1035 std::env::current_dir()?
1036 .join(if solidity { "solidity" } else { "programs" })
1037 .join(&project_name),
1038 )?;
1039 }
1040
1041 if solidity {
1043 solidity_template::create_program(&project_name)?;
1044 } else {
1045 rust_template::create_program(
1046 &project_name,
1047 template,
1048 TestTemplate::Mollusk == test_template,
1049 )?;
1050 }
1051
1052 let migrations_path = Path::new("migrations");
1054 fs::create_dir_all(migrations_path)?;
1055
1056 let license = get_npm_init_license()?;
1057
1058 let jest = TestTemplate::Jest == test_template;
1059 if javascript {
1060 let mut package_json = File::create("package.json")?;
1062 package_json.write_all(rust_template::package_json(jest, license).as_bytes())?;
1063
1064 let mut deploy = File::create(migrations_path.join("deploy.js"))?;
1065 deploy.write_all(rust_template::deploy_script().as_bytes())?;
1066 } else {
1067 let mut ts_config = File::create("tsconfig.json")?;
1069 ts_config.write_all(rust_template::ts_config(jest).as_bytes())?;
1070
1071 let mut ts_package_json = File::create("package.json")?;
1072 ts_package_json.write_all(rust_template::ts_package_json(jest, license).as_bytes())?;
1073
1074 let mut deploy = File::create(migrations_path.join("deploy.ts"))?;
1075 deploy.write_all(rust_template::ts_deploy_script().as_bytes())?;
1076 }
1077
1078 test_template.create_test_files(
1079 &project_name,
1080 javascript,
1081 solidity,
1082 &program_id.to_string(),
1083 )?;
1084
1085 if !no_install {
1086 let package_manager_result = install_node_modules(&package_manager_cmd)?;
1087
1088 if !package_manager_result.status.success() && package_manager_cmd != "npm" {
1089 println!(
1090 "Failed {} install will attempt to npm install",
1091 package_manager_cmd
1092 );
1093 install_node_modules("npm")?;
1094 } else {
1095 eprintln!("Failed to install node modules");
1096 }
1097 }
1098
1099 if !no_git {
1100 let git_result = std::process::Command::new("git")
1101 .arg("init")
1102 .stdout(Stdio::inherit())
1103 .stderr(Stdio::inherit())
1104 .output()
1105 .map_err(|e| anyhow::format_err!("git init failed: {}", e.to_string()))?;
1106 if !git_result.status.success() {
1107 eprintln!("Failed to automatically initialize a new git repository");
1108 }
1109 }
1110
1111 println!("{project_name} initialized");
1112
1113 Ok(())
1114}
1115
1116fn install_node_modules(cmd: &str) -> Result<std::process::Output> {
1117 if cfg!(target_os = "windows") {
1118 std::process::Command::new("cmd")
1119 .arg(format!("/C {cmd} install"))
1120 .stdout(Stdio::inherit())
1121 .stderr(Stdio::inherit())
1122 .output()
1123 .map_err(|e| anyhow::format_err!("{} install failed: {}", cmd, e.to_string()))
1124 } else {
1125 std::process::Command::new(cmd)
1126 .arg("install")
1127 .stdout(Stdio::inherit())
1128 .stderr(Stdio::inherit())
1129 .output()
1130 .map_err(|e| anyhow::format_err!("{} install failed: {}", cmd, e.to_string()))
1131 }
1132}
1133
1134fn new(
1136 cfg_override: &ConfigOverride,
1137 solidity: bool,
1138 name: String,
1139 template: ProgramTemplate,
1140 force: bool,
1141) -> Result<()> {
1142 with_workspace(cfg_override, |cfg| {
1143 match cfg.path().parent() {
1144 None => {
1145 println!("Unable to make new program");
1146 }
1147 Some(parent) => {
1148 std::env::set_current_dir(parent)?;
1149
1150 let cluster = cfg.provider.cluster.clone();
1151 let programs = cfg.programs.entry(cluster).or_default();
1152 if programs.contains_key(&name) {
1153 if !force {
1154 return Err(anyhow!("Program already exists"));
1155 }
1156
1157 fs::remove_dir_all(
1159 std::env::current_dir()?
1160 .join(if solidity { "solidity" } else { "programs" })
1161 .join(&name),
1162 )?;
1163 }
1164
1165 if solidity {
1166 solidity_template::create_program(&name)?;
1167 } else {
1168 rust_template::create_program(&name, template, false)?;
1169 }
1170
1171 programs.insert(
1172 name.clone(),
1173 ProgramDeployment {
1174 address: rust_template::get_or_create_program_id(&name),
1175 path: None,
1176 idl: None,
1177 },
1178 );
1179
1180 let toml = cfg.to_string();
1181 fs::write("Anchor.toml", toml)?;
1182
1183 println!("Created new program.");
1184 }
1185 };
1186 Ok(())
1187 })
1188}
1189
1190pub type Files = Vec<(PathBuf, String)>;
1192
1193pub fn create_files(files: &Files) -> Result<()> {
1201 for (path, content) in files {
1202 let path = path
1203 .display()
1204 .to_string()
1205 .replace('/', std::path::MAIN_SEPARATOR_STR);
1206 let path = Path::new(&path);
1207 if path.exists() {
1208 continue;
1209 }
1210
1211 match path.extension() {
1212 Some(_) => {
1213 fs::create_dir_all(path.parent().unwrap())?;
1214 fs::write(path, content)?;
1215 }
1216 None => fs::create_dir_all(path)?,
1217 }
1218 }
1219
1220 Ok(())
1221}
1222
1223pub fn override_or_create_files(files: &Files) -> Result<()> {
1231 for (path, content) in files {
1232 let path = Path::new(path);
1233 if path.exists() {
1234 let mut f = fs::OpenOptions::new()
1235 .write(true)
1236 .truncate(true)
1237 .open(path)?;
1238 f.write_all(content.as_bytes())?;
1239 f.flush()?;
1240 } else {
1241 fs::create_dir_all(path.parent().unwrap())?;
1242 fs::write(path, content)?;
1243 }
1244 }
1245
1246 Ok(())
1247}
1248
1249pub fn expand(
1250 cfg_override: &ConfigOverride,
1251 program_name: Option<String>,
1252 cargo_args: &[String],
1253) -> Result<()> {
1254 if let Some(program_name) = program_name.as_ref() {
1256 cd_member(cfg_override, program_name)?;
1257 }
1258
1259 let workspace_cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
1260 let cfg_parent = workspace_cfg.path().parent().expect("Invalid Anchor.toml");
1261 let cargo = Manifest::discover()?;
1262
1263 let expansions_path = cfg_parent.join(".anchor").join("expanded-macros");
1264 fs::create_dir_all(&expansions_path)?;
1265
1266 match cargo {
1267 None => expand_all(&workspace_cfg, expansions_path, cargo_args),
1269 Some(cargo) if cargo.path().parent() == workspace_cfg.path().parent() => {
1271 expand_all(&workspace_cfg, expansions_path, cargo_args)
1272 }
1273 Some(cargo) => expand_program(
1275 cargo.path().parent().unwrap().to_path_buf(),
1277 expansions_path,
1278 cargo_args,
1279 ),
1280 }
1281}
1282
1283fn expand_all(
1284 workspace_cfg: &WithPath<Config>,
1285 expansions_path: PathBuf,
1286 cargo_args: &[String],
1287) -> Result<()> {
1288 let cur_dir = std::env::current_dir()?;
1289 for p in workspace_cfg.get_rust_program_list()? {
1290 expand_program(p, expansions_path.clone(), cargo_args)?;
1291 }
1292 std::env::set_current_dir(cur_dir)?;
1293 Ok(())
1294}
1295
1296fn expand_program(
1297 program_path: PathBuf,
1298 expansions_path: PathBuf,
1299 cargo_args: &[String],
1300) -> Result<()> {
1301 let cargo = Manifest::from_path(program_path.join("Cargo.toml"))
1302 .map_err(|_| anyhow!("Could not find Cargo.toml for program"))?;
1303
1304 let target_dir_arg = {
1305 let mut target_dir_arg = OsString::from("--target-dir=");
1306 target_dir_arg.push(expansions_path.join("expand-target"));
1307 target_dir_arg
1308 };
1309
1310 let package_name = &cargo
1311 .package
1312 .as_ref()
1313 .ok_or_else(|| anyhow!("Cargo config is missing a package"))?
1314 .name;
1315 let program_expansions_path = expansions_path.join(package_name);
1316 fs::create_dir_all(&program_expansions_path)?;
1317
1318 let exit = std::process::Command::new("cargo")
1319 .arg("expand")
1320 .arg(target_dir_arg)
1321 .arg(format!("--package={package_name}"))
1322 .args(cargo_args)
1323 .stderr(Stdio::inherit())
1324 .output()
1325 .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1326 if !exit.status.success() {
1327 eprintln!("'anchor expand' failed. Perhaps you have not installed 'cargo-expand'? https://github.com/dtolnay/cargo-expand#installation");
1328 std::process::exit(exit.status.code().unwrap_or(1));
1329 }
1330
1331 let version = cargo.version();
1332 let time = chrono::Utc::now().to_string().replace(' ', "_");
1333 let file_path = program_expansions_path.join(format!("{package_name}-{version}-{time}.rs"));
1334 fs::write(&file_path, &exit.stdout).map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1335
1336 println!(
1337 "Expanded {} into file {}\n",
1338 package_name,
1339 file_path.to_string_lossy()
1340 );
1341 Ok(())
1342}
1343
1344#[allow(clippy::too_many_arguments)]
1345pub fn build(
1346 cfg_override: &ConfigOverride,
1347 no_idl: bool,
1348 idl: Option<String>,
1349 idl_ts: Option<String>,
1350 verifiable: bool,
1351 skip_lint: bool,
1352 program_name: Option<String>,
1353 solana_version: Option<String>,
1354 docker_image: Option<String>,
1355 bootstrap: BootstrapMode,
1356 stdout: Option<File>, stderr: Option<File>, env_vars: Vec<String>,
1359 cargo_args: Vec<String>,
1360 no_docs: bool,
1361 arch: ProgramArch,
1362) -> Result<()> {
1363 if let Some(program_name) = program_name.as_ref() {
1365 cd_member(cfg_override, program_name)?;
1366 }
1367 let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
1368 let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
1369
1370 let workspace_cargo_toml_path = cfg_parent.join("Cargo.toml");
1372 if workspace_cargo_toml_path.exists() {
1373 check_overflow(workspace_cargo_toml_path)?;
1374 }
1375
1376 check_anchor_version(&cfg).ok();
1378 check_deps(&cfg).ok();
1379
1380 let idl_out = match idl {
1381 Some(idl) => Some(PathBuf::from(idl)),
1382 None => Some(cfg_parent.join("target").join("idl")),
1383 };
1384 fs::create_dir_all(idl_out.as_ref().unwrap())?;
1385
1386 let idl_ts_out = match idl_ts {
1387 Some(idl_ts) => Some(PathBuf::from(idl_ts)),
1388 None => Some(cfg_parent.join("target").join("types")),
1389 };
1390 fs::create_dir_all(idl_ts_out.as_ref().unwrap())?;
1391
1392 if !cfg.workspace.types.is_empty() {
1393 fs::create_dir_all(cfg_parent.join(&cfg.workspace.types))?;
1394 };
1395
1396 let cargo = Manifest::discover()?;
1397 let build_config = BuildConfig {
1398 verifiable,
1399 solana_version: solana_version.or_else(|| cfg.toolchain.solana_version.clone()),
1400 docker_image: docker_image.unwrap_or_else(|| cfg.docker()),
1401 bootstrap,
1402 };
1403 match cargo {
1404 None => build_all(
1406 &cfg,
1407 cfg.path(),
1408 no_idl,
1409 idl_out,
1410 idl_ts_out,
1411 &build_config,
1412 stdout,
1413 stderr,
1414 env_vars,
1415 cargo_args,
1416 skip_lint,
1417 no_docs,
1418 arch,
1419 )?,
1420 Some(cargo) if cargo.path().parent() == cfg.path().parent() => build_all(
1422 &cfg,
1423 cfg.path(),
1424 no_idl,
1425 idl_out,
1426 idl_ts_out,
1427 &build_config,
1428 stdout,
1429 stderr,
1430 env_vars,
1431 cargo_args,
1432 skip_lint,
1433 no_docs,
1434 arch,
1435 )?,
1436 Some(cargo) => build_rust_cwd(
1438 &cfg,
1439 cargo.path().to_path_buf(),
1440 no_idl,
1441 idl_out,
1442 idl_ts_out,
1443 &build_config,
1444 stdout,
1445 stderr,
1446 env_vars,
1447 cargo_args,
1448 skip_lint,
1449 no_docs,
1450 &arch,
1451 )?,
1452 }
1453
1454 set_workspace_dir_or_exit();
1455
1456 Ok(())
1457}
1458
1459#[allow(clippy::too_many_arguments)]
1460fn build_all(
1461 cfg: &WithPath<Config>,
1462 cfg_path: &Path,
1463 no_idl: bool,
1464 idl_out: Option<PathBuf>,
1465 idl_ts_out: Option<PathBuf>,
1466 build_config: &BuildConfig,
1467 stdout: Option<File>, stderr: Option<File>, env_vars: Vec<String>,
1470 cargo_args: Vec<String>,
1471 skip_lint: bool,
1472 no_docs: bool,
1473 arch: ProgramArch,
1474) -> Result<()> {
1475 let cur_dir = std::env::current_dir()?;
1476 let r = match cfg_path.parent() {
1477 None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
1478 Some(_parent) => {
1479 for p in cfg.get_rust_program_list()? {
1480 build_rust_cwd(
1481 cfg,
1482 p.join("Cargo.toml"),
1483 no_idl,
1484 idl_out.clone(),
1485 idl_ts_out.clone(),
1486 build_config,
1487 stdout.as_ref().map(|f| f.try_clone()).transpose()?,
1488 stderr.as_ref().map(|f| f.try_clone()).transpose()?,
1489 env_vars.clone(),
1490 cargo_args.clone(),
1491 skip_lint,
1492 no_docs,
1493 &arch,
1494 )?;
1495 }
1496 for (name, path) in cfg.get_solidity_program_list()? {
1497 build_solidity_cwd(
1498 cfg,
1499 name,
1500 path,
1501 idl_out.clone(),
1502 idl_ts_out.clone(),
1503 build_config,
1504 stdout.as_ref().map(|f| f.try_clone()).transpose()?,
1505 stderr.as_ref().map(|f| f.try_clone()).transpose()?,
1506 cargo_args.clone(),
1507 )?;
1508 }
1509 Ok(())
1510 }
1511 };
1512 std::env::set_current_dir(cur_dir)?;
1513 r
1514}
1515
1516#[allow(clippy::too_many_arguments)]
1518fn build_rust_cwd(
1519 cfg: &WithPath<Config>,
1520 cargo_toml: PathBuf,
1521 no_idl: bool,
1522 idl_out: Option<PathBuf>,
1523 idl_ts_out: Option<PathBuf>,
1524 build_config: &BuildConfig,
1525 stdout: Option<File>,
1526 stderr: Option<File>,
1527 env_vars: Vec<String>,
1528 cargo_args: Vec<String>,
1529 skip_lint: bool,
1530 no_docs: bool,
1531 arch: &ProgramArch,
1532) -> Result<()> {
1533 match cargo_toml.parent() {
1534 None => return Err(anyhow!("Unable to find parent")),
1535 Some(p) => std::env::set_current_dir(p)?,
1536 };
1537 match build_config.verifiable {
1538 false => _build_rust_cwd(
1539 cfg, no_idl, idl_out, idl_ts_out, skip_lint, no_docs, arch, cargo_args,
1540 ),
1541 true => build_cwd_verifiable(
1542 cfg,
1543 cargo_toml,
1544 build_config,
1545 stdout,
1546 stderr,
1547 skip_lint,
1548 env_vars,
1549 cargo_args,
1550 no_docs,
1551 arch,
1552 ),
1553 }
1554}
1555
1556#[allow(clippy::too_many_arguments)]
1558fn build_solidity_cwd(
1559 cfg: &WithPath<Config>,
1560 name: String,
1561 path: PathBuf,
1562 idl_out: Option<PathBuf>,
1563 idl_ts_out: Option<PathBuf>,
1564 build_config: &BuildConfig,
1565 stdout: Option<File>,
1566 stderr: Option<File>,
1567 cargo_args: Vec<String>,
1568) -> Result<()> {
1569 match path.parent() {
1570 None => return Err(anyhow!("Unable to find parent")),
1571 Some(p) => std::env::set_current_dir(p)?,
1572 };
1573 match build_config.verifiable {
1574 false => _build_solidity_cwd(
1575 cfg, &name, &path, idl_out, idl_ts_out, stdout, stderr, cargo_args,
1576 ),
1577 true => panic!("verifiable solidity not supported"),
1578 }
1579}
1580
1581#[allow(clippy::too_many_arguments)]
1584fn build_cwd_verifiable(
1585 cfg: &WithPath<Config>,
1586 cargo_toml: PathBuf,
1587 build_config: &BuildConfig,
1588 stdout: Option<File>,
1589 stderr: Option<File>,
1590 skip_lint: bool,
1591 env_vars: Vec<String>,
1592 cargo_args: Vec<String>,
1593 no_docs: bool,
1594 arch: &ProgramArch,
1595) -> Result<()> {
1596 let workspace_dir = cfg.path().parent().unwrap().canonicalize()?;
1598 let target_dir = workspace_dir.join("target");
1599 fs::create_dir_all(target_dir.join("verifiable"))?;
1600 fs::create_dir_all(target_dir.join("idl"))?;
1601 fs::create_dir_all(target_dir.join("types"))?;
1602 if !&cfg.workspace.types.is_empty() {
1603 fs::create_dir_all(workspace_dir.join(&cfg.workspace.types))?;
1604 }
1605
1606 let container_name = "anchor-program";
1607
1608 let result = docker_build(
1610 cfg,
1611 container_name,
1612 cargo_toml,
1613 build_config,
1614 stdout,
1615 stderr,
1616 env_vars,
1617 cargo_args.clone(),
1618 arch,
1619 );
1620
1621 match &result {
1622 Err(e) => {
1623 eprintln!("Error during Docker build: {e:?}");
1624 }
1625 Ok(_) => {
1626 println!("Extracting the IDL");
1628 let idl = generate_idl(cfg, skip_lint, no_docs, &cargo_args)?;
1629 println!("Writing the IDL file");
1631 let out_file = workspace_dir
1632 .join("target")
1633 .join("idl")
1634 .join(&idl.metadata.name)
1635 .with_extension("json");
1636 write_idl(&idl, OutFile::File(out_file))?;
1637
1638 println!("Writing the .ts file");
1640 let ts_file = workspace_dir
1641 .join("target")
1642 .join("types")
1643 .join(&idl.metadata.name)
1644 .with_extension("ts");
1645 fs::write(&ts_file, idl_ts(&idl)?)?;
1646
1647 if !&cfg.workspace.types.is_empty() {
1649 fs::copy(
1650 ts_file,
1651 workspace_dir
1652 .join(&cfg.workspace.types)
1653 .join(idl.metadata.name)
1654 .with_extension("ts"),
1655 )?;
1656 }
1657
1658 println!("Build success");
1659 }
1660 }
1661
1662 result
1663}
1664
1665#[allow(clippy::too_many_arguments)]
1666fn docker_build(
1667 cfg: &WithPath<Config>,
1668 container_name: &str,
1669 cargo_toml: PathBuf,
1670 build_config: &BuildConfig,
1671 stdout: Option<File>,
1672 stderr: Option<File>,
1673 env_vars: Vec<String>,
1674 cargo_args: Vec<String>,
1675 arch: &ProgramArch,
1676) -> Result<()> {
1677 let binary_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
1678
1679 let workdir = Path::new("/workdir");
1681 let volume_mount = format!(
1682 "{}:{}",
1683 cfg.path().parent().unwrap().canonicalize()?.display(),
1684 workdir.to_str().unwrap(),
1685 );
1686 println!("Using image {:?}", build_config.docker_image);
1687
1688 let target_dir = workdir.join("docker-target");
1690 println!("Run docker image");
1691 let exit = std::process::Command::new("docker")
1692 .args([
1693 "run",
1694 "-it",
1695 "-d",
1696 "--name",
1697 container_name,
1698 "--env",
1699 &format!(
1700 "CARGO_TARGET_DIR={}",
1701 target_dir.as_path().to_str().unwrap()
1702 ),
1703 "-v",
1704 &volume_mount,
1705 "-w",
1706 workdir.to_str().unwrap(),
1707 &build_config.docker_image,
1708 "bash",
1709 ])
1710 .stdout(Stdio::inherit())
1711 .stderr(Stdio::inherit())
1712 .output()
1713 .map_err(|e| anyhow::format_err!("Docker build failed: {}", e.to_string()))?;
1714 if !exit.status.success() {
1715 return Err(anyhow!("Failed to build program"));
1716 }
1717
1718 let result = docker_prep(container_name, build_config).and_then(|_| {
1719 let cfg_parent = cfg.path().parent().unwrap();
1720 docker_build_bpf(
1721 container_name,
1722 cargo_toml.as_path(),
1723 cfg_parent,
1724 target_dir.as_path(),
1725 binary_name,
1726 stdout,
1727 stderr,
1728 env_vars,
1729 cargo_args,
1730 arch,
1731 )
1732 });
1733
1734 docker_cleanup(container_name, target_dir.as_path())?;
1736
1737 result
1739}
1740
1741fn docker_prep(container_name: &str, build_config: &BuildConfig) -> Result<()> {
1742 match build_config.bootstrap {
1745 BootstrapMode::Debian => {
1746 docker_exec(container_name, &["apt", "update"])?;
1748 docker_exec(
1749 container_name,
1750 &["apt", "install", "-y", "curl", "build-essential"],
1751 )?;
1752
1753 docker_exec(
1755 container_name,
1756 &["curl", "https://sh.rustup.rs", "-sfo", "rustup.sh"],
1757 )?;
1758 docker_exec(container_name, &["sh", "rustup.sh", "-y"])?;
1759 docker_exec(container_name, &["rm", "-f", "rustup.sh"])?;
1760 }
1761 BootstrapMode::None => {}
1762 }
1763
1764 if let Some(solana_version) = &build_config.solana_version {
1765 println!("Using solana version: {solana_version}");
1766
1767 docker_exec(
1769 container_name,
1770 &[
1771 "curl",
1772 "-sSfL",
1773 &format!("https://release.anza.xyz/v{solana_version}/install",),
1774 "-o",
1775 "solana_installer.sh",
1776 ],
1777 )?;
1778 docker_exec(container_name, &["sh", "solana_installer.sh"])?;
1779 docker_exec(container_name, &["rm", "-f", "solana_installer.sh"])?;
1780 }
1781 Ok(())
1782}
1783
1784#[allow(clippy::too_many_arguments)]
1785fn docker_build_bpf(
1786 container_name: &str,
1787 cargo_toml: &Path,
1788 cfg_parent: &Path,
1789 target_dir: &Path,
1790 binary_name: String,
1791 stdout: Option<File>,
1792 stderr: Option<File>,
1793 env_vars: Vec<String>,
1794 cargo_args: Vec<String>,
1795 arch: &ProgramArch,
1796) -> Result<()> {
1797 let manifest_path =
1798 pathdiff::diff_paths(cargo_toml.canonicalize()?, cfg_parent.canonicalize()?)
1799 .ok_or_else(|| anyhow!("Unable to diff paths"))?;
1800 println!(
1801 "Building {} manifest: {:?}",
1802 binary_name,
1803 manifest_path.display()
1804 );
1805
1806 let subcommand = arch.build_subcommand();
1807
1808 let exit = std::process::Command::new("docker")
1810 .args([
1811 "exec",
1812 "--env",
1813 "PATH=/root/.local/share/solana/install/active_release/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
1814 ])
1815 .args(env_vars
1816 .iter()
1817 .map(|x| ["--env", x.as_str()])
1818 .collect::<Vec<[&str; 2]>>()
1819 .concat())
1820 .args([
1821 container_name,
1822 "cargo",
1823 subcommand,
1824 "--manifest-path",
1825 &manifest_path.display().to_string(),
1826 ])
1827 .args(cargo_args)
1828 .stdout(match stdout {
1829 None => Stdio::inherit(),
1830 Some(f) => f.into(),
1831 })
1832 .stderr(match stderr {
1833 None => Stdio::inherit(),
1834 Some(f) => f.into(),
1835 })
1836 .output()
1837 .map_err(|e| anyhow::format_err!("Docker build failed: {}", e.to_string()))?;
1838 if !exit.status.success() {
1839 return Err(anyhow!("Failed to build program"));
1840 }
1841
1842 println!("Copying out the build artifacts");
1844 let out_file = cfg_parent
1845 .canonicalize()?
1846 .join(
1847 Path::new("target")
1848 .join("verifiable")
1849 .join(&binary_name)
1850 .with_extension("so"),
1851 )
1852 .display()
1853 .to_string();
1854
1855 let mut bin_path = target_dir.join("deploy");
1858 bin_path.push(format!("{binary_name}.so"));
1859 let bin_artifact = format!(
1860 "{}:{}",
1861 container_name,
1862 bin_path.as_path().to_str().unwrap()
1863 );
1864 let exit = std::process::Command::new("docker")
1865 .args(["cp", &bin_artifact, &out_file])
1866 .stdout(Stdio::inherit())
1867 .stderr(Stdio::inherit())
1868 .output()
1869 .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1870 if !exit.status.success() {
1871 Err(anyhow!(
1872 "Failed to copy binary out of docker. Is the target directory set correctly?"
1873 ))
1874 } else {
1875 Ok(())
1876 }
1877}
1878
1879fn docker_cleanup(container_name: &str, target_dir: &Path) -> Result<()> {
1880 println!("Cleaning up the docker target directory");
1882 docker_exec(container_name, &["rm", "-rf", target_dir.to_str().unwrap()])?;
1883
1884 println!("Removing the docker container");
1886 let exit = std::process::Command::new("docker")
1887 .args(["rm", "-f", container_name])
1888 .stdout(Stdio::inherit())
1889 .stderr(Stdio::inherit())
1890 .output()
1891 .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1892 if !exit.status.success() {
1893 println!("Unable to remove the docker container");
1894 std::process::exit(exit.status.code().unwrap_or(1));
1895 }
1896 Ok(())
1897}
1898
1899fn docker_exec(container_name: &str, args: &[&str]) -> Result<()> {
1900 let exit = std::process::Command::new("docker")
1901 .args([&["exec", container_name], args].concat())
1902 .stdout(Stdio::inherit())
1903 .stderr(Stdio::inherit())
1904 .output()
1905 .map_err(|e| anyhow!("Failed to run command \"{:?}\": {:?}", args, e))?;
1906 if !exit.status.success() {
1907 Err(anyhow!("Failed to run command: {:?}", args))
1908 } else {
1909 Ok(())
1910 }
1911}
1912
1913#[allow(clippy::too_many_arguments)]
1914fn _build_rust_cwd(
1915 cfg: &WithPath<Config>,
1916 no_idl: bool,
1917 idl_out: Option<PathBuf>,
1918 idl_ts_out: Option<PathBuf>,
1919 skip_lint: bool,
1920 no_docs: bool,
1921 arch: &ProgramArch,
1922 cargo_args: Vec<String>,
1923) -> Result<()> {
1924 let exit = std::process::Command::new("cargo")
1925 .arg(arch.build_subcommand())
1926 .args(cargo_args.clone())
1927 .stdout(Stdio::inherit())
1928 .stderr(Stdio::inherit())
1929 .output()
1930 .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
1931 if !exit.status.success() {
1932 std::process::exit(exit.status.code().unwrap_or(1));
1933 }
1934
1935 if !no_idl {
1937 let idl = generate_idl(cfg, skip_lint, no_docs, &cargo_args)?;
1938
1939 let out = match idl_out {
1941 None => PathBuf::from(".")
1942 .join(&idl.metadata.name)
1943 .with_extension("json"),
1944 Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("json")),
1945 };
1946 let ts_out = match idl_ts_out {
1948 None => PathBuf::from(".")
1949 .join(&idl.metadata.name)
1950 .with_extension("ts"),
1951 Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("ts")),
1952 };
1953
1954 write_idl(&idl, OutFile::File(out))?;
1956 fs::write(&ts_out, idl_ts(&idl)?)?;
1958
1959 let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
1961 if !&cfg.workspace.types.is_empty() {
1962 fs::copy(
1963 &ts_out,
1964 cfg_parent
1965 .join(&cfg.workspace.types)
1966 .join(&idl.metadata.name)
1967 .with_extension("ts"),
1968 )?;
1969 }
1970 }
1971
1972 Ok(())
1973}
1974
1975#[allow(clippy::too_many_arguments)]
1976fn _build_solidity_cwd(
1977 cfg: &WithPath<Config>,
1978 name: &str,
1979 path: &Path,
1980 idl_out: Option<PathBuf>,
1981 idl_ts_out: Option<PathBuf>,
1982 stdout: Option<File>,
1983 stderr: Option<File>,
1984 solang_args: Vec<String>,
1985) -> Result<()> {
1986 let mut cmd = std::process::Command::new("solang");
1987 let cmd = cmd.args(["compile", "--target", "solana", "--contract", name]);
1988
1989 if let Some(idl_out) = &idl_out {
1990 cmd.arg("--output-meta");
1991 cmd.arg(idl_out);
1992 }
1993
1994 let target_bin = cfg.path().parent().unwrap().join("target").join("deploy");
1995
1996 cmd.arg("--output");
1997 cmd.arg(target_bin);
1998 cmd.arg("--verbose");
1999 cmd.arg(path);
2000
2001 let exit = cmd
2002 .args(solang_args)
2003 .stdout(match stdout {
2004 None => Stdio::inherit(),
2005 Some(f) => f.into(),
2006 })
2007 .stderr(match stderr {
2008 None => Stdio::inherit(),
2009 Some(f) => f.into(),
2010 })
2011 .output()
2012 .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
2013 if !exit.status.success() {
2014 std::process::exit(exit.status.code().unwrap_or(1));
2015 }
2016
2017 let idl_path = idl_out
2019 .unwrap_or(PathBuf::from("."))
2020 .join(format!("{}.json", name));
2021
2022 let idl = fs::read(idl_path)?;
2023 let idl = convert_idl(&idl)?;
2024
2025 let ts_out = match idl_ts_out {
2027 None => PathBuf::from(".")
2028 .join(&idl.metadata.name)
2029 .with_extension("ts"),
2030 Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("ts")),
2031 };
2032
2033 fs::write(&ts_out, idl_ts(&idl)?)?;
2035 let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
2037 if !&cfg.workspace.types.is_empty() {
2038 fs::copy(
2039 &ts_out,
2040 cfg_parent
2041 .join(&cfg.workspace.types)
2042 .join(&idl.metadata.name)
2043 .with_extension("ts"),
2044 )?;
2045 }
2046
2047 Ok(())
2048}
2049
2050#[allow(clippy::too_many_arguments)]
2051fn verify(
2052 cfg_override: &ConfigOverride,
2053 program_id: Pubkey,
2054 program_name: Option<String>,
2055 solana_version: Option<String>,
2056 docker_image: Option<String>,
2057 bootstrap: BootstrapMode,
2058 env_vars: Vec<String>,
2059 cargo_args: Vec<String>,
2060 skip_build: bool,
2061 arch: ProgramArch,
2062) -> Result<()> {
2063 if let Some(program_name) = program_name.as_ref() {
2065 cd_member(cfg_override, program_name)?;
2066 }
2067
2068 let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
2070 let cargo = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
2071
2072 let cur_dir = std::env::current_dir()?;
2074 if !skip_build {
2075 build(
2076 cfg_override,
2077 true,
2078 None,
2079 None,
2080 true,
2081 true,
2082 None,
2083 solana_version.or_else(|| cfg.toolchain.solana_version.clone()),
2084 docker_image,
2085 bootstrap,
2086 None,
2087 None,
2088 env_vars,
2089 cargo_args.clone(),
2090 false,
2091 arch,
2092 )?;
2093 }
2094 std::env::set_current_dir(cur_dir)?;
2095
2096 let binary_name = cargo.lib_name()?;
2098 let bin_path = cfg
2099 .path()
2100 .parent()
2101 .ok_or_else(|| anyhow!("Unable to find workspace root"))?
2102 .join("target")
2103 .join("verifiable")
2104 .join(&binary_name)
2105 .with_extension("so");
2106
2107 let url = cluster_url(&cfg, &cfg.test_validator);
2108 let bin_ver = verify_bin(program_id, &bin_path, &url)?;
2109 if !bin_ver.is_verified {
2110 println!("Error: Binaries don't match");
2111 std::process::exit(1);
2112 }
2113
2114 let local_idl = generate_idl(&cfg, true, false, &cargo_args)?;
2116 if bin_ver.state != BinVerificationState::Buffer {
2117 let deployed_idl = fetch_idl(cfg_override, program_id).map(serde_json::from_value)??;
2118 if local_idl != deployed_idl {
2119 println!("Error: IDLs don't match");
2120 std::process::exit(1);
2121 }
2122 }
2123
2124 println!("{program_id} is verified.");
2125
2126 Ok(())
2127}
2128
2129fn cd_member(cfg_override: &ConfigOverride, program_name: &str) -> Result<()> {
2130 let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
2132
2133 for program in cfg.read_all_programs()? {
2134 if program.solidity {
2135 if let Some(path) = program.path.parent() {
2136 std::env::set_current_dir(path)?;
2137 return Ok(());
2138 }
2139 } else {
2140 let cargo_toml = program.path.join("Cargo.toml");
2141 if !cargo_toml.exists() {
2142 return Err(anyhow!(
2143 "Did not find Cargo.toml at the path: {}",
2144 program.path.display()
2145 ));
2146 }
2147
2148 let manifest = Manifest::from_path(&cargo_toml)?;
2149 let pkg_name = manifest.package().name();
2150 let lib_name = manifest.lib_name()?;
2151 if program_name == pkg_name || program_name == lib_name {
2152 std::env::set_current_dir(&program.path)?;
2153 return Ok(());
2154 }
2155 }
2156 }
2157
2158 Err(anyhow!("{} is not part of the workspace", program_name,))
2159}
2160
2161pub fn verify_bin(program_id: Pubkey, bin_path: &Path, cluster: &str) -> Result<BinVerification> {
2162 let client = RpcClient::new_with_commitment(cluster, CommitmentConfig::finalized());
2164
2165 let (deployed_bin, state) = {
2167 let account = client.get_account(&program_id)?;
2168 if account.owner == bpf_loader::id() || account.owner == bpf_loader_deprecated::id() {
2169 let bin = account.data.to_vec();
2170 let state = BinVerificationState::ProgramData {
2171 slot: 0, upgrade_authority_address: None,
2173 };
2174 (bin, state)
2175 } else if account.owner == bpf_loader_upgradeable::id() {
2176 match account.state()? {
2177 UpgradeableLoaderState::Program {
2178 programdata_address,
2179 } => {
2180 let account = client.get_account(&programdata_address)?;
2181 let bin = account.data
2182 [UpgradeableLoaderState::size_of_programdata_metadata()..]
2183 .to_vec();
2184
2185 if let UpgradeableLoaderState::ProgramData {
2186 slot,
2187 upgrade_authority_address,
2188 } = account.state()?
2189 {
2190 let state = BinVerificationState::ProgramData {
2191 slot,
2192 upgrade_authority_address,
2193 };
2194 (bin, state)
2195 } else {
2196 return Err(anyhow!("Expected program data"));
2197 }
2198 }
2199 UpgradeableLoaderState::Buffer { .. } => {
2200 let offset = UpgradeableLoaderState::size_of_buffer_metadata();
2201 (
2202 account.data[offset..].to_vec(),
2203 BinVerificationState::Buffer,
2204 )
2205 }
2206 _ => {
2207 return Err(anyhow!(
2208 "Invalid program id, not a buffer or program account"
2209 ))
2210 }
2211 }
2212 } else {
2213 return Err(anyhow!(
2214 "Invalid program id, not owned by any loader program"
2215 ));
2216 }
2217 };
2218 let mut local_bin = {
2219 let mut f = File::open(bin_path)?;
2220 let mut contents = vec![];
2221 f.read_to_end(&mut contents)?;
2222 contents
2223 };
2224
2225 if local_bin.len() < deployed_bin.len() {
2228 local_bin.append(&mut vec![0; deployed_bin.len() - local_bin.len()]);
2229 }
2230
2231 let is_verified = local_bin == deployed_bin;
2233
2234 Ok(BinVerification { state, is_verified })
2235}
2236
2237#[derive(PartialEq, Eq)]
2238pub struct BinVerification {
2239 pub state: BinVerificationState,
2240 pub is_verified: bool,
2241}
2242
2243#[derive(PartialEq, Eq)]
2244pub enum BinVerificationState {
2245 Buffer,
2246 ProgramData {
2247 slot: u64,
2248 upgrade_authority_address: Option<Pubkey>,
2249 },
2250}
2251
2252fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
2253 match subcmd {
2254 IdlCommand::Init {
2255 program_id,
2256 filepath,
2257 priority_fee,
2258 } => idl_init(cfg_override, program_id, filepath, priority_fee),
2259 IdlCommand::Close {
2260 program_id,
2261 idl_address,
2262 print_only,
2263 priority_fee,
2264 } => {
2265 let closed_address = idl_close(
2266 cfg_override,
2267 program_id,
2268 idl_address,
2269 print_only,
2270 priority_fee,
2271 )?;
2272 if !print_only {
2273 println!("Idl account closed: {closed_address}");
2274 }
2275 Ok(())
2276 }
2277 IdlCommand::WriteBuffer {
2278 program_id,
2279 filepath,
2280 priority_fee,
2281 } => {
2282 let idl_buffer = idl_write_buffer(cfg_override, program_id, filepath, priority_fee)?;
2283 println!("Idl buffer created: {idl_buffer}");
2284 Ok(())
2285 }
2286 IdlCommand::SetBuffer {
2287 program_id,
2288 buffer,
2289 print_only,
2290 priority_fee,
2291 } => idl_set_buffer(cfg_override, program_id, buffer, print_only, priority_fee).map(|_| ()),
2292 IdlCommand::Upgrade {
2293 program_id,
2294 filepath,
2295 priority_fee,
2296 } => idl_upgrade(cfg_override, program_id, filepath, priority_fee),
2297 IdlCommand::SetAuthority {
2298 program_id,
2299 address,
2300 new_authority,
2301 print_only,
2302 priority_fee,
2303 } => idl_set_authority(
2304 cfg_override,
2305 program_id,
2306 address,
2307 new_authority,
2308 print_only,
2309 priority_fee,
2310 ),
2311 IdlCommand::EraseAuthority {
2312 program_id,
2313 priority_fee,
2314 } => idl_erase_authority(cfg_override, program_id, priority_fee),
2315 IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
2316 IdlCommand::Build {
2317 program_name,
2318 out,
2319 out_ts,
2320 no_docs,
2321 skip_lint,
2322 cargo_args,
2323 } => idl_build(
2324 cfg_override,
2325 program_name,
2326 out,
2327 out_ts,
2328 no_docs,
2329 skip_lint,
2330 cargo_args,
2331 ),
2332 IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
2333 IdlCommand::Convert {
2334 path,
2335 out,
2336 program_id,
2337 } => idl_convert(path, out, program_id),
2338 IdlCommand::Type { path, out } => idl_type(path, out),
2339 }
2340}
2341
2342fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<serde_json::Value> {
2346 let url = match Config::discover(cfg_override)? {
2347 Some(cfg) => cluster_url(&cfg, &cfg.test_validator),
2348 None => {
2349 if let Some(cluster) = cfg_override.cluster.as_ref() {
2353 cluster.url().to_string()
2354 } else {
2355 config::get_solana_cfg_url()?
2356 }
2357 }
2358 };
2359
2360 let client = create_client(url);
2361
2362 let mut account = client.get_account(&idl_addr)?;
2363 if account.executable {
2364 let idl_addr = IdlAccount::address(&idl_addr);
2365 account = client.get_account(&idl_addr)?;
2366 }
2367
2368 let mut d: &[u8] = &account.data[IdlAccount::DISCRIMINATOR.len()..];
2370 let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?;
2371
2372 let compressed_len: usize = idl_account.data_len.try_into().unwrap();
2373 let compressed_bytes = &account.data[44..44 + compressed_len];
2374 let mut z = ZlibDecoder::new(compressed_bytes);
2375 let mut s = Vec::new();
2376 z.read_to_end(&mut s)?;
2377 serde_json::from_slice(&s[..]).map_err(Into::into)
2378}
2379
2380fn get_idl_account(client: &RpcClient, idl_address: &Pubkey) -> Result<IdlAccount> {
2381 let account = client.get_account(idl_address)?;
2382 let mut data: &[u8] = &account.data;
2383 AccountDeserialize::try_deserialize(&mut data).map_err(|e| anyhow!("{:?}", e))
2384}
2385
2386fn idl_init(
2387 cfg_override: &ConfigOverride,
2388 program_id: Pubkey,
2389 idl_filepath: String,
2390 priority_fee: Option<u64>,
2391) -> Result<()> {
2392 with_workspace(cfg_override, |cfg| {
2393 let keypair = cfg.provider.wallet.to_string();
2394
2395 let idl = fs::read(idl_filepath)?;
2396 let idl = convert_idl(&idl)?;
2397
2398 let idl_address = create_idl_account(cfg, &keypair, &program_id, &idl, priority_fee)?;
2399
2400 println!("Idl account created: {idl_address:?}");
2401 Ok(())
2402 })
2403}
2404
2405fn idl_close(
2406 cfg_override: &ConfigOverride,
2407 program_id: Pubkey,
2408 idl_address: Option<Pubkey>,
2409 print_only: bool,
2410 priority_fee: Option<u64>,
2411) -> Result<Pubkey> {
2412 with_workspace(cfg_override, |cfg| {
2413 let idl_address = idl_address.unwrap_or_else(|| IdlAccount::address(&program_id));
2414 idl_close_account(cfg, &program_id, idl_address, print_only, priority_fee)?;
2415
2416 Ok(idl_address)
2417 })
2418}
2419
2420fn idl_write_buffer(
2421 cfg_override: &ConfigOverride,
2422 program_id: Pubkey,
2423 idl_filepath: String,
2424 priority_fee: Option<u64>,
2425) -> Result<Pubkey> {
2426 with_workspace(cfg_override, |cfg| {
2427 let keypair = cfg.provider.wallet.to_string();
2428
2429 let idl = fs::read(idl_filepath)?;
2430 let idl = convert_idl(&idl)?;
2431
2432 let idl_buffer = create_idl_buffer(cfg, &keypair, &program_id, &idl, priority_fee)?;
2433 idl_write(cfg, &program_id, &idl, idl_buffer, priority_fee)?;
2434
2435 Ok(idl_buffer)
2436 })
2437}
2438
2439fn idl_set_buffer(
2440 cfg_override: &ConfigOverride,
2441 program_id: Pubkey,
2442 buffer: Pubkey,
2443 print_only: bool,
2444 priority_fee: Option<u64>,
2445) -> Result<Pubkey> {
2446 with_workspace(cfg_override, |cfg| {
2447 let keypair = get_keypair(&cfg.provider.wallet.to_string())?;
2448 let url = cluster_url(cfg, &cfg.test_validator);
2449 let client = create_client(url);
2450
2451 let idl_address = IdlAccount::address(&program_id);
2452 let idl_authority = if print_only {
2453 get_idl_account(&client, &idl_address)?.authority
2454 } else {
2455 keypair.pubkey()
2456 };
2457 let ix = {
2459 let accounts = vec![
2460 AccountMeta::new(buffer, false),
2461 AccountMeta::new(idl_address, false),
2462 AccountMeta::new(idl_authority, true),
2463 ];
2464 let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
2465 data.append(&mut IdlInstruction::SetBuffer.try_to_vec()?);
2466 Instruction {
2467 program_id,
2468 accounts,
2469 data,
2470 }
2471 };
2472
2473 if print_only {
2474 print_idl_instruction("SetBuffer", &ix, &idl_address)?;
2475 } else {
2476 let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?;
2478
2479 let mut latest_hash = client.get_latest_blockhash()?;
2481 for retries in 0..20 {
2482 if !client.is_blockhash_valid(&latest_hash, client.commitment())? {
2483 latest_hash = client.get_latest_blockhash()?;
2484 }
2485 let tx = Transaction::new_signed_with_payer(
2486 &instructions,
2487 Some(&keypair.pubkey()),
2488 &[&keypair],
2489 latest_hash,
2490 );
2491
2492 match client.send_and_confirm_transaction_with_spinner(&tx) {
2493 Ok(_) => break,
2494 Err(e) => {
2495 if retries == 19 {
2496 return Err(anyhow!("Error: {e}. Failed to send transaction."));
2497 }
2498 println!("Error: {e}. Retrying transaction.");
2499 }
2500 }
2501 }
2502 }
2503
2504 Ok(idl_address)
2505 })
2506}
2507
2508fn idl_upgrade(
2509 cfg_override: &ConfigOverride,
2510 program_id: Pubkey,
2511 idl_filepath: String,
2512 priority_fee: Option<u64>,
2513) -> Result<()> {
2514 let buffer_address = idl_write_buffer(cfg_override, program_id, idl_filepath, priority_fee)?;
2515 let idl_address = idl_set_buffer(
2516 cfg_override,
2517 program_id,
2518 buffer_address,
2519 false,
2520 priority_fee,
2521 )?;
2522 idl_close(
2523 cfg_override,
2524 program_id,
2525 Some(buffer_address),
2526 false,
2527 priority_fee,
2528 )?;
2529 println!("Idl account {idl_address} successfully upgraded");
2530 Ok(())
2531}
2532
2533fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
2534 with_workspace(cfg_override, |cfg| {
2535 let url = cluster_url(cfg, &cfg.test_validator);
2536 let client = create_client(url);
2537 let idl_address = {
2538 let account = client.get_account(&program_id)?;
2539 if account.executable {
2540 IdlAccount::address(&program_id)
2541 } else {
2542 program_id
2543 }
2544 };
2545
2546 let idl_account = get_idl_account(&client, &idl_address)?;
2547
2548 println!("{:?}", idl_account.authority);
2549
2550 Ok(())
2551 })
2552}
2553
2554fn idl_set_authority(
2555 cfg_override: &ConfigOverride,
2556 program_id: Pubkey,
2557 address: Option<Pubkey>,
2558 new_authority: Pubkey,
2559 print_only: bool,
2560 priority_fee: Option<u64>,
2561) -> Result<()> {
2562 with_workspace(cfg_override, |cfg| {
2563 let idl_address = match address {
2565 None => IdlAccount::address(&program_id),
2566 Some(addr) => addr,
2567 };
2568 let keypair = get_keypair(&cfg.provider.wallet.to_string())?;
2569 let url = cluster_url(cfg, &cfg.test_validator);
2570 let client = create_client(url);
2571
2572 let idl_authority = if print_only {
2573 get_idl_account(&client, &idl_address)?.authority
2574 } else {
2575 keypair.pubkey()
2576 };
2577
2578 let data =
2580 serialize_idl_ix(anchor_lang::idl::IdlInstruction::SetAuthority { new_authority })?;
2581
2582 let accounts = vec![
2584 AccountMeta::new(idl_address, false),
2585 AccountMeta::new_readonly(idl_authority, true),
2586 ];
2587
2588 let ix = Instruction {
2590 program_id,
2591 accounts,
2592 data,
2593 };
2594
2595 if print_only {
2596 print_idl_instruction("SetAuthority", &ix, &idl_address)?;
2597 } else {
2598 let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?;
2599
2600 let latest_hash = client.get_latest_blockhash()?;
2602 let tx = Transaction::new_signed_with_payer(
2603 &instructions,
2604 Some(&keypair.pubkey()),
2605 &[&keypair],
2606 latest_hash,
2607 );
2608 client.send_and_confirm_transaction_with_spinner(&tx)?;
2609
2610 println!("Authority update complete.");
2611 }
2612
2613 Ok(())
2614 })
2615}
2616
2617fn idl_erase_authority(
2618 cfg_override: &ConfigOverride,
2619 program_id: Pubkey,
2620 priority_fee: Option<u64>,
2621) -> Result<()> {
2622 println!("Are you sure you want to erase the IDL authority: [y/n]");
2623
2624 let stdin = std::io::stdin();
2625 let mut stdin_lines = stdin.lock().lines();
2626 let input = stdin_lines.next().unwrap().unwrap();
2627 if input != "y" {
2628 println!("Not erasing.");
2629 return Ok(());
2630 }
2631
2632 idl_set_authority(
2633 cfg_override,
2634 program_id,
2635 None,
2636 ERASED_AUTHORITY,
2637 false,
2638 priority_fee,
2639 )?;
2640
2641 Ok(())
2642}
2643
2644fn idl_close_account(
2645 cfg: &Config,
2646 program_id: &Pubkey,
2647 idl_address: Pubkey,
2648 print_only: bool,
2649 priority_fee: Option<u64>,
2650) -> Result<()> {
2651 let keypair = get_keypair(&cfg.provider.wallet.to_string())?;
2652 let url = cluster_url(cfg, &cfg.test_validator);
2653 let client = create_client(url);
2654
2655 let idl_authority = if print_only {
2656 get_idl_account(&client, &idl_address)?.authority
2657 } else {
2658 keypair.pubkey()
2659 };
2660 let accounts = vec![
2662 AccountMeta::new(idl_address, false),
2663 AccountMeta::new_readonly(idl_authority, true),
2664 AccountMeta::new(keypair.pubkey(), false),
2665 ];
2666 let ix = Instruction {
2668 program_id: *program_id,
2669 accounts,
2670 data: { serialize_idl_ix(anchor_lang::idl::IdlInstruction::Close {})? },
2671 };
2672
2673 if print_only {
2674 print_idl_instruction("Close", &ix, &idl_address)?;
2675 } else {
2676 let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?;
2677
2678 let latest_hash = client.get_latest_blockhash()?;
2680 let tx = Transaction::new_signed_with_payer(
2681 &instructions,
2682 Some(&keypair.pubkey()),
2683 &[&keypair],
2684 latest_hash,
2685 );
2686 client.send_and_confirm_transaction_with_spinner(&tx)?;
2687 }
2688
2689 Ok(())
2690}
2691
2692fn idl_write(
2696 cfg: &Config,
2697 program_id: &Pubkey,
2698 idl: &Idl,
2699 idl_address: Pubkey,
2700 priority_fee: Option<u64>,
2701) -> Result<()> {
2702 let keypair = get_keypair(&cfg.provider.wallet.to_string())?;
2704 let url = cluster_url(cfg, &cfg.test_validator);
2705 let client = create_client(url);
2706
2707 let idl_data = {
2709 let json_bytes = serde_json::to_vec(idl)?;
2710 let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
2711 e.write_all(&json_bytes)?;
2712 e.finish()?
2713 };
2714
2715 println!("Idl data length: {:?} bytes", idl_data.len());
2716
2717 const MAX_WRITE_SIZE: usize = 600;
2718 let mut offset = 0;
2719 while offset < idl_data.len() {
2720 println!("Step {offset}/{} ", idl_data.len());
2721 let data = {
2723 let start = offset;
2724 let end = std::cmp::min(offset + MAX_WRITE_SIZE, idl_data.len());
2725 serialize_idl_ix(anchor_lang::idl::IdlInstruction::Write {
2726 data: idl_data[start..end].to_vec(),
2727 })?
2728 };
2729 let accounts = vec![
2731 AccountMeta::new(idl_address, false),
2732 AccountMeta::new_readonly(keypair.pubkey(), true),
2733 ];
2734 let ix = Instruction {
2736 program_id: *program_id,
2737 accounts,
2738 data,
2739 };
2740 let instructions = prepend_compute_unit_ix(vec![ix], &client, priority_fee)?;
2742
2743 let mut latest_hash = client.get_latest_blockhash()?;
2744 for retries in 0..20 {
2745 if !client.is_blockhash_valid(&latest_hash, client.commitment())? {
2746 latest_hash = client.get_latest_blockhash()?;
2747 }
2748 let tx = Transaction::new_signed_with_payer(
2749 &instructions,
2750 Some(&keypair.pubkey()),
2751 &[&keypair],
2752 latest_hash,
2753 );
2754
2755 match client.send_and_confirm_transaction_with_spinner(&tx) {
2756 Ok(_) => break,
2757 Err(e) => {
2758 if retries == 19 {
2759 return Err(anyhow!("Error: {e}. Failed to send transaction."));
2760 }
2761 println!("Error: {e}. Retrying transaction.");
2762 }
2763 }
2764 }
2765
2766 offset += MAX_WRITE_SIZE;
2767 }
2768 Ok(())
2769}
2770
2771fn idl_build(
2772 cfg_override: &ConfigOverride,
2773 program_name: Option<String>,
2774 out: Option<String>,
2775 out_ts: Option<String>,
2776 no_docs: bool,
2777 skip_lint: bool,
2778 cargo_args: Vec<String>,
2779) -> Result<()> {
2780 let cfg = Config::discover(cfg_override)?.expect("Not in workspace");
2781 let current_dir = std::env::current_dir()?;
2782 let program_path = match program_name {
2783 Some(name) => cfg.get_program(&name)?.path,
2784 None => {
2785 let programs = cfg.read_all_programs()?;
2786 if programs.len() == 1 {
2787 programs.into_iter().next().unwrap().path
2788 } else {
2789 programs
2790 .into_iter()
2791 .find(|program| program.path == current_dir)
2792 .ok_or_else(|| anyhow!("Not in a program directory"))?
2793 .path
2794 }
2795 }
2796 };
2797 std::env::set_current_dir(program_path)?;
2798 let idl = generate_idl(&cfg, skip_lint, no_docs, &cargo_args)?;
2799 std::env::set_current_dir(current_dir)?;
2800
2801 let out = match out {
2802 Some(path) => OutFile::File(PathBuf::from(path)),
2803 None => OutFile::Stdout,
2804 };
2805 write_idl(&idl, out)?;
2806
2807 if let Some(path) = out_ts {
2808 fs::write(path, idl_ts(&idl)?)?;
2809 }
2810
2811 Ok(())
2812}
2813
2814fn generate_idl(
2816 cfg: &WithPath<Config>,
2817 skip_lint: bool,
2818 no_docs: bool,
2819 cargo_args: &[String],
2820) -> Result<Idl> {
2821 check_idl_build_feature()?;
2822
2823 anchor_lang_idl::build::IdlBuilder::new()
2824 .resolution(cfg.features.resolution)
2825 .skip_lint(cfg.features.skip_lint || skip_lint)
2826 .no_docs(no_docs)
2827 .cargo_args(cargo_args.into())
2828 .build()
2829}
2830
2831fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
2832 let idl = fetch_idl(cfg_override, address).map(|idl| serde_json::to_string_pretty(&idl))??;
2833 match out {
2834 Some(out) => fs::write(out, idl)?,
2835 _ => println!("{idl}"),
2836 };
2837
2838 Ok(())
2839}
2840
2841fn idl_convert(path: String, out: Option<String>, program_id: Option<Pubkey>) -> Result<()> {
2842 let idl = fs::read(path)?;
2843
2844 let idl = match program_id {
2846 Some(program_id) => {
2847 let mut idl = serde_json::from_slice::<serde_json::Value>(&idl)?;
2848 idl.as_object_mut()
2849 .ok_or_else(|| anyhow!("IDL must be an object"))?
2850 .insert(
2851 "metadata".into(),
2852 serde_json::json!({ "address": program_id.to_string() }),
2853 );
2854 serde_json::to_vec(&idl)?
2855 }
2856 _ => idl,
2857 };
2858
2859 let idl = convert_idl(&idl)?;
2860 let out = match out {
2861 None => OutFile::Stdout,
2862 Some(out) => OutFile::File(PathBuf::from(out)),
2863 };
2864 write_idl(&idl, out)
2865}
2866
2867fn idl_type(path: String, out: Option<String>) -> Result<()> {
2868 let idl = fs::read(path)?;
2869 let idl = convert_idl(&idl)?;
2870 let types = idl_ts(&idl)?;
2871 match out {
2872 Some(out) => fs::write(out, types)?,
2873 _ => println!("{types}"),
2874 };
2875 Ok(())
2876}
2877
2878fn idl_ts(idl: &Idl) -> Result<String> {
2879 let idl_name = &idl.metadata.name;
2880 let type_name = idl_name.to_pascal_case();
2881 let idl = serde_json::to_string(idl)?;
2882
2883 let camel_idl = Regex::new(r#""\w+":"([\w\d]+)""#)?
2885 .captures_iter(&idl)
2886 .fold(idl.clone(), |acc, cur| {
2887 let name = cur.get(1).unwrap().as_str();
2888
2889 if Pubkey::from_str(name).is_ok() {
2891 return acc;
2892 }
2893
2894 let camel_name = name.to_lower_camel_case();
2895 acc.replace(&format!(r#""{name}""#), &format!(r#""{camel_name}""#))
2896 });
2897
2898 let camel_idl = serde_json::to_string_pretty(&serde_json::from_str::<Idl>(&camel_idl)?)?;
2900
2901 Ok(format!(
2902 r#"/**
2903 * Program IDL in camelCase format in order to be used in JS/TS.
2904 *
2905 * Note that this is only a type helper and is not the actual IDL. The original
2906 * IDL can be found at `target/idl/{idl_name}.json`.
2907 */
2908export type {type_name} = {camel_idl};
2909"#
2910 ))
2911}
2912
2913fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
2914 let idl_json = serde_json::to_string_pretty(idl)?;
2915 match out {
2916 OutFile::Stdout => println!("{idl_json}"),
2917 OutFile::File(out) => fs::write(out, idl_json)?,
2918 };
2919
2920 Ok(())
2921}
2922
2923fn print_idl_instruction(ix_name: &str, ix: &Instruction, idl_address: &Pubkey) -> Result<()> {
2925 use base64::engine::general_purpose::STANDARD;
2926 use base64::Engine;
2927
2928 println!("Print only mode. No execution!");
2929 println!("Instruction: {ix_name}");
2930 println!("IDL address: {idl_address}");
2931 println!("Program: {}", ix.program_id);
2932
2933 let mut serialized_ix = bincode::serialize(ix)?;
2935
2936 let mut remove_extra_vec_bytes = |index: usize| {
2939 serialized_ix.drain((index + 4)..(index + 8));
2940 };
2941
2942 let accounts_index = std::mem::size_of_val(&ix.program_id);
2943 remove_extra_vec_bytes(accounts_index);
2944 let data_index = accounts_index + 4 + std::mem::size_of_val(&*ix.accounts);
2945 remove_extra_vec_bytes(data_index);
2946
2947 println!(
2948 "Base64 encoded instruction: {}",
2949 STANDARD.encode(serialized_ix)
2950 );
2951
2952 Ok(())
2953}
2954
2955fn account(
2956 cfg_override: &ConfigOverride,
2957 account_type: String,
2958 address: Pubkey,
2959 idl_filepath: Option<String>,
2960) -> Result<()> {
2961 let (program_name, account_type_name) = account_type
2962 .split_once('.') .and_then(|(x, y)| y.find('.').map_or_else(|| Some((x, y)), |_| None)) .ok_or_else(|| {
2965 anyhow!(
2966 "Please enter the account struct in the following format: <program_name>.<Account>",
2967 )
2968 })?;
2969
2970 let idl = idl_filepath.map_or_else(
2971 || {
2972 Config::discover(cfg_override)
2973 .expect("Error when detecting workspace.")
2974 .expect("Not in workspace.")
2975 .read_all_programs()
2976 .expect("Workspace must contain atleast one program.")
2977 .into_iter()
2978 .find(|p| p.lib_name == *program_name)
2979 .ok_or_else(|| anyhow!("Program {program_name} not found in workspace."))
2980 .map(|p| p.idl)?
2981 .ok_or_else(|| {
2982 anyhow!(
2983 "IDL not found. Please build the program atleast once to generate the IDL."
2984 )
2985 })
2986 },
2987 |idl_path| {
2988 let idl = fs::read(idl_path)?;
2989 let idl = convert_idl(&idl)?;
2990 if idl.metadata.name != program_name {
2991 return Err(anyhow!("IDL does not match program {program_name}."));
2992 }
2993
2994 Ok(idl)
2995 },
2996 )?;
2997
2998 let cluster = match &cfg_override.cluster {
2999 Some(cluster) => cluster.clone(),
3000 None => Config::discover(cfg_override)?
3001 .map(|cfg| cfg.provider.cluster.clone())
3002 .unwrap_or(Cluster::Localnet),
3003 };
3004
3005 let data = create_client(cluster.url()).get_account_data(&address)?;
3006 let disc_len = idl
3007 .accounts
3008 .iter()
3009 .find(|acc| acc.name == account_type_name)
3010 .map(|acc| acc.discriminator.len())
3011 .ok_or_else(|| anyhow!("Account `{account_type_name}` not found in IDL"))?;
3012 let mut data_view = &data[disc_len..];
3013
3014 let deserialized_json =
3015 deserialize_idl_defined_type_to_json(&idl, account_type_name, &mut data_view)?;
3016
3017 println!(
3018 "{}",
3019 serde_json::to_string_pretty(&deserialized_json).unwrap()
3020 );
3021
3022 Ok(())
3023}
3024
3025fn deserialize_idl_defined_type_to_json(
3027 idl: &Idl,
3028 defined_type_name: &str,
3029 data: &mut &[u8],
3030) -> Result<JsonValue, anyhow::Error> {
3031 let defined_type = &idl
3032 .accounts
3033 .iter()
3034 .find(|acc| acc.name == defined_type_name)
3035 .and_then(|acc| idl.types.iter().find(|ty| ty.name == acc.name))
3036 .or_else(|| idl.types.iter().find(|ty| ty.name == defined_type_name))
3037 .ok_or_else(|| anyhow!("Type `{}` not found in IDL.", defined_type_name))?
3038 .ty;
3039
3040 let mut deserialized_fields = Map::new();
3041
3042 match defined_type {
3043 IdlTypeDefTy::Struct { fields } => {
3044 if let Some(fields) = fields {
3045 match fields {
3046 IdlDefinedFields::Named(fields) => {
3047 for field in fields {
3048 deserialized_fields.insert(
3049 field.name.clone(),
3050 deserialize_idl_type_to_json(&field.ty, data, idl)?,
3051 );
3052 }
3053 }
3054 IdlDefinedFields::Tuple(fields) => {
3055 let mut values = Vec::new();
3056 for field in fields {
3057 values.push(deserialize_idl_type_to_json(field, data, idl)?);
3058 }
3059 deserialized_fields
3060 .insert(defined_type_name.to_owned(), JsonValue::Array(values));
3061 }
3062 }
3063 }
3064 }
3065 IdlTypeDefTy::Enum { variants } => {
3066 let repr = <u8 as AnchorDeserialize>::deserialize(data)?;
3067
3068 let variant = variants
3069 .get(repr as usize)
3070 .ok_or_else(|| anyhow!("Error while deserializing enum variant {repr}"))?;
3071
3072 let mut value = json!({});
3073
3074 if let Some(enum_field) = &variant.fields {
3075 match enum_field {
3076 IdlDefinedFields::Named(fields) => {
3077 let mut values = Map::new();
3078 for field in fields {
3079 values.insert(
3080 field.name.clone(),
3081 deserialize_idl_type_to_json(&field.ty, data, idl)?,
3082 );
3083 }
3084 value = JsonValue::Object(values);
3085 }
3086 IdlDefinedFields::Tuple(fields) => {
3087 let mut values = Vec::new();
3088 for field in fields {
3089 values.push(deserialize_idl_type_to_json(field, data, idl)?);
3090 }
3091 value = JsonValue::Array(values);
3092 }
3093 }
3094 }
3095
3096 deserialized_fields.insert(variant.name.clone(), value);
3097 }
3098 IdlTypeDefTy::Type { alias } => {
3099 return deserialize_idl_type_to_json(alias, data, idl);
3100 }
3101 }
3102
3103 Ok(JsonValue::Object(deserialized_fields))
3104}
3105
3106fn deserialize_idl_type_to_json(
3108 idl_type: &IdlType,
3109 data: &mut &[u8],
3110 parent_idl: &Idl,
3111) -> Result<JsonValue, anyhow::Error> {
3112 if data.is_empty() {
3113 return Err(anyhow::anyhow!("Unable to parse from empty bytes"));
3114 }
3115
3116 Ok(match idl_type {
3117 IdlType::Bool => json!(<bool as AnchorDeserialize>::deserialize(data)?),
3118 IdlType::U8 => {
3119 json!(<u8 as AnchorDeserialize>::deserialize(data)?)
3120 }
3121 IdlType::I8 => {
3122 json!(<i8 as AnchorDeserialize>::deserialize(data)?)
3123 }
3124 IdlType::U16 => {
3125 json!(<u16 as AnchorDeserialize>::deserialize(data)?)
3126 }
3127 IdlType::I16 => {
3128 json!(<i16 as AnchorDeserialize>::deserialize(data)?)
3129 }
3130 IdlType::U32 => {
3131 json!(<u32 as AnchorDeserialize>::deserialize(data)?)
3132 }
3133 IdlType::I32 => {
3134 json!(<i32 as AnchorDeserialize>::deserialize(data)?)
3135 }
3136 IdlType::F32 => json!(<f32 as AnchorDeserialize>::deserialize(data)?),
3137 IdlType::U64 => {
3138 json!(<u64 as AnchorDeserialize>::deserialize(data)?)
3139 }
3140 IdlType::I64 => {
3141 json!(<i64 as AnchorDeserialize>::deserialize(data)?)
3142 }
3143 IdlType::F64 => json!(<f64 as AnchorDeserialize>::deserialize(data)?),
3144 IdlType::U128 => {
3145 json!(<u128 as AnchorDeserialize>::deserialize(data)?.to_string())
3147 }
3148 IdlType::I128 => {
3149 json!(<i128 as AnchorDeserialize>::deserialize(data)?.to_string())
3151 }
3152 IdlType::U256 => todo!("Upon completion of u256 IDL standard"),
3153 IdlType::I256 => todo!("Upon completion of i256 IDL standard"),
3154 IdlType::Bytes => JsonValue::Array(
3155 <Vec<u8> as AnchorDeserialize>::deserialize(data)?
3156 .iter()
3157 .map(|i| json!(*i))
3158 .collect(),
3159 ),
3160 IdlType::String => json!(<String as AnchorDeserialize>::deserialize(data)?),
3161 IdlType::Pubkey => {
3162 json!(<Pubkey as AnchorDeserialize>::deserialize(data)?.to_string())
3163 }
3164 IdlType::Array(ty, size) => match size {
3165 IdlArrayLen::Value(size) => {
3166 let mut array_data: Vec<JsonValue> = Vec::with_capacity(*size);
3167
3168 for _ in 0..*size {
3169 array_data.push(deserialize_idl_type_to_json(ty, data, parent_idl)?);
3170 }
3171
3172 JsonValue::Array(array_data)
3173 }
3174 IdlArrayLen::Generic(_) => unimplemented!("Generic array length is not yet supported"),
3176 },
3177 IdlType::Option(ty) => {
3178 let is_present = <u8 as AnchorDeserialize>::deserialize(data)?;
3179
3180 if is_present == 0 {
3181 JsonValue::String("None".to_string())
3182 } else {
3183 deserialize_idl_type_to_json(ty, data, parent_idl)?
3184 }
3185 }
3186 IdlType::Vec(ty) => {
3187 let size: usize = <u32 as AnchorDeserialize>::deserialize(data)?
3188 .try_into()
3189 .unwrap();
3190
3191 let mut vec_data: Vec<JsonValue> = Vec::with_capacity(size);
3192
3193 for _ in 0..size {
3194 vec_data.push(deserialize_idl_type_to_json(ty, data, parent_idl)?);
3195 }
3196
3197 JsonValue::Array(vec_data)
3198 }
3199 IdlType::Defined {
3200 name,
3201 generics: _generics,
3202 } => {
3203 deserialize_idl_defined_type_to_json(parent_idl, name, data)?
3205 }
3206 IdlType::Generic(generic) => json!(generic),
3207 _ => unimplemented!("{idl_type:?}"),
3208 })
3209}
3210
3211enum OutFile {
3212 Stdout,
3213 File(PathBuf),
3214}
3215
3216#[allow(clippy::too_many_arguments)]
3218fn test(
3219 cfg_override: &ConfigOverride,
3220 program_name: Option<String>,
3221 skip_deploy: bool,
3222 skip_local_validator: bool,
3223 skip_build: bool,
3224 skip_lint: bool,
3225 no_idl: bool,
3226 detach: bool,
3227 tests_to_run: Vec<String>,
3228 extra_args: Vec<String>,
3229 env_vars: Vec<String>,
3230 cargo_args: Vec<String>,
3231 arch: ProgramArch,
3232) -> Result<()> {
3233 let test_paths = tests_to_run
3234 .iter()
3235 .map(|path| {
3236 PathBuf::from(path)
3237 .canonicalize()
3238 .map_err(|_| anyhow!("Wrong path {}", path))
3239 })
3240 .collect::<Result<Vec<_>, _>>()?;
3241
3242 with_workspace(cfg_override, |cfg| {
3243 if !skip_build {
3245 build(
3246 cfg_override,
3247 no_idl,
3248 None,
3249 None,
3250 false,
3251 skip_lint,
3252 program_name.clone(),
3253 None,
3254 None,
3255 BootstrapMode::None,
3256 None,
3257 None,
3258 env_vars,
3259 cargo_args,
3260 false,
3261 arch,
3262 )?;
3263 }
3264
3265 let root = cfg.path().parent().unwrap().to_owned();
3266 cfg.add_test_config(root, test_paths)?;
3267
3268 let is_localnet = cfg.provider.cluster == Cluster::Localnet;
3275 if (!is_localnet || skip_local_validator) && !skip_deploy {
3276 deploy(cfg_override, None, None, false, vec![])?;
3277 }
3278 let mut is_first_suite = true;
3279 if let Some(test_script) = cfg.scripts.get_mut("test") {
3280 is_first_suite = false;
3281
3282 match program_name {
3283 Some(program_name) => {
3284 if let Some((from, to)) = Regex::new("\\s(tests/\\S+\\.(js|ts))")
3285 .unwrap()
3286 .captures_iter(&test_script.clone())
3287 .last()
3288 .and_then(|c| c.get(1).and_then(|mtch| c.get(2).map(|ext| (mtch, ext))))
3289 .map(|(mtch, ext)| {
3290 (
3291 mtch.as_str(),
3292 format!("tests/{program_name}.{}", ext.as_str()),
3293 )
3294 })
3295 {
3296 println!("\nRunning tests of program `{program_name}`!");
3297 *test_script = test_script.replace(from, &to);
3299 }
3300 }
3301 _ => println!(
3302 "\nFound a 'test' script in the Anchor.toml. Running it as a test suite!"
3303 ),
3304 }
3305
3306 run_test_suite(
3307 cfg,
3308 cfg.path(),
3309 is_localnet,
3310 skip_local_validator,
3311 skip_deploy,
3312 detach,
3313 &cfg.test_validator,
3314 &cfg.scripts,
3315 &extra_args,
3316 )?;
3317 }
3318 if let Some(test_config) = &cfg.test_config {
3319 for test_suite in test_config.iter() {
3320 if !is_first_suite {
3321 std::thread::sleep(std::time::Duration::from_millis(
3322 test_suite
3323 .1
3324 .test
3325 .as_ref()
3326 .map(|val| val.shutdown_wait)
3327 .unwrap_or(SHUTDOWN_WAIT) as u64,
3328 ));
3329 } else {
3330 is_first_suite = false;
3331 }
3332
3333 run_test_suite(
3334 cfg,
3335 test_suite.0,
3336 is_localnet,
3337 skip_local_validator,
3338 skip_deploy,
3339 detach,
3340 &test_suite.1.test,
3341 &test_suite.1.scripts,
3342 &extra_args,
3343 )?;
3344 }
3345 }
3346 Ok(())
3347 })
3348}
3349
3350#[allow(clippy::too_many_arguments)]
3351fn run_test_suite(
3352 cfg: &WithPath<Config>,
3353 test_suite_path: impl AsRef<Path>,
3354 is_localnet: bool,
3355 skip_local_validator: bool,
3356 skip_deploy: bool,
3357 detach: bool,
3358 test_validator: &Option<TestValidator>,
3359 scripts: &ScriptsConfig,
3360 extra_args: &[String],
3361) -> Result<()> {
3362 println!("\nRunning test suite: {:#?}\n", test_suite_path.as_ref());
3363 let mut validator_handle = None;
3365 if is_localnet && (!skip_local_validator) {
3366 let flags = match skip_deploy {
3367 true => None,
3368 false => Some(validator_flags(cfg, test_validator)?),
3369 };
3370 validator_handle = Some(start_test_validator(cfg, test_validator, flags, true)?);
3371 }
3372
3373 let url = cluster_url(cfg, test_validator);
3374
3375 let node_options = format!(
3376 "{} {}",
3377 match std::env::var_os("NODE_OPTIONS") {
3378 Some(value) => value
3379 .into_string()
3380 .map_err(std::env::VarError::NotUnicode)?,
3381 None => "".to_owned(),
3382 },
3383 get_node_dns_option()?,
3384 );
3385
3386 let log_streams = stream_logs(cfg, &url);
3388
3389 let test_result = {
3391 let cmd = scripts
3392 .get("test")
3393 .expect("Not able to find script for `test`")
3394 .clone();
3395 let script_args = format!("{cmd} {}", extra_args.join(" "));
3396 std::process::Command::new("bash")
3397 .arg("-c")
3398 .arg(script_args)
3399 .env("ANCHOR_PROVIDER_URL", url)
3400 .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
3401 .env("NODE_OPTIONS", node_options)
3402 .stdout(Stdio::inherit())
3403 .stderr(Stdio::inherit())
3404 .output()
3405 .map_err(anyhow::Error::from)
3406 .context(cmd)
3407 };
3408
3409 if test_result.is_ok() && detach {
3411 println!("Local validator still running. Press Ctrl + C quit.");
3412 std::io::stdin().lock().lines().next().unwrap().unwrap();
3413 }
3414
3415 if let Some(mut child) = validator_handle {
3417 if let Err(err) = child.kill() {
3418 println!("Failed to kill subprocess {}: {}", child.id(), err);
3419 }
3420 }
3421 for mut child in log_streams? {
3422 if let Err(err) = child.kill() {
3423 println!("Failed to kill subprocess {}: {}", child.id(), err);
3424 }
3425 }
3426
3427 match test_result {
3429 Ok(exit) => {
3430 if !exit.status.success() {
3431 std::process::exit(exit.status.code().unwrap());
3432 }
3433 }
3434 Err(err) => {
3435 println!("Failed to run test: {err:#}");
3436 return Err(err);
3437 }
3438 }
3439
3440 Ok(())
3441}
3442
3443fn validator_flags(
3447 cfg: &WithPath<Config>,
3448 test_validator: &Option<TestValidator>,
3449) -> Result<Vec<String>> {
3450 let programs = cfg.programs.get(&Cluster::Localnet);
3451
3452 let test_upgradeable_program = test_validator
3453 .as_ref()
3454 .map(|test_validator| test_validator.upgradeable)
3455 .unwrap_or(false);
3456
3457 let mut flags = Vec::new();
3458 for mut program in cfg.read_all_programs()? {
3459 let verifiable = false;
3460 let binary_path = program.binary_path(verifiable).display().to_string();
3461
3462 let address = programs
3465 .and_then(|m| m.get(&program.lib_name))
3466 .map(|deployment| Ok(deployment.address.to_string()))
3467 .unwrap_or_else(|| program.pubkey().map(|p| p.to_string()))?;
3468
3469 if test_upgradeable_program {
3470 flags.push("--upgradeable-program".to_string());
3471 flags.push(address.clone());
3472 flags.push(binary_path);
3473 flags.push(cfg.wallet_kp()?.pubkey().to_string());
3474 } else {
3475 flags.push("--bpf-program".to_string());
3476 flags.push(address.clone());
3477 flags.push(binary_path);
3478 }
3479
3480 if let Some(idl) = program.idl.as_mut() {
3481 idl.address = address;
3483
3484 let idl_out = Path::new("target")
3486 .join("idl")
3487 .join(&idl.metadata.name)
3488 .with_extension("json");
3489 write_idl(idl, OutFile::File(idl_out))?;
3490 }
3491 }
3492
3493 if let Some(test) = test_validator.as_ref() {
3494 if let Some(genesis) = &test.genesis {
3495 for entry in genesis {
3496 let program_path = Path::new(&entry.program);
3497 if !program_path.exists() {
3498 return Err(anyhow!(
3499 "Program in genesis configuration does not exist at path: {}",
3500 program_path.display()
3501 ));
3502 }
3503 if entry.upgradeable.unwrap_or(false) {
3504 flags.push("--upgradeable-program".to_string());
3505 flags.push(entry.address.clone());
3506 flags.push(entry.program.clone());
3507 flags.push(cfg.wallet_kp()?.pubkey().to_string());
3508 } else {
3509 flags.push("--bpf-program".to_string());
3510 flags.push(entry.address.clone());
3511 flags.push(entry.program.clone());
3512 }
3513 }
3514 }
3515 if let Some(validator) = &test.validator {
3516 let entries = serde_json::to_value(validator)?;
3517 for (key, value) in entries.as_object().unwrap() {
3518 if key == "ledger" {
3519 continue;
3522 };
3523 if key == "account" {
3524 for entry in value.as_array().unwrap() {
3525 flags.push("--account".to_string());
3527 flags.push(entry["address"].as_str().unwrap().to_string());
3528 flags.push(entry["filename"].as_str().unwrap().to_string());
3529 }
3530 } else if key == "account_dir" {
3531 for entry in value.as_array().unwrap() {
3532 flags.push("--account-dir".to_string());
3533 flags.push(entry["directory"].as_str().unwrap().to_string());
3534 }
3535 } else if key == "clone" {
3536 let client = if let Some(url) = entries["url"].as_str() {
3538 create_client(url)
3539 } else {
3540 return Err(anyhow!(
3541 "Validator url for Solana's JSON RPC should be provided in order to clone accounts from it"
3542 ));
3543 };
3544
3545 let pubkeys = value
3546 .as_array()
3547 .unwrap()
3548 .iter()
3549 .map(|entry| {
3550 let address = entry["address"].as_str().unwrap();
3551 Pubkey::from_str(address)
3552 .map_err(|_| anyhow!("Invalid pubkey {}", address))
3553 })
3554 .collect::<Result<HashSet<Pubkey>>>()?
3555 .into_iter()
3556 .collect::<Vec<_>>();
3557 let accounts = client.get_multiple_accounts(&pubkeys)?;
3558
3559 for (pubkey, account) in pubkeys.into_iter().zip(accounts) {
3560 match account {
3561 Some(account) => {
3562 if account.owner == bpf_loader_upgradeable::id()
3565 && matches!(
3567 account.deserialize_data::<UpgradeableLoaderState>()?,
3568 UpgradeableLoaderState::Program { .. }
3569 )
3570 {
3571 flags.push("--clone-upgradeable-program".to_string());
3572 flags.push(pubkey.to_string());
3573 } else {
3574 flags.push("--clone".to_string());
3575 flags.push(pubkey.to_string());
3576 }
3577 }
3578 _ => return Err(anyhow!("Account {} not found", pubkey)),
3579 }
3580 }
3581 } else if key == "deactivate_feature" {
3582 let pubkeys_result: Result<Vec<Pubkey>, _> = value
3584 .as_array()
3585 .unwrap()
3586 .iter()
3587 .map(|entry| {
3588 let feature_flag = entry.as_str().unwrap();
3589 Pubkey::from_str(feature_flag).map_err(|_| {
3590 anyhow!("Invalid pubkey (feature flag) {}", feature_flag)
3591 })
3592 })
3593 .collect();
3594 let features = pubkeys_result?;
3595 for feature in features {
3596 flags.push("--deactivate-feature".to_string());
3597 flags.push(feature.to_string());
3598 }
3599 } else {
3600 flags.push(format!("--{}", key.replace('_', "-")));
3602 if let serde_json::Value::String(v) = value {
3603 flags.push(v.to_string());
3604 } else {
3605 flags.push(value.to_string());
3606 }
3607 }
3608 }
3609 }
3610 }
3611
3612 Ok(flags)
3613}
3614
3615fn stream_logs(config: &WithPath<Config>, rpc_url: &str) -> Result<Vec<std::process::Child>> {
3616 let program_logs_dir = Path::new(".anchor").join("program-logs");
3617 if program_logs_dir.exists() {
3618 fs::remove_dir_all(&program_logs_dir)?;
3619 }
3620 fs::create_dir_all(&program_logs_dir)?;
3621
3622 let mut handles = vec![];
3623 for program in config.read_all_programs()? {
3624 let idl_path = Path::new("target")
3625 .join("idl")
3626 .join(&program.lib_name)
3627 .with_extension("json");
3628 let idl = fs::read(idl_path)?;
3629 let idl = convert_idl(&idl)?;
3630
3631 let log_file = File::create(
3632 program_logs_dir.join(format!("{}.{}.log", idl.address, program.lib_name)),
3633 )?;
3634 let stdio = std::process::Stdio::from(log_file);
3635 let child = std::process::Command::new("solana")
3636 .arg("logs")
3637 .arg(idl.address)
3638 .arg("--url")
3639 .arg(rpc_url)
3640 .stdout(stdio)
3641 .spawn()?;
3642 handles.push(child);
3643 }
3644 if let Some(test) = config.test_validator.as_ref() {
3645 if let Some(genesis) = &test.genesis {
3646 for entry in genesis {
3647 let log_file =
3648 File::create(program_logs_dir.join(&entry.address).with_extension("log"))?;
3649 let stdio = std::process::Stdio::from(log_file);
3650 let child = std::process::Command::new("solana")
3651 .arg("logs")
3652 .arg(entry.address.clone())
3653 .arg("--url")
3654 .arg(rpc_url)
3655 .stdout(stdio)
3656 .spawn()?;
3657 handles.push(child);
3658 }
3659 }
3660 }
3661
3662 Ok(handles)
3663}
3664
3665fn start_test_validator(
3666 cfg: &Config,
3667 test_validator: &Option<TestValidator>,
3668 flags: Option<Vec<String>>,
3669 test_log_stdout: bool,
3670) -> Result<Child> {
3671 let (test_ledger_directory, test_ledger_log_filename) =
3672 test_validator_file_paths(test_validator)?;
3673
3674 let (test_validator_stdout, test_validator_stderr) = match test_log_stdout {
3676 true => {
3677 let test_validator_stdout_file = File::create(&test_ledger_log_filename)?;
3678 let test_validator_sterr_file = test_validator_stdout_file.try_clone()?;
3679 (
3680 Stdio::from(test_validator_stdout_file),
3681 Stdio::from(test_validator_sterr_file),
3682 )
3683 }
3684 false => (Stdio::inherit(), Stdio::inherit()),
3685 };
3686
3687 let rpc_url = test_validator_rpc_url(test_validator);
3688
3689 let rpc_port = cfg
3690 .test_validator
3691 .as_ref()
3692 .and_then(|test| test.validator.as_ref().map(|v| v.rpc_port))
3693 .unwrap_or(solana_sdk::rpc_port::DEFAULT_RPC_PORT);
3694 if !portpicker::is_free(rpc_port) {
3695 return Err(anyhow!(
3696 "Your configured rpc port: {rpc_port} is already in use"
3697 ));
3698 }
3699 let faucet_port = cfg
3700 .test_validator
3701 .as_ref()
3702 .and_then(|test| test.validator.as_ref().and_then(|v| v.faucet_port))
3703 .unwrap_or(solana_faucet::faucet::FAUCET_PORT);
3704 if !portpicker::is_free(faucet_port) {
3705 return Err(anyhow!(
3706 "Your configured faucet port: {faucet_port} is already in use"
3707 ));
3708 }
3709
3710 let mut validator_handle = std::process::Command::new("solana-test-validator")
3711 .arg("--ledger")
3712 .arg(test_ledger_directory)
3713 .arg("--mint")
3714 .arg(cfg.wallet_kp()?.pubkey().to_string())
3715 .args(flags.unwrap_or_default())
3716 .stdout(test_validator_stdout)
3717 .stderr(test_validator_stderr)
3718 .spawn()
3719 .map_err(|e| anyhow!("Failed to spawn `solana-test-validator`: {e}"))?;
3720
3721 let client = create_client(rpc_url);
3723 let mut count = 0;
3724 let ms_wait = test_validator
3725 .as_ref()
3726 .map(|test| test.startup_wait)
3727 .unwrap_or(STARTUP_WAIT);
3728 while count < ms_wait {
3729 let r = client.get_latest_blockhash();
3730 if r.is_ok() {
3731 break;
3732 }
3733 std::thread::sleep(std::time::Duration::from_millis(100));
3734 count += 100;
3735 }
3736 if count >= ms_wait {
3737 eprintln!(
3738 "Unable to get latest blockhash. Test validator does not look started. \
3739 Check {test_ledger_log_filename:?} for errors. Consider increasing [test.startup_wait] in Anchor.toml."
3740 );
3741 validator_handle.kill()?;
3742 std::process::exit(1);
3743 }
3744 Ok(validator_handle)
3745}
3746
3747fn test_validator_rpc_url(test_validator: &Option<TestValidator>) -> String {
3750 match test_validator {
3751 Some(TestValidator {
3752 validator: Some(validator),
3753 ..
3754 }) => format!("http://{}:{}", validator.bind_address, validator.rpc_port),
3755 _ => "http://127.0.0.1:8899".to_string(),
3756 }
3757}
3758
3759fn test_validator_file_paths(test_validator: &Option<TestValidator>) -> Result<(PathBuf, PathBuf)> {
3762 let ledger_path = match test_validator {
3763 Some(TestValidator {
3764 validator: Some(validator),
3765 ..
3766 }) => PathBuf::from(&validator.ledger),
3767 _ => get_default_ledger_path(),
3768 };
3769
3770 if !ledger_path.is_relative() {
3771 eprintln!("Ledger directory {ledger_path:?} must be relative");
3774 std::process::exit(1);
3775 }
3776 if ledger_path.exists() {
3777 fs::remove_dir_all(&ledger_path)?;
3778 }
3779
3780 fs::create_dir_all(&ledger_path)?;
3781
3782 let log_path = ledger_path.join("test-ledger-log.txt");
3783 Ok((ledger_path, log_path))
3784}
3785
3786fn cluster_url(cfg: &Config, test_validator: &Option<TestValidator>) -> String {
3787 let is_localnet = cfg.provider.cluster == Cluster::Localnet;
3788 match is_localnet {
3789 true => test_validator_rpc_url(test_validator),
3792 false => cfg.provider.cluster.url().to_string(),
3793 }
3794}
3795
3796fn clean(cfg_override: &ConfigOverride) -> Result<()> {
3797 let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
3798 let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
3799 let dot_anchor_dir = cfg_parent.join(".anchor");
3800 let target_dir = cfg_parent.join("target");
3801 let deploy_dir = target_dir.join("deploy");
3802
3803 if dot_anchor_dir.exists() {
3804 fs::remove_dir_all(&dot_anchor_dir)
3805 .map_err(|e| anyhow!("Could not remove directory {:?}: {}", dot_anchor_dir, e))?;
3806 }
3807
3808 if target_dir.exists() {
3809 for entry in fs::read_dir(target_dir)? {
3810 let path = entry?.path();
3811 if path.is_dir() && path != deploy_dir {
3812 fs::remove_dir_all(&path)
3813 .map_err(|e| anyhow!("Could not remove directory {}: {}", path.display(), e))?;
3814 } else if path.is_file() {
3815 fs::remove_file(&path)
3816 .map_err(|e| anyhow!("Could not remove file {}: {}", path.display(), e))?;
3817 }
3818 }
3819 } else {
3820 println!("skipping target directory: not found")
3821 }
3822
3823 if deploy_dir.exists() {
3824 for file in fs::read_dir(deploy_dir)? {
3825 let path = file?.path();
3826 if path.extension() != Some(&OsString::from("json")) {
3827 fs::remove_file(&path)
3828 .map_err(|e| anyhow!("Could not remove file {}: {}", path.display(), e))?;
3829 }
3830 }
3831 } else {
3832 println!("skipping deploy directory: not found")
3833 }
3834
3835 Ok(())
3836}
3837
3838fn deploy(
3839 cfg_override: &ConfigOverride,
3840 program_name: Option<String>,
3841 program_keypair: Option<String>,
3842 verifiable: bool,
3843 solana_args: Vec<String>,
3844) -> Result<()> {
3845 with_workspace(cfg_override, |cfg| {
3847 let url = cluster_url(cfg, &cfg.test_validator);
3848 let keypair = cfg.provider.wallet.to_string();
3849
3850 let client = create_client(&url);
3852 let solana_args = add_recommended_deployment_solana_args(&client, solana_args)?;
3853
3854 println!("Deploying cluster: {}", url);
3856 println!("Upgrade authority: {}", keypair);
3857
3858 for mut program in cfg.get_programs(program_name)? {
3859 let binary_path = program.binary_path(verifiable).display().to_string();
3860
3861 println!("Deploying program {:?}...", program.lib_name);
3862 println!("Program path: {}...", binary_path);
3863
3864 let (program_keypair_filepath, program_id) = match &program_keypair {
3865 Some(path) => (path.clone(), get_keypair(path)?.pubkey()),
3866 None => (
3867 program.keypair_file()?.path().display().to_string(),
3868 program.pubkey()?,
3869 ),
3870 };
3871
3872 let exit = std::process::Command::new("solana")
3874 .arg("program")
3875 .arg("deploy")
3876 .arg("--url")
3877 .arg(&url)
3878 .arg("--keypair")
3879 .arg(&keypair)
3880 .arg("--program-id")
3881 .arg(strip_workspace_prefix(program_keypair_filepath))
3882 .arg(strip_workspace_prefix(binary_path))
3883 .args(&solana_args)
3884 .stdout(Stdio::inherit())
3885 .stderr(Stdio::inherit())
3886 .output()
3887 .expect("Must deploy");
3888
3889 if !exit.status.success() {
3891 println!("There was a problem deploying: {exit:?}.");
3892 std::process::exit(exit.status.code().unwrap_or(1));
3893 }
3894
3895 if let Some(idl) = program.idl.as_mut() {
3896 idl.address = program_id.to_string();
3898
3899 let idl_out = Path::new("target")
3901 .join("idl")
3902 .join(&idl.metadata.name)
3903 .with_extension("json");
3904 write_idl(idl, OutFile::File(idl_out))?;
3905 }
3906 }
3907
3908 println!("Deploy success");
3909
3910 Ok(())
3911 })
3912}
3913
3914fn upgrade(
3915 cfg_override: &ConfigOverride,
3916 program_id: Pubkey,
3917 program_filepath: String,
3918 max_retries: u32,
3919 solana_args: Vec<String>,
3920) -> Result<()> {
3921 let path: PathBuf = program_filepath.parse().unwrap();
3922 let program_filepath = path.canonicalize()?.display().to_string();
3923
3924 with_workspace(cfg_override, |cfg| {
3925 let url = cluster_url(cfg, &cfg.test_validator);
3926 let client = create_client(&url);
3927 let solana_args = add_recommended_deployment_solana_args(&client, solana_args)?;
3928
3929 for retry in 0..(1 + max_retries) {
3930 let exit = std::process::Command::new("solana")
3931 .arg("program")
3932 .arg("deploy")
3933 .arg("--url")
3934 .arg(url.clone())
3935 .arg("--keypair")
3936 .arg(cfg.provider.wallet.to_string())
3937 .arg("--program-id")
3938 .arg(program_id.to_string())
3939 .arg(strip_workspace_prefix(program_filepath.clone()))
3940 .args(&solana_args)
3941 .stdout(Stdio::inherit())
3942 .stderr(Stdio::inherit())
3943 .output()
3944 .expect("Must deploy");
3945 if exit.status.success() {
3946 break;
3947 }
3948
3949 println!("There was a problem deploying: {exit:?}.");
3950 if retry < max_retries {
3951 println!("Retrying {} more time(s)...", max_retries - retry);
3952 } else {
3953 std::process::exit(exit.status.code().unwrap_or(1));
3954 }
3955 }
3956 Ok(())
3957 })
3958}
3959
3960fn create_idl_account(
3961 cfg: &Config,
3962 keypair_path: &str,
3963 program_id: &Pubkey,
3964 idl: &Idl,
3965 priority_fee: Option<u64>,
3966) -> Result<Pubkey> {
3967 let idl_address = IdlAccount::address(program_id);
3969 let keypair = get_keypair(keypair_path)?;
3970 let url = cluster_url(cfg, &cfg.test_validator);
3971 let client = create_client(url);
3972 let idl_data = serialize_idl(idl)?;
3973
3974 {
3976 let pda_max_growth = 60_000;
3977 let idl_header_size = 44;
3978 let idl_data_len = idl_data.len() as u64;
3979 if idl_data_len > pda_max_growth {
3982 return Err(anyhow!(
3983 "Your IDL is over 60kb and this isn't supported right now"
3984 ));
3985 }
3986 let data_len = (idl_data_len * 2).min(pda_max_growth - idl_header_size);
3988
3989 let num_additional_instructions = data_len / 10000;
3990 let mut instructions = Vec::new();
3991 let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Create { data_len })?;
3992 let program_signer = Pubkey::find_program_address(&[], program_id).0;
3993 let accounts = vec![
3994 AccountMeta::new_readonly(keypair.pubkey(), true),
3995 AccountMeta::new(idl_address, false),
3996 AccountMeta::new_readonly(program_signer, false),
3997 AccountMeta::new_readonly(solana_sdk::system_program::ID, false),
3998 AccountMeta::new_readonly(*program_id, false),
3999 ];
4000 instructions.push(Instruction {
4001 program_id: *program_id,
4002 accounts,
4003 data,
4004 });
4005
4006 for _ in 0..num_additional_instructions {
4007 let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Resize { data_len })?;
4008 instructions.push(Instruction {
4009 program_id: *program_id,
4010 accounts: vec![
4011 AccountMeta::new(idl_address, false),
4012 AccountMeta::new_readonly(keypair.pubkey(), true),
4013 AccountMeta::new_readonly(solana_sdk::system_program::ID, false),
4014 ],
4015 data,
4016 });
4017 }
4018 instructions = prepend_compute_unit_ix(instructions, &client, priority_fee)?;
4019
4020 let mut latest_hash = client.get_latest_blockhash()?;
4021 for retries in 0..20 {
4022 if !client.is_blockhash_valid(&latest_hash, client.commitment())? {
4023 latest_hash = client.get_latest_blockhash()?;
4024 }
4025
4026 let tx = Transaction::new_signed_with_payer(
4027 &instructions,
4028 Some(&keypair.pubkey()),
4029 &[&keypair],
4030 latest_hash,
4031 );
4032
4033 match client.send_and_confirm_transaction_with_spinner(&tx) {
4034 Ok(_) => break,
4035 Err(err) => {
4036 if retries == 19 {
4037 return Err(anyhow!("Error creating IDL account: {}", err));
4038 }
4039 println!("Error creating IDL account: {}. Retrying...", err);
4040 }
4041 }
4042 }
4043 }
4044
4045 idl_write(
4047 cfg,
4048 program_id,
4049 idl,
4050 IdlAccount::address(program_id),
4051 priority_fee,
4052 )?;
4053
4054 Ok(idl_address)
4055}
4056
4057fn create_idl_buffer(
4058 cfg: &Config,
4059 keypair_path: &str,
4060 program_id: &Pubkey,
4061 idl: &Idl,
4062 priority_fee: Option<u64>,
4063) -> Result<Pubkey> {
4064 let keypair = get_keypair(keypair_path)?;
4065 let url = cluster_url(cfg, &cfg.test_validator);
4066 let client = create_client(url);
4067
4068 let buffer = Keypair::new();
4069
4070 let create_account_ix = {
4072 let space = IdlAccount::DISCRIMINATOR.len() + 32 + 4 + serialize_idl(idl)?.len();
4073 let lamports = client.get_minimum_balance_for_rent_exemption(space)?;
4074 solana_sdk::system_instruction::create_account(
4075 &keypair.pubkey(),
4076 &buffer.pubkey(),
4077 lamports,
4078 space as u64,
4079 program_id,
4080 )
4081 };
4082
4083 let create_buffer_ix = {
4085 let accounts = vec![
4086 AccountMeta::new(buffer.pubkey(), false),
4087 AccountMeta::new_readonly(keypair.pubkey(), true),
4088 ];
4089 let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
4090 data.append(&mut IdlInstruction::CreateBuffer.try_to_vec()?);
4091 Instruction {
4092 program_id: *program_id,
4093 accounts,
4094 data,
4095 }
4096 };
4097
4098 let instructions = prepend_compute_unit_ix(
4099 vec![create_account_ix, create_buffer_ix],
4100 &client,
4101 priority_fee,
4102 )?;
4103
4104 let mut latest_hash = client.get_latest_blockhash()?;
4105 for retries in 0..20 {
4106 if !client.is_blockhash_valid(&latest_hash, client.commitment())? {
4107 latest_hash = client.get_latest_blockhash()?;
4108 }
4109 let tx = Transaction::new_signed_with_payer(
4110 &instructions,
4111 Some(&keypair.pubkey()),
4112 &[&keypair, &buffer],
4113 latest_hash,
4114 );
4115 match client.send_and_confirm_transaction_with_spinner(&tx) {
4116 Ok(_) => break,
4117 Err(err) => {
4118 if retries == 19 {
4119 return Err(anyhow!("Error creating buffer account: {}", err));
4120 }
4121 println!("Error creating buffer account: {}. Retrying...", err);
4122 }
4123 }
4124 }
4125
4126 Ok(buffer.pubkey())
4127}
4128
4129fn serialize_idl(idl: &Idl) -> Result<Vec<u8>> {
4131 let json_bytes = serde_json::to_vec(idl)?;
4132 let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
4133 e.write_all(&json_bytes)?;
4134 e.finish().map_err(Into::into)
4135}
4136
4137fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8>> {
4138 let mut data = Vec::with_capacity(256);
4139 data.extend_from_slice(&anchor_lang::idl::IDL_IX_TAG.to_le_bytes());
4140 ix_inner.serialize(&mut data)?;
4141 Ok(data)
4142}
4143
4144fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
4145 with_workspace(cfg_override, |cfg| {
4146 println!("Running migration deploy script");
4147
4148 let url = cluster_url(cfg, &cfg.test_validator);
4149 let cur_dir = std::env::current_dir()?;
4150 let migrations_dir = cur_dir.join("migrations");
4151 let deploy_ts = Path::new("deploy.ts");
4152
4153 let use_ts = Path::new("tsconfig.json").exists() && migrations_dir.join(deploy_ts).exists();
4154
4155 if !Path::new(".anchor").exists() {
4156 fs::create_dir(".anchor")?;
4157 }
4158 std::env::set_current_dir(".anchor")?;
4159
4160 let exit = if use_ts {
4161 let module_path = migrations_dir.join(deploy_ts);
4162 let deploy_script_host_str =
4163 rust_template::deploy_ts_script_host(&url, &module_path.display().to_string());
4164 fs::write(deploy_ts, deploy_script_host_str)?;
4165
4166 let pkg_manager_cmd = match &cfg.toolchain.package_manager {
4167 Some(pkg_manager) => pkg_manager.to_string(),
4168 None => PackageManager::default().to_string(),
4169 };
4170
4171 std::process::Command::new(pkg_manager_cmd)
4172 .args([
4173 "run",
4174 "ts-node",
4175 &fs::canonicalize(deploy_ts)?.to_string_lossy(),
4176 ])
4177 .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
4178 .stdout(Stdio::inherit())
4179 .stderr(Stdio::inherit())
4180 .output()?
4181 } else {
4182 let deploy_js = deploy_ts.with_extension("js");
4183 let module_path = migrations_dir.join(&deploy_js);
4184 let deploy_script_host_str =
4185 rust_template::deploy_js_script_host(&url, &module_path.display().to_string());
4186 fs::write(&deploy_js, deploy_script_host_str)?;
4187
4188 std::process::Command::new("node")
4189 .arg(&deploy_js)
4190 .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
4191 .stdout(Stdio::inherit())
4192 .stderr(Stdio::inherit())
4193 .output()?
4194 };
4195
4196 if !exit.status.success() {
4197 eprintln!("Deploy failed.");
4198 std::process::exit(exit.status.code().unwrap());
4199 }
4200
4201 println!("Deploy complete.");
4202 Ok(())
4203 })
4204}
4205
4206fn set_workspace_dir_or_exit() {
4207 let d = match Config::discover(&ConfigOverride::default()) {
4208 Err(err) => {
4209 println!("Workspace configuration error: {err}");
4210 std::process::exit(1);
4211 }
4212 Ok(d) => d,
4213 };
4214 match d {
4215 None => {
4216 println!("Not in anchor workspace.");
4217 std::process::exit(1);
4218 }
4219 Some(cfg) => {
4220 match cfg.path().parent() {
4221 None => {
4222 println!("Unable to make new program");
4223 }
4224 Some(parent) => {
4225 if std::env::set_current_dir(parent).is_err() {
4226 println!("Not in anchor workspace.");
4227 std::process::exit(1);
4228 }
4229 }
4230 };
4231 }
4232 }
4233}
4234
4235#[cfg(feature = "dev")]
4236fn airdrop(cfg_override: &ConfigOverride) -> Result<()> {
4237 let url = cfg_override
4238 .cluster
4239 .as_ref()
4240 .unwrap_or_else(|| &Cluster::Devnet)
4241 .url();
4242 loop {
4243 let exit = std::process::Command::new("solana")
4244 .arg("airdrop")
4245 .arg("10")
4246 .arg("--url")
4247 .arg(&url)
4248 .stdout(Stdio::inherit())
4249 .stderr(Stdio::inherit())
4250 .output()
4251 .expect("Must airdrop");
4252 if !exit.status.success() {
4253 println!("There was a problem airdropping: {:?}.", exit);
4254 std::process::exit(exit.status.code().unwrap_or(1));
4255 }
4256 std::thread::sleep(std::time::Duration::from_millis(10000));
4257 }
4258}
4259
4260fn cluster(_cmd: ClusterCommand) -> Result<()> {
4261 println!("Cluster Endpoints:\n");
4262 println!("* Mainnet - https://api.mainnet-beta.solana.com");
4263 println!("* Devnet - https://api.devnet.solana.com");
4264 println!("* Testnet - https://api.testnet.solana.com");
4265 Ok(())
4266}
4267
4268fn shell(cfg_override: &ConfigOverride) -> Result<()> {
4269 with_workspace(cfg_override, |cfg| {
4270 let programs = {
4271 let mut idls: HashMap<String, Idl> = cfg
4273 .read_all_programs()?
4274 .iter()
4275 .filter(|program| program.idl.is_some())
4276 .map(|program| {
4277 (
4278 program.idl.as_ref().unwrap().metadata.name.clone(),
4279 program.idl.clone().unwrap(),
4280 )
4281 })
4282 .collect();
4283 if let Some(programs) = cfg.programs.get(&cfg.provider.cluster) {
4285 let _ = programs
4286 .iter()
4287 .map(|(name, pd)| {
4288 if let Some(idl_fp) = &pd.idl {
4289 let file_str =
4290 fs::read_to_string(idl_fp).expect("Unable to read IDL file");
4291 let idl = serde_json::from_str(&file_str).expect("Idl not readable");
4292 idls.insert(name.clone(), idl);
4293 }
4294 })
4295 .collect::<Vec<_>>();
4296 }
4297
4298 match cfg.programs.get(&cfg.provider.cluster) {
4300 None => Vec::new(),
4301 Some(programs) => programs
4302 .iter()
4303 .filter_map(|(name, program_deployment)| {
4304 Some(ProgramWorkspace {
4305 name: name.to_string(),
4306 program_id: program_deployment.address,
4307 idl: match idls.get(name) {
4308 None => return None,
4309 Some(idl) => idl.clone(),
4310 },
4311 })
4312 })
4313 .collect::<Vec<ProgramWorkspace>>(),
4314 }
4315 };
4316 let url = cluster_url(cfg, &cfg.test_validator);
4317 let js_code = rust_template::node_shell(&url, &cfg.provider.wallet.to_string(), programs)?;
4318 let mut child = std::process::Command::new("node")
4319 .args(["-e", &js_code, "-i", "--experimental-repl-await"])
4320 .stdout(Stdio::inherit())
4321 .stderr(Stdio::inherit())
4322 .spawn()
4323 .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
4324
4325 if !child.wait()?.success() {
4326 println!("Error running node shell");
4327 return Ok(());
4328 }
4329 Ok(())
4330 })
4331}
4332
4333fn run(cfg_override: &ConfigOverride, script: String, script_args: Vec<String>) -> Result<()> {
4334 with_workspace(cfg_override, |cfg| {
4335 let url = cluster_url(cfg, &cfg.test_validator);
4336 let script = cfg
4337 .scripts
4338 .get(&script)
4339 .ok_or_else(|| anyhow!("Unable to find script"))?;
4340 let script_with_args = format!("{script} {}", script_args.join(" "));
4341 let exit = std::process::Command::new("bash")
4342 .arg("-c")
4343 .arg(&script_with_args)
4344 .env("ANCHOR_PROVIDER_URL", url)
4345 .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
4346 .stdout(Stdio::inherit())
4347 .stderr(Stdio::inherit())
4348 .output()
4349 .unwrap();
4350 if !exit.status.success() {
4351 std::process::exit(exit.status.code().unwrap_or(1));
4352 }
4353 Ok(())
4354 })
4355}
4356
4357fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> {
4358 let anchor_dir = Path::new(&*shellexpand::tilde("~"))
4359 .join(".config")
4360 .join("anchor");
4361 if !anchor_dir.exists() {
4362 fs::create_dir(&anchor_dir)?;
4363 }
4364
4365 std::env::set_current_dir(&anchor_dir)?;
4366
4367 let mut file = File::create("credentials")?;
4369 file.write_all(rust_template::credentials(&token).as_bytes())?;
4370 Ok(())
4371}
4372
4373fn publish(
4374 cfg_override: &ConfigOverride,
4375 program_name: String,
4376 env_vars: Vec<String>,
4377 cargo_args: Vec<String>,
4378 skip_build: bool,
4379 arch: ProgramArch,
4380) -> Result<()> {
4381 let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
4383
4384 let program = cfg.get_program(&program_name)?;
4385
4386 let program_cargo_lock = pathdiff::diff_paths(
4387 program.path.join("Cargo.lock"),
4388 cfg.path().parent().unwrap(),
4389 )
4390 .ok_or_else(|| anyhow!("Unable to diff Cargo.lock path"))?;
4391 let cargo_lock = Path::new("Cargo.lock");
4392
4393 if !program_cargo_lock.exists() && !cargo_lock.exists() {
4395 return Err(anyhow!("Cargo.lock must exist for a verifiable build"));
4396 }
4397
4398 println!("Publishing will make your code public. Are you sure? Enter (yes)/no:");
4399
4400 let answer = std::io::stdin().lock().lines().next().unwrap().unwrap();
4401 if answer != "yes" {
4402 println!("Aborting");
4403 return Ok(());
4404 }
4405
4406 let anchor_package = AnchorPackage::from(program_name.clone(), &cfg)?;
4407 let anchor_package_bytes = serde_json::to_vec(&anchor_package)?;
4408
4409 let workspace_dir = cfg.path().parent().unwrap();
4411 std::env::set_current_dir(workspace_dir)?;
4412
4413 let dot_anchor = workspace_dir.join(".anchor");
4415 fs::create_dir_all(&dot_anchor)?;
4416 let tarball_filename = dot_anchor.join(format!("{program_name}.tar.gz"));
4417 let tar_gz = File::create(&tarball_filename)?;
4418 let enc = GzEncoder::new(tar_gz, Compression::default());
4419 let mut tar = tar::Builder::new(enc);
4420
4421 println!("PACKING: Anchor.toml");
4423 tar.append_path("Anchor.toml")?;
4424 if cargo_lock.exists() {
4425 println!("PACKING: Cargo.lock");
4426 tar.append_path(cargo_lock)?;
4427 }
4428 if Path::new("Cargo.toml").exists() {
4429 println!("PACKING: Cargo.toml");
4430 tar.append_path("Cargo.toml")?;
4431 }
4432 if Path::new("LICENSE").exists() {
4433 println!("PACKING: LICENSE");
4434 tar.append_path("LICENSE")?;
4435 }
4436 if Path::new("README.md").exists() {
4437 println!("PACKING: README.md");
4438 tar.append_path("README.md")?;
4439 }
4440 if Path::new("idl.json").exists() {
4441 println!("PACKING: idl.json");
4442 tar.append_path("idl.json")?;
4443 }
4444
4445 for path in cfg.get_rust_program_list()? {
4447 let mut dirs = walkdir::WalkDir::new(path)
4448 .into_iter()
4449 .filter_entry(|e| !is_hidden(e));
4450
4451 let _ = dirs.next().unwrap()?;
4453
4454 for entry in dirs {
4455 let e = entry.map_err(|e| anyhow!("{:?}", e))?;
4456
4457 let e = pathdiff::diff_paths(e.path(), cfg.path().parent().unwrap())
4458 .ok_or_else(|| anyhow!("Unable to diff paths"))?;
4459
4460 let path_str = e.display().to_string();
4461
4462 if !path_str.contains("target/") && !path_str.contains("/target") {
4464 let metadata = fs::File::open(&e)?.metadata()?;
4466 if metadata.len() > 0 {
4467 println!("PACKING: {}", e.display());
4468 if e.is_dir() {
4469 tar.append_dir_all(&e, &e)?;
4470 } else {
4471 tar.append_path(&e)?;
4472 }
4473 }
4474 }
4475 }
4476 }
4477
4478 tar.into_inner()?;
4480
4481 let ws_dir = dot_anchor.join("workspace");
4483 if Path::exists(&ws_dir) {
4484 fs::remove_dir_all(&ws_dir)?;
4485 }
4486 fs::create_dir_all(&ws_dir)?;
4487
4488 std::env::set_current_dir(&ws_dir)?;
4490 unpack_archive(&tarball_filename)?;
4491
4492 if !skip_build {
4494 build(
4495 cfg_override,
4496 false,
4497 None,
4498 None,
4499 true,
4500 false,
4501 Some(program_name),
4502 None,
4503 None,
4504 BootstrapMode::None,
4505 None,
4506 None,
4507 env_vars,
4508 cargo_args,
4509 true,
4510 arch,
4511 )?;
4512 }
4513
4514 let token = registry_api_token(cfg_override)?;
4516 let form = Form::new()
4517 .part("manifest", Part::bytes(anchor_package_bytes))
4518 .part("workspace", {
4519 let file = File::open(&tarball_filename)?;
4520 Part::reader(file)
4521 });
4522 let client = Client::new();
4523 let resp = client
4524 .post(format!("{}/api/v0/build", cfg.registry.url))
4525 .bearer_auth(token)
4526 .multipart(form)
4527 .send()?;
4528
4529 if resp.status() == 200 {
4530 println!("Build triggered");
4531 } else {
4532 println!(
4533 "{:?}",
4534 resp.text().unwrap_or_else(|_| "Server error".to_string())
4535 );
4536 }
4537
4538 Ok(())
4539}
4540
4541fn unpack_archive(tar_path: impl AsRef<Path>) -> Result<()> {
4543 let tar = GzDecoder::new(std::fs::File::open(tar_path)?);
4544 let mut archive = Archive::new(tar);
4545 archive.unpack(".")?;
4546 archive.into_inner();
4547
4548 Ok(())
4549}
4550
4551fn registry_api_token(_cfg_override: &ConfigOverride) -> Result<String> {
4552 #[derive(Debug, Deserialize)]
4553 struct Registry {
4554 token: String,
4555 }
4556 #[derive(Debug, Deserialize)]
4557 struct Credentials {
4558 registry: Registry,
4559 }
4560 let filename = Path::new(&*shellexpand::tilde("~"))
4561 .join(".config")
4562 .join("anchor")
4563 .join("credentials");
4564 let mut file = File::open(filename)?;
4565 let mut contents = String::new();
4566 file.read_to_string(&mut contents)?;
4567
4568 let credentials_toml: Credentials = toml::from_str(&contents)?;
4569
4570 Ok(credentials_toml.registry.token)
4571}
4572
4573fn keys(cfg_override: &ConfigOverride, cmd: KeysCommand) -> Result<()> {
4574 match cmd {
4575 KeysCommand::List => keys_list(cfg_override),
4576 KeysCommand::Sync { program_name } => keys_sync(cfg_override, program_name),
4577 }
4578}
4579
4580fn keys_list(cfg_override: &ConfigOverride) -> Result<()> {
4581 with_workspace(cfg_override, |cfg| {
4582 for program in cfg.read_all_programs()? {
4583 let pubkey = program.pubkey()?;
4584 println!("{}: {}", program.lib_name, pubkey);
4585 }
4586 Ok(())
4587 })
4588}
4589
4590fn keys_sync(cfg_override: &ConfigOverride, program_name: Option<String>) -> Result<()> {
4592 with_workspace(cfg_override, |cfg| {
4593 let declare_id_regex = RegexBuilder::new(r#"^(([\w]+::)*)declare_id!\("(\w*)"\)"#)
4594 .multi_line(true)
4595 .build()
4596 .unwrap();
4597
4598 let mut changed_src = false;
4599 for program in cfg.get_programs(program_name)? {
4600 let actual_program_id = program.pubkey()?.to_string();
4602
4603 let src_path = program.path.join("src");
4605 let files_to_check = vec![src_path.join("lib.rs"), src_path.join("id.rs")];
4606
4607 for path in files_to_check {
4608 let mut content = match fs::read_to_string(&path) {
4609 Ok(content) => content,
4610 Err(_) => continue,
4611 };
4612
4613 let incorrect_program_id = declare_id_regex
4614 .captures(&content)
4615 .and_then(|captures| captures.get(3))
4616 .filter(|program_id_match| program_id_match.as_str() != actual_program_id);
4617 if let Some(program_id_match) = incorrect_program_id {
4618 println!("Found incorrect program id declaration in {path:?}");
4619
4620 content.replace_range(program_id_match.range(), &actual_program_id);
4622 fs::write(&path, content)?;
4623
4624 changed_src = true;
4625 println!("Updated to {actual_program_id}\n");
4626 break;
4627 }
4628 }
4629
4630 'outer: for programs in cfg.programs.values_mut() {
4632 for (name, deployment) in programs {
4633 if name != &program.lib_name {
4635 continue;
4636 }
4637
4638 if deployment.address.to_string() != actual_program_id {
4639 println!("Found incorrect program id declaration in Anchor.toml for the program `{name}`");
4640
4641 deployment.address = Pubkey::from_str(&actual_program_id).unwrap();
4643 fs::write(cfg.path(), cfg.to_string())?;
4644
4645 println!("Updated to {actual_program_id}\n");
4646 break 'outer;
4647 }
4648 }
4649 }
4650 }
4651
4652 println!("All program id declarations are synced.");
4653 if changed_src {
4654 println!("Please rebuild the program to update the generated artifacts.")
4655 }
4656
4657 Ok(())
4658 })
4659}
4660
4661fn localnet(
4662 cfg_override: &ConfigOverride,
4663 skip_build: bool,
4664 skip_deploy: bool,
4665 skip_lint: bool,
4666 env_vars: Vec<String>,
4667 cargo_args: Vec<String>,
4668 arch: ProgramArch,
4669) -> Result<()> {
4670 with_workspace(cfg_override, |cfg| {
4671 if !skip_build {
4673 build(
4674 cfg_override,
4675 false,
4676 None,
4677 None,
4678 false,
4679 skip_lint,
4680 None,
4681 None,
4682 None,
4683 BootstrapMode::None,
4684 None,
4685 None,
4686 env_vars,
4687 cargo_args,
4688 false,
4689 arch,
4690 )?;
4691 }
4692
4693 let flags = match skip_deploy {
4694 true => None,
4695 false => Some(validator_flags(cfg, &cfg.test_validator)?),
4696 };
4697
4698 let validator_handle = &mut start_test_validator(cfg, &cfg.test_validator, flags, false)?;
4699
4700 let url = test_validator_rpc_url(&cfg.test_validator);
4702 let log_streams = stream_logs(cfg, &url);
4703
4704 std::io::stdin().lock().lines().next().unwrap().unwrap();
4705
4706 if let Err(err) = validator_handle.kill() {
4708 println!(
4709 "Failed to kill subprocess {}: {}",
4710 validator_handle.id(),
4711 err
4712 );
4713 }
4714
4715 for mut child in log_streams? {
4716 if let Err(err) = child.kill() {
4717 println!("Failed to kill subprocess {}: {}", child.id(), err);
4718 }
4719 }
4720
4721 Ok(())
4722 })
4723}
4724
4725fn with_workspace<R>(
4732 cfg_override: &ConfigOverride,
4733 f: impl FnOnce(&mut WithPath<Config>) -> R,
4734) -> R {
4735 set_workspace_dir_or_exit();
4736
4737 let mut cfg = Config::discover(cfg_override)
4738 .expect("Previously set the workspace dir")
4739 .expect("Anchor.toml must always exist");
4740
4741 let r = f(&mut cfg);
4742
4743 set_workspace_dir_or_exit();
4744
4745 r
4746}
4747
4748fn is_hidden(entry: &walkdir::DirEntry) -> bool {
4749 entry
4750 .file_name()
4751 .to_str()
4752 .map(|s| s == "." || s.starts_with('.') || s == "target")
4753 .unwrap_or(false)
4754}
4755
4756fn get_node_version() -> Result<Version> {
4757 let node_version = std::process::Command::new("node")
4758 .arg("--version")
4759 .stderr(Stdio::inherit())
4760 .output()
4761 .map_err(|e| anyhow::format_err!("node failed: {}", e.to_string()))?;
4762 let output = std::str::from_utf8(&node_version.stdout)?
4763 .strip_prefix('v')
4764 .unwrap()
4765 .trim();
4766 Version::parse(output).map_err(Into::into)
4767}
4768
4769fn add_recommended_deployment_solana_args(
4770 client: &RpcClient,
4771 args: Vec<String>,
4772) -> Result<Vec<String>> {
4773 let mut augmented_args = args.clone();
4774
4775 if !args.contains(&"--with-compute-unit-price".to_string()) {
4777 let priority_fee = get_recommended_micro_lamport_fee(client)?;
4778 augmented_args.push("--with-compute-unit-price".to_string());
4779 augmented_args.push(priority_fee.to_string());
4780 }
4781
4782 const DEFAULT_MAX_SIGN_ATTEMPTS: u8 = 30;
4783 if !args.contains(&"--max-sign-attempts".to_string()) {
4784 augmented_args.push("--max-sign-attempts".to_string());
4785 augmented_args.push(DEFAULT_MAX_SIGN_ATTEMPTS.to_string());
4786 }
4787
4788 if !args.contains(&"--buffer".to_owned()) {
4792 let tmp_keypair_path = std::env::temp_dir().join("anchor-upgrade-buffer.json");
4793 if !tmp_keypair_path.exists() {
4794 if let Err(err) = Keypair::new().write_to_file(&tmp_keypair_path) {
4795 return Err(anyhow!(
4796 "Error creating keypair for buffer account, {:?}",
4797 err
4798 ));
4799 }
4800 }
4801
4802 augmented_args.push("--buffer".to_owned());
4803 augmented_args.push(tmp_keypair_path.to_string_lossy().to_string());
4804 }
4805
4806 Ok(augmented_args)
4807}
4808
4809fn get_recommended_micro_lamport_fee(client: &RpcClient) -> Result<u64> {
4810 let mut fees = client.get_recent_prioritization_fees(&[])?;
4811 if fees.is_empty() {
4812 return Ok(0);
4814 }
4815
4816 fees.sort_unstable_by_key(|fee| fee.prioritization_fee);
4818 let median_index = fees.len() / 2;
4819
4820 let median_priority_fee = if fees.len() % 2 == 0 {
4821 (fees[median_index - 1].prioritization_fee + fees[median_index].prioritization_fee) / 2
4822 } else {
4823 fees[median_index].prioritization_fee
4824 };
4825
4826 Ok(median_priority_fee)
4827}
4828
4829fn prepend_compute_unit_ix(
4832 instructions: Vec<Instruction>,
4833 client: &RpcClient,
4834 priority_fee: Option<u64>,
4835) -> Result<Vec<Instruction>> {
4836 let priority_fee = match priority_fee {
4837 Some(fee) => fee,
4838 None => get_recommended_micro_lamport_fee(client)?,
4839 };
4840
4841 if priority_fee > 0 {
4842 let mut instructions_appended = instructions.clone();
4843 instructions_appended.insert(
4844 0,
4845 ComputeBudgetInstruction::set_compute_unit_price(priority_fee),
4846 );
4847 Ok(instructions_appended)
4848 } else {
4849 Ok(instructions)
4850 }
4851}
4852
4853fn get_node_dns_option() -> Result<&'static str> {
4854 let version = get_node_version()?;
4855 let req = VersionReq::parse(">=16.4.0").unwrap();
4856 let option = match req.matches(&version) {
4857 true => "--dns-result-order=ipv4first",
4858 false => "",
4859 };
4860 Ok(option)
4861}
4862
4863fn strip_workspace_prefix(absolute_path: String) -> String {
4870 let workspace_prefix =
4871 std::env::current_dir().unwrap().display().to_string() + std::path::MAIN_SEPARATOR_STR;
4872 absolute_path
4873 .strip_prefix(&workspace_prefix)
4874 .unwrap_or(&absolute_path)
4875 .into()
4876}
4877
4878fn create_client<U: ToString>(url: U) -> RpcClient {
4880 RpcClient::new_with_commitment(url, CommitmentConfig::confirmed())
4881}
4882
4883#[cfg(test)]
4884mod tests {
4885 use super::*;
4886
4887 #[test]
4888 #[should_panic(expected = "Anchor workspace name must be a valid Rust identifier.")]
4889 fn test_init_reserved_word() {
4890 init(
4891 &ConfigOverride {
4892 cluster: None,
4893 wallet: None,
4894 },
4895 "await".to_string(),
4896 true,
4897 false,
4898 true,
4899 PackageManager::default(),
4900 false,
4901 ProgramTemplate::default(),
4902 TestTemplate::default(),
4903 false,
4904 )
4905 .unwrap();
4906 }
4907
4908 #[test]
4909 #[should_panic(expected = "Anchor workspace name must be a valid Rust identifier.")]
4910 fn test_init_reserved_word_from_syn() {
4911 init(
4912 &ConfigOverride {
4913 cluster: None,
4914 wallet: None,
4915 },
4916 "fn".to_string(),
4917 true,
4918 false,
4919 true,
4920 PackageManager::default(),
4921 false,
4922 ProgramTemplate::default(),
4923 TestTemplate::default(),
4924 false,
4925 )
4926 .unwrap();
4927 }
4928
4929 #[test]
4930 #[should_panic(expected = "Anchor workspace name must be a valid Rust identifier.")]
4931 fn test_init_starting_with_digit() {
4932 init(
4933 &ConfigOverride {
4934 cluster: None,
4935 wallet: None,
4936 },
4937 "1project".to_string(),
4938 true,
4939 false,
4940 true,
4941 PackageManager::default(),
4942 false,
4943 ProgramTemplate::default(),
4944 TestTemplate::default(),
4945 false,
4946 )
4947 .unwrap();
4948 }
4949}