main.rs 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722
  1. //! CLI for workspace management of anchor programs.
  2. use crate::config::{read_all_programs, Config, Program, ProgramWorkspace, WalletPath};
  3. use anchor_client::Cluster;
  4. use anchor_lang::idl::{IdlAccount, IdlInstruction};
  5. use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
  6. use anchor_syn::idl::Idl;
  7. use anyhow::{anyhow, Context, Result};
  8. use clap::Clap;
  9. use flate2::read::ZlibDecoder;
  10. use flate2::write::ZlibEncoder;
  11. use flate2::Compression;
  12. use rand::rngs::OsRng;
  13. use serde::{Deserialize, Serialize};
  14. use solana_client::rpc_client::RpcClient;
  15. use solana_client::rpc_config::RpcSendTransactionConfig;
  16. use solana_program::instruction::{AccountMeta, Instruction};
  17. use solana_sdk::account_utils::StateMut;
  18. use solana_sdk::bpf_loader_upgradeable::UpgradeableLoaderState;
  19. use solana_sdk::commitment_config::CommitmentConfig;
  20. use solana_sdk::pubkey::Pubkey;
  21. use solana_sdk::signature::Keypair;
  22. use solana_sdk::signature::Signer;
  23. use solana_sdk::sysvar;
  24. use solana_sdk::transaction::Transaction;
  25. use std::collections::HashMap;
  26. use std::fs::{self, File};
  27. use std::io::prelude::*;
  28. use std::path::{Path, PathBuf};
  29. use std::process::{Child, Stdio};
  30. use std::string::ToString;
  31. mod config;
  32. mod template;
  33. // Version of the docker image.
  34. const VERSION: &str = env!("CARGO_PKG_VERSION");
  35. const DOCKER_BUILDER_VERSION: &str = VERSION;
  36. #[derive(Debug, Clap)]
  37. #[clap(version = VERSION)]
  38. pub struct Opts {
  39. #[clap(flatten)]
  40. pub cfg_override: ConfigOverride,
  41. #[clap(subcommand)]
  42. pub command: Command,
  43. }
  44. #[derive(Debug, Clap)]
  45. pub struct ConfigOverride {
  46. /// Cluster override.
  47. #[clap(global = true, long = "provider.cluster")]
  48. cluster: Option<Cluster>,
  49. /// Wallet override.
  50. #[clap(global = true, long = "provider.wallet")]
  51. wallet: Option<WalletPath>,
  52. }
  53. #[derive(Debug, Clap)]
  54. pub enum Command {
  55. /// Initializes a workspace.
  56. Init {
  57. name: String,
  58. #[clap(short, long)]
  59. typescript: bool,
  60. },
  61. /// Builds the workspace.
  62. Build {
  63. /// Output directory for the IDL.
  64. #[clap(short, long)]
  65. idl: Option<String>,
  66. /// True if the build artifact needs to be deterministic and verifiable.
  67. #[clap(short, long)]
  68. verifiable: bool,
  69. #[clap(short, long)]
  70. program_name: Option<String>,
  71. },
  72. /// Verifies the on-chain bytecode matches the locally compiled artifact.
  73. /// Run this command inside a program subdirectory, i.e., in the dir
  74. /// containing the program's Cargo.toml.
  75. Verify {
  76. /// The deployed program to compare against.
  77. program_id: Pubkey,
  78. },
  79. /// Runs integration tests against a localnetwork.
  80. Test {
  81. /// Use this flag if you want to run tests against previously deployed
  82. /// programs.
  83. #[clap(long)]
  84. skip_deploy: bool,
  85. /// Flag to skip starting a local validator, if the configured cluster
  86. /// url is a localnet.
  87. #[clap(long)]
  88. skip_local_validator: bool,
  89. /// Flag to skip building the program in the workspace,
  90. /// use this to save time when running test and the program code is not altered.
  91. #[clap(long)]
  92. skip_build: bool,
  93. /// Use this flag if you want to use yarn as your package manager.
  94. #[clap(long)]
  95. yarn: bool,
  96. file: Option<String>,
  97. },
  98. /// Creates a new program.
  99. New { name: String },
  100. /// Commands for interacting with interface definitions.
  101. Idl {
  102. #[clap(subcommand)]
  103. subcmd: IdlCommand,
  104. },
  105. /// Deploys each program in the workspace.
  106. Deploy {
  107. #[clap(short, long)]
  108. program_name: Option<String>,
  109. },
  110. /// Runs the deploy migration script.
  111. Migrate,
  112. /// Deploys, initializes an IDL, and migrates all in one command.
  113. Launch {
  114. /// True if the build should be verifiable. If deploying to mainnet,
  115. /// this should almost always be set.
  116. #[clap(short, long)]
  117. verifiable: bool,
  118. #[clap(short, long)]
  119. program_name: Option<String>,
  120. },
  121. /// Upgrades a single program. The configured wallet must be the upgrade
  122. /// authority.
  123. Upgrade {
  124. /// The program to upgrade.
  125. #[clap(short, long)]
  126. program_id: Pubkey,
  127. /// Filepath to the new program binary.
  128. program_filepath: String,
  129. },
  130. #[cfg(feature = "dev")]
  131. /// Runs an airdrop loop, continuously funding the configured wallet.
  132. Airdrop {
  133. #[clap(short, long)]
  134. url: Option<String>,
  135. },
  136. /// Cluster commands.
  137. Cluster {
  138. #[clap(subcommand)]
  139. subcmd: ClusterCommand,
  140. },
  141. /// Starts a node shell with an Anchor client setup according to the local
  142. /// config.
  143. Shell,
  144. /// Runs the script defined by the current workspace's Anchor.toml.
  145. Run {
  146. /// The name of the script to run.
  147. script: String,
  148. },
  149. }
  150. #[derive(Debug, Clap)]
  151. pub enum IdlCommand {
  152. /// Initializes a program's IDL account. Can only be run once.
  153. Init {
  154. program_id: Pubkey,
  155. #[clap(short, long)]
  156. filepath: String,
  157. },
  158. /// Writes an IDL into a buffer account. This can be used with SetBuffer
  159. /// to perform an upgrade.
  160. WriteBuffer {
  161. program_id: Pubkey,
  162. #[clap(short, long)]
  163. filepath: String,
  164. },
  165. /// Sets a new IDL buffer for the program.
  166. SetBuffer {
  167. program_id: Pubkey,
  168. /// Address of the buffer account to set as the idl on the program.
  169. #[clap(short, long)]
  170. buffer: Pubkey,
  171. },
  172. /// Upgrades the IDL to the new file. An alias for first writing and then
  173. /// then setting the idl buffer account.
  174. Upgrade {
  175. program_id: Pubkey,
  176. #[clap(short, long)]
  177. filepath: String,
  178. },
  179. /// Sets a new authority on the IDL account.
  180. SetAuthority {
  181. /// The IDL account buffer to set the authority of. If none is given,
  182. /// then the canonical IDL account is used.
  183. address: Option<Pubkey>,
  184. /// Program to change the IDL authority.
  185. #[clap(short, long)]
  186. program_id: Pubkey,
  187. /// New authority of the IDL account.
  188. #[clap(short, long)]
  189. new_authority: Pubkey,
  190. },
  191. /// Command to remove the ability to modify the IDL account. This should
  192. /// likely be used in conjection with eliminating an "upgrade authority" on
  193. /// the program.
  194. EraseAuthority {
  195. #[clap(short, long)]
  196. program_id: Pubkey,
  197. },
  198. /// Outputs the authority for the IDL account.
  199. Authority {
  200. /// The program to view.
  201. program_id: Pubkey,
  202. },
  203. /// Parses an IDL from source.
  204. Parse {
  205. /// Path to the program's interface definition.
  206. #[clap(short, long)]
  207. file: String,
  208. /// Output file for the idl (stdout if not specified).
  209. #[clap(short, long)]
  210. out: Option<String>,
  211. },
  212. /// Fetches an IDL for the given address from a cluster.
  213. /// The address can be a program, IDL account, or IDL buffer.
  214. Fetch {
  215. address: Pubkey,
  216. /// Output file for the idl (stdout if not specified).
  217. #[clap(short, long)]
  218. out: Option<String>,
  219. },
  220. }
  221. #[derive(Debug, Clap)]
  222. pub enum ClusterCommand {
  223. /// Prints common cluster urls.
  224. List,
  225. }
  226. fn main() -> Result<()> {
  227. let opts = Opts::parse();
  228. match opts.command {
  229. Command::Init { name, typescript } => init(&opts.cfg_override, name, typescript),
  230. Command::New { name } => new(&opts.cfg_override, name),
  231. Command::Build {
  232. idl,
  233. verifiable,
  234. program_name,
  235. } => build(&opts.cfg_override, idl, verifiable, program_name),
  236. Command::Verify { program_id } => verify(&opts.cfg_override, program_id),
  237. Command::Deploy { program_name } => deploy(&opts.cfg_override, program_name),
  238. Command::Upgrade {
  239. program_id,
  240. program_filepath,
  241. } => upgrade(&opts.cfg_override, program_id, program_filepath),
  242. Command::Idl { subcmd } => idl(&opts.cfg_override, subcmd),
  243. Command::Migrate => migrate(&opts.cfg_override),
  244. Command::Launch {
  245. verifiable,
  246. program_name,
  247. } => launch(&opts.cfg_override, verifiable, program_name),
  248. Command::Test {
  249. skip_deploy,
  250. skip_local_validator,
  251. skip_build,
  252. yarn,
  253. file,
  254. } => test(
  255. &opts.cfg_override,
  256. skip_deploy,
  257. skip_local_validator,
  258. skip_build,
  259. yarn,
  260. file,
  261. ),
  262. #[cfg(feature = "dev")]
  263. Command::Airdrop => airdrop(cfg_override),
  264. Command::Cluster { subcmd } => cluster(subcmd),
  265. Command::Shell => shell(&opts.cfg_override),
  266. Command::Run { script } => run(&opts.cfg_override, script),
  267. }
  268. }
  269. fn init(cfg_override: &ConfigOverride, name: String, typescript: bool) -> Result<()> {
  270. let cfg = Config::discover(cfg_override)?;
  271. if cfg.is_some() {
  272. println!("Anchor workspace already initialized");
  273. }
  274. fs::create_dir(name.clone())?;
  275. std::env::set_current_dir(&name)?;
  276. fs::create_dir("app")?;
  277. let cfg = Config::default();
  278. let toml = cfg.to_string();
  279. let mut file = File::create("Anchor.toml")?;
  280. file.write_all(toml.as_bytes())?;
  281. // Build virtual manifest.
  282. let mut virt_manifest = File::create("Cargo.toml")?;
  283. virt_manifest.write_all(template::virtual_manifest().as_bytes())?;
  284. // Initialize .gitignore file
  285. let mut virt_manifest = File::create(".gitignore")?;
  286. virt_manifest.write_all(template::git_ignore().as_bytes())?;
  287. // Build the program.
  288. fs::create_dir("programs")?;
  289. new_program(&name)?;
  290. // Build the test suite.
  291. fs::create_dir("tests")?;
  292. // Build the migrations directory.
  293. fs::create_dir("migrations")?;
  294. if typescript {
  295. // Build typescript config
  296. let mut ts_config = File::create("tsconfig.json")?;
  297. ts_config.write_all(template::ts_config().as_bytes())?;
  298. let mut deploy = File::create("migrations/deploy.ts")?;
  299. deploy.write_all(&template::ts_deploy_script().as_bytes())?;
  300. let mut mocha = File::create(&format!("tests/{}.spec.ts", name))?;
  301. mocha.write_all(template::ts_mocha(&name).as_bytes())?;
  302. } else {
  303. let mut mocha = File::create(&format!("tests/{}.js", name))?;
  304. mocha.write_all(template::mocha(&name).as_bytes())?;
  305. let mut deploy = File::create("migrations/deploy.js")?;
  306. deploy.write_all(&template::deploy_script().as_bytes())?;
  307. }
  308. println!("{} initialized", name);
  309. Ok(())
  310. }
  311. // Creates a new program crate in the `programs/<name>` directory.
  312. fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
  313. with_workspace(cfg_override, |_cfg, path, _cargo| {
  314. match path.parent() {
  315. None => {
  316. println!("Unable to make new program");
  317. }
  318. Some(parent) => {
  319. std::env::set_current_dir(&parent)?;
  320. new_program(&name)?;
  321. println!("Created new program.");
  322. }
  323. };
  324. Ok(())
  325. })
  326. }
  327. // Creates a new program crate in the current directory with `name`.
  328. fn new_program(name: &str) -> Result<()> {
  329. fs::create_dir(&format!("programs/{}", name))?;
  330. fs::create_dir(&format!("programs/{}/src/", name))?;
  331. let mut cargo_toml = File::create(&format!("programs/{}/Cargo.toml", name))?;
  332. cargo_toml.write_all(template::cargo_toml(&name).as_bytes())?;
  333. let mut xargo_toml = File::create(&format!("programs/{}/Xargo.toml", name))?;
  334. xargo_toml.write_all(template::xargo_toml().as_bytes())?;
  335. let mut lib_rs = File::create(&format!("programs/{}/src/lib.rs", name))?;
  336. lib_rs.write_all(template::lib_rs(&name).as_bytes())?;
  337. Ok(())
  338. }
  339. fn build(
  340. cfg_override: &ConfigOverride,
  341. idl: Option<String>,
  342. verifiable: bool,
  343. program_name: Option<String>,
  344. ) -> Result<()> {
  345. if let Some(program_name) = program_name {
  346. for program in read_all_programs()? {
  347. let p = program.path.file_name().unwrap().to_str().unwrap();
  348. if program_name.as_str() == p {
  349. std::env::set_current_dir(&program.path)?;
  350. }
  351. }
  352. }
  353. let (cfg, path, cargo) = Config::discover(cfg_override)?.expect("Not in workspace.");
  354. let idl_out = match idl {
  355. Some(idl) => Some(PathBuf::from(idl)),
  356. None => {
  357. let cfg_parent = match path.parent() {
  358. None => return Err(anyhow!("Invalid Anchor.toml")),
  359. Some(parent) => parent,
  360. };
  361. fs::create_dir_all(cfg_parent.join("target/idl"))?;
  362. Some(cfg_parent.join("target/idl"))
  363. }
  364. };
  365. match cargo {
  366. None => build_all(&cfg, path, idl_out, verifiable)?,
  367. Some(ct) => build_cwd(path.as_path(), ct, idl_out, verifiable)?,
  368. };
  369. set_workspace_dir_or_exit();
  370. Ok(())
  371. }
  372. fn build_all(
  373. _cfg: &Config,
  374. cfg_path: PathBuf,
  375. idl_out: Option<PathBuf>,
  376. verifiable: bool,
  377. ) -> Result<()> {
  378. let cur_dir = std::env::current_dir()?;
  379. let r = match cfg_path.parent() {
  380. None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
  381. Some(parent) => {
  382. let files = fs::read_dir(parent.join("programs"))?;
  383. for f in files {
  384. let p = f?.path();
  385. build_cwd(
  386. cfg_path.as_path(),
  387. p.join("Cargo.toml"),
  388. idl_out.clone(),
  389. verifiable,
  390. )?;
  391. }
  392. Ok(())
  393. }
  394. };
  395. std::env::set_current_dir(cur_dir)?;
  396. r
  397. }
  398. // Runs the build command outside of a workspace.
  399. fn build_cwd(
  400. cfg_path: &Path,
  401. cargo_toml: PathBuf,
  402. idl_out: Option<PathBuf>,
  403. verifiable: bool,
  404. ) -> Result<()> {
  405. match cargo_toml.parent() {
  406. None => return Err(anyhow!("Unable to find parent")),
  407. Some(p) => std::env::set_current_dir(&p)?,
  408. };
  409. match verifiable {
  410. false => _build_cwd(idl_out),
  411. true => build_cwd_verifiable(cfg_path.parent().unwrap()),
  412. }
  413. }
  414. // Builds an anchor program in a docker image and copies the build artifacts
  415. // into the `target/` directory.
  416. fn build_cwd_verifiable(workspace_dir: &Path) -> Result<()> {
  417. // Docker vars.
  418. let container_name = "anchor-program";
  419. let image_name = format!("projectserum/build:v{}", DOCKER_BUILDER_VERSION);
  420. let volume_mount = format!(
  421. "{}:/workdir",
  422. workspace_dir.canonicalize()?.display().to_string()
  423. );
  424. // Create output dirs.
  425. fs::create_dir_all(workspace_dir.join("target/deploy"))?;
  426. fs::create_dir_all(workspace_dir.join("target/idl"))?;
  427. // Build the program in docker.
  428. let exit = std::process::Command::new("docker")
  429. .args(&[
  430. "run",
  431. "--name",
  432. &container_name,
  433. "-v",
  434. &volume_mount,
  435. &image_name,
  436. "anchor",
  437. "build",
  438. ])
  439. .stdout(Stdio::inherit())
  440. .stderr(Stdio::inherit())
  441. .output()
  442. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  443. if !exit.status.success() {
  444. println!("Error building program");
  445. return Ok(());
  446. }
  447. let idl = extract_idl("src/lib.rs")?;
  448. // Copy the binary out of the docker image.
  449. let out_file = format!("../../target/deploy/{}.so", idl.name);
  450. let bin_artifact = format!("{}:/workdir/target/deploy/{}.so", container_name, idl.name);
  451. let exit = std::process::Command::new("docker")
  452. .args(&["cp", &bin_artifact, &out_file])
  453. .stdout(Stdio::inherit())
  454. .stderr(Stdio::inherit())
  455. .output()
  456. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  457. if !exit.status.success() {
  458. return Ok(());
  459. }
  460. // Copy the idl out of the docker image.
  461. let out_file = format!("../../target/idl/{}.json", idl.name);
  462. let idl_artifact = format!("{}:/workdir/target/idl/{}.json", container_name, idl.name);
  463. let exit = std::process::Command::new("docker")
  464. .args(&["cp", &idl_artifact, &out_file])
  465. .stdout(Stdio::inherit())
  466. .stderr(Stdio::inherit())
  467. .output()
  468. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  469. if !exit.status.success() {
  470. return Ok(());
  471. }
  472. // Remove the docker image.
  473. let exit = std::process::Command::new("docker")
  474. .args(&["rm", &container_name])
  475. .stdout(Stdio::inherit())
  476. .stderr(Stdio::inherit())
  477. .output()
  478. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  479. if !exit.status.success() {
  480. std::process::exit(exit.status.code().unwrap_or(1));
  481. }
  482. Ok(())
  483. }
  484. fn _build_cwd(idl_out: Option<PathBuf>) -> Result<()> {
  485. let exit = std::process::Command::new("cargo")
  486. .arg("build-bpf")
  487. .stdout(Stdio::inherit())
  488. .stderr(Stdio::inherit())
  489. .output()
  490. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  491. if !exit.status.success() {
  492. std::process::exit(exit.status.code().unwrap_or(1));
  493. }
  494. // Always assume idl is located ar src/lib.rs.
  495. let idl = extract_idl("src/lib.rs")?;
  496. let out = match idl_out {
  497. None => PathBuf::from(".").join(&idl.name).with_extension("json"),
  498. Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")),
  499. };
  500. write_idl(&idl, OutFile::File(out))
  501. }
  502. fn verify(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
  503. let (cfg, _path, cargo) = Config::discover(cfg_override)?.expect("Not in workspace.");
  504. let cargo = cargo.ok_or(anyhow!("Must be inside program subdirectory."))?;
  505. let program_dir = cargo.parent().unwrap();
  506. // Build the program we want to verify.
  507. let cur_dir = std::env::current_dir()?;
  508. build(cfg_override, None, true, None)?;
  509. std::env::set_current_dir(&cur_dir)?;
  510. let local_idl = extract_idl("src/lib.rs")?;
  511. // Verify binary.
  512. let bin_path = program_dir
  513. .join("../../target/deploy/")
  514. .join(format!("{}.so", local_idl.name));
  515. let is_buffer = verify_bin(program_id, &bin_path, cfg.provider.cluster.url())?;
  516. // Verify IDL (only if it's not a buffer account).
  517. if !is_buffer {
  518. std::env::set_current_dir(program_dir)?;
  519. let deployed_idl = fetch_idl(cfg_override, program_id)?;
  520. if local_idl != deployed_idl {
  521. println!("Error: IDLs don't match");
  522. std::process::exit(1);
  523. }
  524. }
  525. println!("{} is verified.", program_id);
  526. Ok(())
  527. }
  528. fn verify_bin(program_id: Pubkey, bin_path: &Path, cluster: &str) -> Result<bool> {
  529. let client = RpcClient::new(cluster.to_string());
  530. // Get the deployed build artifacts.
  531. let (deployed_bin, is_buffer) = {
  532. let account = client
  533. .get_account_with_commitment(&program_id, CommitmentConfig::default())?
  534. .value
  535. .map_or(Err(anyhow!("Account not found")), Ok)?;
  536. match account.state()? {
  537. UpgradeableLoaderState::Program {
  538. programdata_address,
  539. } => (
  540. client
  541. .get_account_with_commitment(&programdata_address, CommitmentConfig::default())?
  542. .value
  543. .map_or(Err(anyhow!("Account not found")), Ok)?
  544. .data[UpgradeableLoaderState::programdata_data_offset().unwrap_or(0)..]
  545. .to_vec(),
  546. false,
  547. ),
  548. UpgradeableLoaderState::Buffer { .. } => {
  549. let offset = UpgradeableLoaderState::buffer_data_offset().unwrap_or(0);
  550. (account.data[offset..].to_vec(), true)
  551. }
  552. _ => return Err(anyhow!("Invalid program id")),
  553. }
  554. };
  555. let mut local_bin = {
  556. let mut f = File::open(bin_path)?;
  557. let mut contents = vec![];
  558. f.read_to_end(&mut contents)?;
  559. contents
  560. };
  561. // The deployed program probably has zero bytes appended. The default is
  562. // 2x the binary size in case of an upgrade.
  563. if local_bin.len() < deployed_bin.len() {
  564. local_bin.append(&mut vec![0; deployed_bin.len() - local_bin.len()]);
  565. }
  566. // Finally, check the bytes.
  567. if local_bin != deployed_bin {
  568. println!("Error: Binaries don't match");
  569. std::process::exit(1);
  570. }
  571. Ok(is_buffer)
  572. }
  573. // Fetches an IDL for the given program_id.
  574. fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<Idl> {
  575. let cfg = Config::discover(cfg_override)?
  576. .expect("Inside a workspace")
  577. .0;
  578. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  579. let mut account = client
  580. .get_account_with_commitment(&idl_addr, CommitmentConfig::processed())?
  581. .value
  582. .map_or(Err(anyhow!("Account not found")), Ok)?;
  583. if account.executable {
  584. let idl_addr = IdlAccount::address(&idl_addr);
  585. account = client
  586. .get_account_with_commitment(&idl_addr, CommitmentConfig::processed())?
  587. .value
  588. .map_or(Err(anyhow!("Account not found")), Ok)?;
  589. }
  590. // Cut off account discriminator.
  591. let mut d: &[u8] = &account.data[8..];
  592. let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?;
  593. let mut z = ZlibDecoder::new(&idl_account.data[..]);
  594. let mut s = Vec::new();
  595. z.read_to_end(&mut s)?;
  596. serde_json::from_slice(&s[..]).map_err(Into::into)
  597. }
  598. fn extract_idl(file: &str) -> Result<Idl> {
  599. let file = shellexpand::tilde(file);
  600. anchor_syn::idl::file::parse(&*file)
  601. }
  602. fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
  603. match subcmd {
  604. IdlCommand::Init {
  605. program_id,
  606. filepath,
  607. } => idl_init(cfg_override, program_id, filepath),
  608. IdlCommand::WriteBuffer {
  609. program_id,
  610. filepath,
  611. } => idl_write_buffer(cfg_override, program_id, filepath).map(|_| ()),
  612. IdlCommand::SetBuffer { program_id, buffer } => {
  613. idl_set_buffer(cfg_override, program_id, buffer)
  614. }
  615. IdlCommand::Upgrade {
  616. program_id,
  617. filepath,
  618. } => idl_upgrade(cfg_override, program_id, filepath),
  619. IdlCommand::SetAuthority {
  620. program_id,
  621. address,
  622. new_authority,
  623. } => idl_set_authority(cfg_override, program_id, address, new_authority),
  624. IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id),
  625. IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
  626. IdlCommand::Parse { file, out } => idl_parse(file, out),
  627. IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
  628. }
  629. }
  630. fn idl_init(cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: String) -> Result<()> {
  631. with_workspace(cfg_override, |cfg, _path, _cargo| {
  632. let keypair = cfg.provider.wallet.to_string();
  633. let bytes = std::fs::read(idl_filepath)?;
  634. let idl: Idl = serde_json::from_reader(&*bytes)?;
  635. let idl_address = create_idl_account(&cfg, &keypair, &program_id, &idl)?;
  636. println!("Idl account created: {:?}", idl_address);
  637. Ok(())
  638. })
  639. }
  640. fn idl_write_buffer(
  641. cfg_override: &ConfigOverride,
  642. program_id: Pubkey,
  643. idl_filepath: String,
  644. ) -> Result<Pubkey> {
  645. with_workspace(cfg_override, |cfg, _path, _cargo| {
  646. let keypair = cfg.provider.wallet.to_string();
  647. let bytes = std::fs::read(idl_filepath)?;
  648. let idl: Idl = serde_json::from_reader(&*bytes)?;
  649. let idl_buffer = create_idl_buffer(&cfg, &keypair, &program_id, &idl)?;
  650. idl_write(&cfg, &program_id, &idl, idl_buffer)?;
  651. println!("Idl buffer created: {:?}", idl_buffer);
  652. Ok(idl_buffer)
  653. })
  654. }
  655. fn idl_set_buffer(cfg_override: &ConfigOverride, program_id: Pubkey, buffer: Pubkey) -> Result<()> {
  656. with_workspace(cfg_override, |cfg, _path, _cargo| {
  657. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  658. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  659. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  660. // Instruction to set the buffer onto the IdlAccount.
  661. let set_buffer_ix = {
  662. let accounts = vec![
  663. AccountMeta::new(buffer, false),
  664. AccountMeta::new(IdlAccount::address(&program_id), false),
  665. AccountMeta::new(keypair.pubkey(), true),
  666. ];
  667. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  668. data.append(&mut IdlInstruction::SetBuffer.try_to_vec()?);
  669. Instruction {
  670. program_id,
  671. accounts,
  672. data,
  673. }
  674. };
  675. // Build the transaction.
  676. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  677. let tx = Transaction::new_signed_with_payer(
  678. &[set_buffer_ix],
  679. Some(&keypair.pubkey()),
  680. &[&keypair],
  681. recent_hash,
  682. );
  683. // Send the transaction.
  684. client.send_and_confirm_transaction_with_spinner_and_config(
  685. &tx,
  686. CommitmentConfig::confirmed(),
  687. RpcSendTransactionConfig {
  688. skip_preflight: true,
  689. ..RpcSendTransactionConfig::default()
  690. },
  691. )?;
  692. Ok(())
  693. })
  694. }
  695. fn idl_upgrade(
  696. cfg_override: &ConfigOverride,
  697. program_id: Pubkey,
  698. idl_filepath: String,
  699. ) -> Result<()> {
  700. let buffer = idl_write_buffer(cfg_override, program_id, idl_filepath)?;
  701. idl_set_buffer(cfg_override, program_id, buffer)
  702. }
  703. fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
  704. with_workspace(cfg_override, |cfg, _path, _cargo| {
  705. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  706. let idl_address = {
  707. let account = client
  708. .get_account_with_commitment(&program_id, CommitmentConfig::processed())?
  709. .value
  710. .map_or(Err(anyhow!("Account not found")), Ok)?;
  711. if account.executable {
  712. IdlAccount::address(&program_id)
  713. } else {
  714. program_id
  715. }
  716. };
  717. let account = client.get_account(&idl_address)?;
  718. let mut data: &[u8] = &account.data;
  719. let idl_account: IdlAccount = AccountDeserialize::try_deserialize(&mut data)?;
  720. println!("{:?}", idl_account.authority);
  721. Ok(())
  722. })
  723. }
  724. fn idl_set_authority(
  725. cfg_override: &ConfigOverride,
  726. program_id: Pubkey,
  727. address: Option<Pubkey>,
  728. new_authority: Pubkey,
  729. ) -> Result<()> {
  730. with_workspace(cfg_override, |cfg, _path, _cargo| {
  731. // Misc.
  732. let idl_address = match address {
  733. None => IdlAccount::address(&program_id),
  734. Some(addr) => addr,
  735. };
  736. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  737. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  738. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  739. // Instruction data.
  740. let data =
  741. serialize_idl_ix(anchor_lang::idl::IdlInstruction::SetAuthority { new_authority })?;
  742. // Instruction accounts.
  743. let accounts = vec![
  744. AccountMeta::new(idl_address, false),
  745. AccountMeta::new_readonly(keypair.pubkey(), true),
  746. ];
  747. // Instruction.
  748. let ix = Instruction {
  749. program_id,
  750. accounts,
  751. data,
  752. };
  753. // Send transaction.
  754. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  755. let tx = Transaction::new_signed_with_payer(
  756. &[ix],
  757. Some(&keypair.pubkey()),
  758. &[&keypair],
  759. recent_hash,
  760. );
  761. client.send_and_confirm_transaction_with_spinner_and_config(
  762. &tx,
  763. CommitmentConfig::confirmed(),
  764. RpcSendTransactionConfig {
  765. skip_preflight: true,
  766. ..RpcSendTransactionConfig::default()
  767. },
  768. )?;
  769. println!("Authority update complete.");
  770. Ok(())
  771. })
  772. }
  773. fn idl_erase_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
  774. println!("Are you sure you want to erase the IDL authority: [y/n]");
  775. let stdin = std::io::stdin();
  776. let mut stdin_lines = stdin.lock().lines();
  777. let input = stdin_lines.next().unwrap().unwrap();
  778. if input != "y" {
  779. println!("Not erasing.");
  780. return Ok(());
  781. }
  782. // Program will treat the zero authority as erased.
  783. let new_authority = Pubkey::new_from_array([0u8; 32]);
  784. idl_set_authority(cfg_override, program_id, None, new_authority)?;
  785. Ok(())
  786. }
  787. // Write the idl to the account buffer, chopping up the IDL into pieces
  788. // and sending multiple transactions in the event the IDL doesn't fit into
  789. // a single transaction.
  790. fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey) -> Result<()> {
  791. // Remove the metadata before deploy.
  792. let mut idl = idl.clone();
  793. idl.metadata = None;
  794. // Misc.
  795. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  796. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  797. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  798. // Serialize and compress the idl.
  799. let idl_data = {
  800. let json_bytes = serde_json::to_vec(&idl)?;
  801. let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
  802. e.write_all(&json_bytes)?;
  803. e.finish()?
  804. };
  805. const MAX_WRITE_SIZE: usize = 1000;
  806. let mut offset = 0;
  807. while offset < idl_data.len() {
  808. // Instruction data.
  809. let data = {
  810. let start = offset;
  811. let end = std::cmp::min(offset + MAX_WRITE_SIZE, idl_data.len());
  812. serialize_idl_ix(anchor_lang::idl::IdlInstruction::Write {
  813. data: idl_data[start..end].to_vec(),
  814. })?
  815. };
  816. // Instruction accounts.
  817. let accounts = vec![
  818. AccountMeta::new(idl_address, false),
  819. AccountMeta::new_readonly(keypair.pubkey(), true),
  820. ];
  821. // Instruction.
  822. let ix = Instruction {
  823. program_id: *program_id,
  824. accounts,
  825. data,
  826. };
  827. // Send transaction.
  828. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  829. let tx = Transaction::new_signed_with_payer(
  830. &[ix],
  831. Some(&keypair.pubkey()),
  832. &[&keypair],
  833. recent_hash,
  834. );
  835. client.send_and_confirm_transaction_with_spinner_and_config(
  836. &tx,
  837. CommitmentConfig::confirmed(),
  838. RpcSendTransactionConfig {
  839. skip_preflight: true,
  840. ..RpcSendTransactionConfig::default()
  841. },
  842. )?;
  843. offset += MAX_WRITE_SIZE;
  844. }
  845. Ok(())
  846. }
  847. fn idl_parse(file: String, out: Option<String>) -> Result<()> {
  848. let idl = extract_idl(&file)?;
  849. let out = match out {
  850. None => OutFile::Stdout,
  851. Some(out) => OutFile::File(PathBuf::from(out)),
  852. };
  853. write_idl(&idl, out)
  854. }
  855. fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
  856. let idl = fetch_idl(cfg_override, address)?;
  857. let out = match out {
  858. None => OutFile::Stdout,
  859. Some(out) => OutFile::File(PathBuf::from(out)),
  860. };
  861. write_idl(&idl, out)
  862. }
  863. fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
  864. let idl_json = serde_json::to_string_pretty(idl)?;
  865. match out {
  866. OutFile::Stdout => println!("{}", idl_json),
  867. OutFile::File(out) => std::fs::write(out, idl_json)?,
  868. };
  869. Ok(())
  870. }
  871. enum OutFile {
  872. Stdout,
  873. File(PathBuf),
  874. }
  875. // Builds, deploys, and tests all workspace programs in a single command.
  876. fn test(
  877. cfg_override: &ConfigOverride,
  878. skip_deploy: bool,
  879. skip_local_validator: bool,
  880. skip_build: bool,
  881. use_yarn: bool,
  882. file: Option<String>,
  883. ) -> Result<()> {
  884. with_workspace(cfg_override, |cfg, _path, _cargo| {
  885. // Build if needed.
  886. if !skip_build {
  887. build(cfg_override, None, false, None)?;
  888. }
  889. // Run the deploy against the cluster in two cases:
  890. //
  891. // 1. The cluster is not localnet.
  892. // 2. The cluster is localnet, but we're not booting a local validator.
  893. //
  894. // In either case, skip the deploy if the user specifies.
  895. let is_localnet = cfg.provider.cluster == Cluster::Localnet;
  896. if !is_localnet || (is_localnet && skip_local_validator) {
  897. if !skip_deploy {
  898. deploy(cfg_override, None)?;
  899. }
  900. }
  901. // Start local test validator, if needed.
  902. let mut validator_handle = None;
  903. if is_localnet && (!skip_local_validator) {
  904. let flags = match skip_deploy {
  905. true => None,
  906. false => Some(genesis_flags(cfg)?),
  907. };
  908. validator_handle = Some(start_test_validator(cfg, flags)?);
  909. }
  910. // Setup log reader.
  911. let log_streams = stream_logs(&cfg.provider.cluster.url());
  912. // Check to see if yarn is installed, panic if not.
  913. if use_yarn {
  914. which::which("yarn").unwrap();
  915. }
  916. // Run the tests.
  917. let test_result: Result<_> = {
  918. let ts_config_exist = Path::new("tsconfig.json").exists();
  919. let mut args = vec!["-t", "1000000"];
  920. if let Some(ref file) = file {
  921. args.push(file);
  922. } else if ts_config_exist {
  923. args.push("tests/**/*.spec.ts");
  924. } else {
  925. args.push("tests/");
  926. }
  927. let exit = match (ts_config_exist, use_yarn) {
  928. (true, true) => std::process::Command::new("yarn")
  929. .arg("ts-mocha")
  930. .arg("-p")
  931. .arg("./tsconfig.json")
  932. .args(args)
  933. .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
  934. .stdout(Stdio::inherit())
  935. .stderr(Stdio::inherit())
  936. .output()
  937. .map_err(anyhow::Error::from)
  938. .with_context(|| "ts-mocha"),
  939. (false, true) => std::process::Command::new("yarn")
  940. .arg("mocha")
  941. .args(args)
  942. .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
  943. .stdout(Stdio::inherit())
  944. .stderr(Stdio::inherit())
  945. .output()
  946. .map_err(anyhow::Error::from)
  947. .with_context(|| "mocha"),
  948. (true, false) => std::process::Command::new("ts-mocha")
  949. .arg("-p")
  950. .arg("./tsconfig.json")
  951. .args(args)
  952. .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
  953. .stdout(Stdio::inherit())
  954. .stderr(Stdio::inherit())
  955. .output()
  956. .map_err(anyhow::Error::from)
  957. .with_context(|| "ts-mocha"),
  958. (false, false) => std::process::Command::new("mocha")
  959. .args(args)
  960. .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
  961. .stdout(Stdio::inherit())
  962. .stderr(Stdio::inherit())
  963. .output()
  964. .map_err(anyhow::Error::from)
  965. .with_context(|| "mocha"),
  966. };
  967. exit
  968. };
  969. // Check all errors and shut down.
  970. if let Some(mut child) = validator_handle {
  971. if let Err(err) = child.kill() {
  972. println!("Failed to kill subprocess {}: {}", child.id(), err);
  973. }
  974. }
  975. for mut child in log_streams? {
  976. if let Err(err) = child.kill() {
  977. println!("Failed to kill subprocess {}: {}", child.id(), err);
  978. }
  979. }
  980. match test_result {
  981. Ok(exit) => {
  982. if !exit.status.success() {
  983. std::process::exit(exit.status.code().unwrap());
  984. }
  985. }
  986. Err(err) => {
  987. println!("Failed to run test: {:#}", err)
  988. }
  989. }
  990. Ok(())
  991. })
  992. }
  993. // Returns the solana-test-validator flags to embed the workspace programs
  994. // in the genesis block. This allows us to run tests without every deploying.
  995. fn genesis_flags(cfg: &Config) -> Result<Vec<String>> {
  996. let mut flags = Vec::new();
  997. for mut program in read_all_programs()? {
  998. let binary_path = program.binary_path().display().to_string();
  999. let kp = Keypair::generate(&mut OsRng);
  1000. let address = kp.pubkey().to_string();
  1001. flags.push("--bpf-program".to_string());
  1002. flags.push(address.clone());
  1003. flags.push(binary_path);
  1004. // Add program address to the IDL.
  1005. program.idl.metadata = Some(serde_json::to_value(IdlTestMetadata { address })?);
  1006. // Persist it.
  1007. let idl_out = PathBuf::from("target/idl")
  1008. .join(&program.idl.name)
  1009. .with_extension("json");
  1010. write_idl(&program.idl, OutFile::File(idl_out))?;
  1011. }
  1012. if let Some(test) = cfg.test.as_ref() {
  1013. for entry in &test.genesis {
  1014. flags.push("--bpf-program".to_string());
  1015. flags.push(entry.address.clone());
  1016. flags.push(entry.program.clone());
  1017. }
  1018. }
  1019. Ok(flags)
  1020. }
  1021. fn stream_logs(url: &str) -> Result<Vec<std::process::Child>> {
  1022. let program_logs_dir = ".anchor/program-logs";
  1023. if Path::new(program_logs_dir).exists() {
  1024. std::fs::remove_dir_all(program_logs_dir)?;
  1025. }
  1026. fs::create_dir_all(program_logs_dir)?;
  1027. let mut handles = vec![];
  1028. for program in read_all_programs()? {
  1029. let mut file = File::open(&format!("target/idl/{}.json", program.lib_name))?;
  1030. let mut contents = vec![];
  1031. file.read_to_end(&mut contents)?;
  1032. let idl: Idl = serde_json::from_slice(&contents)?;
  1033. let metadata = idl
  1034. .metadata
  1035. .ok_or_else(|| anyhow!("Program address not found."))?;
  1036. let metadata: IdlTestMetadata = serde_json::from_value(metadata)?;
  1037. let log_file = File::create(format!(
  1038. "{}/{}.{}.log",
  1039. program_logs_dir, metadata.address, program.idl.name
  1040. ))?;
  1041. let stdio = std::process::Stdio::from(log_file);
  1042. let child = std::process::Command::new("solana")
  1043. .arg("logs")
  1044. .arg(metadata.address)
  1045. .arg("--url")
  1046. .arg(url)
  1047. .stdout(stdio)
  1048. .spawn()?;
  1049. handles.push(child);
  1050. }
  1051. Ok(handles)
  1052. }
  1053. #[derive(Debug, Serialize, Deserialize)]
  1054. pub struct IdlTestMetadata {
  1055. address: String,
  1056. }
  1057. fn start_test_validator(cfg: &Config, flags: Option<Vec<String>>) -> Result<Child> {
  1058. fs::create_dir_all(".anchor")?;
  1059. let test_ledger_filename = ".anchor/test-ledger";
  1060. let test_ledger_log_filename = ".anchor/test-ledger-log.txt";
  1061. if Path::new(test_ledger_filename).exists() {
  1062. std::fs::remove_dir_all(test_ledger_filename)?;
  1063. }
  1064. if Path::new(test_ledger_log_filename).exists() {
  1065. std::fs::remove_file(test_ledger_log_filename)?;
  1066. }
  1067. // Start a validator for testing.
  1068. let test_validator_stdout = File::create(test_ledger_log_filename)?;
  1069. let test_validator_stderr = test_validator_stdout.try_clone()?;
  1070. let validator_handle = std::process::Command::new("solana-test-validator")
  1071. .arg("--ledger")
  1072. .arg(test_ledger_filename)
  1073. .arg("--mint")
  1074. .arg(cfg.wallet_kp()?.pubkey().to_string())
  1075. .args(flags.unwrap_or_default())
  1076. .stdout(Stdio::from(test_validator_stdout))
  1077. .stderr(Stdio::from(test_validator_stderr))
  1078. .spawn()
  1079. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  1080. // Wait for the validator to be ready.
  1081. let client = RpcClient::new("http://localhost:8899".to_string());
  1082. let mut count = 0;
  1083. let ms_wait = 5000;
  1084. while count < ms_wait {
  1085. let r = client.get_recent_blockhash();
  1086. if r.is_ok() {
  1087. break;
  1088. }
  1089. std::thread::sleep(std::time::Duration::from_millis(1));
  1090. count += 1;
  1091. }
  1092. if count == 5000 {
  1093. println!("Unable to start test validator.");
  1094. std::process::exit(1);
  1095. }
  1096. Ok(validator_handle)
  1097. }
  1098. fn deploy(cfg_override: &ConfigOverride, program_name: Option<String>) -> Result<()> {
  1099. _deploy(cfg_override, program_name).map(|_| ())
  1100. }
  1101. fn _deploy(
  1102. cfg_override: &ConfigOverride,
  1103. program_str: Option<String>,
  1104. ) -> Result<Vec<(Pubkey, Program)>> {
  1105. with_workspace(cfg_override, |cfg, _path, _cargo| {
  1106. let url = cfg.provider.cluster.url().to_string();
  1107. let keypair = cfg.provider.wallet.to_string();
  1108. // Deploy the programs.
  1109. println!("Deploying workspace: {}", url);
  1110. println!("Upgrade authority: {}", keypair);
  1111. let mut programs = Vec::new();
  1112. for mut program in read_all_programs()? {
  1113. if let Some(single_prog_str) = &program_str {
  1114. let program_name = program.path.file_name().unwrap().to_str().unwrap();
  1115. if single_prog_str.as_str() != program_name {
  1116. continue;
  1117. }
  1118. }
  1119. let binary_path = program.binary_path().display().to_string();
  1120. println!(
  1121. "Deploying program {:?}...",
  1122. program.path.file_name().unwrap().to_str().unwrap()
  1123. );
  1124. println!("Program path: {}...", binary_path);
  1125. // Write the program's keypair filepath. This forces a new deploy
  1126. // address.
  1127. let program_kp = Keypair::generate(&mut OsRng);
  1128. let mut file = File::create(program.anchor_keypair_path())?;
  1129. file.write_all(format!("{:?}", &program_kp.to_bytes()).as_bytes())?;
  1130. // Send deploy transactions.
  1131. let exit = std::process::Command::new("solana")
  1132. .arg("program")
  1133. .arg("deploy")
  1134. .arg("--url")
  1135. .arg(&url)
  1136. .arg("--keypair")
  1137. .arg(&keypair)
  1138. .arg("--program-id")
  1139. .arg(program.anchor_keypair_path().display().to_string())
  1140. .arg(&binary_path)
  1141. .stdout(Stdio::inherit())
  1142. .stderr(Stdio::inherit())
  1143. .output()
  1144. .expect("Must deploy");
  1145. if !exit.status.success() {
  1146. println!("There was a problem deploying: {:?}.", exit);
  1147. std::process::exit(exit.status.code().unwrap_or(1));
  1148. }
  1149. // Add program address to the IDL.
  1150. program.idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
  1151. address: program_kp.pubkey().to_string(),
  1152. })?);
  1153. // Persist it.
  1154. let idl_out = PathBuf::from("target/idl")
  1155. .join(&program.idl.name)
  1156. .with_extension("json");
  1157. write_idl(&program.idl, OutFile::File(idl_out))?;
  1158. programs.push((program_kp.pubkey(), program))
  1159. }
  1160. println!("Deploy success");
  1161. Ok(programs)
  1162. })
  1163. }
  1164. fn upgrade(
  1165. cfg_override: &ConfigOverride,
  1166. program_id: Pubkey,
  1167. program_filepath: String,
  1168. ) -> Result<()> {
  1169. let path: PathBuf = program_filepath.parse().unwrap();
  1170. let program_filepath = path.canonicalize()?.display().to_string();
  1171. with_workspace(cfg_override, |cfg, _path, _cargo| {
  1172. let exit = std::process::Command::new("solana")
  1173. .arg("program")
  1174. .arg("deploy")
  1175. .arg("--url")
  1176. .arg(cfg.provider.cluster.url())
  1177. .arg("--keypair")
  1178. .arg(&cfg.provider.wallet.to_string())
  1179. .arg("--program-id")
  1180. .arg(program_id.to_string())
  1181. .arg(&program_filepath)
  1182. .stdout(Stdio::inherit())
  1183. .stderr(Stdio::inherit())
  1184. .output()
  1185. .expect("Must deploy");
  1186. if !exit.status.success() {
  1187. println!("There was a problem deploying: {:?}.", exit);
  1188. std::process::exit(exit.status.code().unwrap_or(1));
  1189. }
  1190. Ok(())
  1191. })
  1192. }
  1193. fn launch(
  1194. cfg_override: &ConfigOverride,
  1195. verifiable: bool,
  1196. program_name: Option<String>,
  1197. ) -> Result<()> {
  1198. // Build and deploy.
  1199. build(cfg_override, None, verifiable, program_name.clone())?;
  1200. let programs = _deploy(cfg_override, program_name.clone())?;
  1201. with_workspace(cfg_override, |cfg, _path, _cargo| {
  1202. let keypair = cfg.provider.wallet.to_string();
  1203. // Add metadata to all IDLs.
  1204. for (address, program) in programs {
  1205. // Store the IDL on chain.
  1206. let idl_address = create_idl_account(&cfg, &keypair, &address, &program.idl)?;
  1207. println!("IDL account created: {}", idl_address.to_string());
  1208. }
  1209. // Run migration script.
  1210. if Path::new("migrations/deploy.js").exists() || Path::new("migrations/deploy.ts").exists()
  1211. {
  1212. migrate(cfg_override)?;
  1213. }
  1214. Ok(())
  1215. })
  1216. }
  1217. // The Solana CLI doesn't redeploy a program if this file exists.
  1218. // So remove it to make all commands explicit.
  1219. fn clear_program_keys() -> Result<()> {
  1220. for program in read_all_programs()? {
  1221. let anchor_keypair_path = program.anchor_keypair_path();
  1222. if Path::exists(&anchor_keypair_path) {
  1223. std::fs::remove_file(anchor_keypair_path).expect("Always remove");
  1224. }
  1225. }
  1226. Ok(())
  1227. }
  1228. fn create_idl_account(
  1229. cfg: &Config,
  1230. keypair_path: &str,
  1231. program_id: &Pubkey,
  1232. idl: &Idl,
  1233. ) -> Result<Pubkey> {
  1234. // Misc.
  1235. let idl_address = IdlAccount::address(program_id);
  1236. let keypair = solana_sdk::signature::read_keypair_file(keypair_path)
  1237. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1238. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  1239. let idl_data = serialize_idl(idl)?;
  1240. // Run `Create instruction.
  1241. {
  1242. let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Create {
  1243. data_len: (idl_data.len() as u64) * 2, // Double for future growth.
  1244. })?;
  1245. let program_signer = Pubkey::find_program_address(&[], program_id).0;
  1246. let accounts = vec![
  1247. AccountMeta::new_readonly(keypair.pubkey(), true),
  1248. AccountMeta::new(idl_address, false),
  1249. AccountMeta::new_readonly(program_signer, false),
  1250. AccountMeta::new_readonly(solana_program::system_program::ID, false),
  1251. AccountMeta::new_readonly(*program_id, false),
  1252. AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
  1253. ];
  1254. let ix = Instruction {
  1255. program_id: *program_id,
  1256. accounts,
  1257. data,
  1258. };
  1259. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1260. let tx = Transaction::new_signed_with_payer(
  1261. &[ix],
  1262. Some(&keypair.pubkey()),
  1263. &[&keypair],
  1264. recent_hash,
  1265. );
  1266. client.send_and_confirm_transaction_with_spinner_and_config(
  1267. &tx,
  1268. CommitmentConfig::confirmed(),
  1269. RpcSendTransactionConfig {
  1270. skip_preflight: true,
  1271. ..RpcSendTransactionConfig::default()
  1272. },
  1273. )?;
  1274. }
  1275. // Write directly to the IDL account buffer.
  1276. idl_write(cfg, program_id, idl, IdlAccount::address(program_id))?;
  1277. Ok(idl_address)
  1278. }
  1279. fn create_idl_buffer(
  1280. cfg: &Config,
  1281. keypair_path: &str,
  1282. program_id: &Pubkey,
  1283. idl: &Idl,
  1284. ) -> Result<Pubkey> {
  1285. let keypair = solana_sdk::signature::read_keypair_file(keypair_path)
  1286. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1287. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  1288. let buffer = Keypair::generate(&mut OsRng);
  1289. // Creates the new buffer account with the system program.
  1290. let create_account_ix = {
  1291. let space = 8 + 32 + 4 + serialize_idl(idl)?.len() as usize;
  1292. let lamports = client.get_minimum_balance_for_rent_exemption(space)?;
  1293. solana_sdk::system_instruction::create_account(
  1294. &keypair.pubkey(),
  1295. &buffer.pubkey(),
  1296. lamports,
  1297. space as u64,
  1298. program_id,
  1299. )
  1300. };
  1301. // Program instruction to create the buffer.
  1302. let create_buffer_ix = {
  1303. let accounts = vec![
  1304. AccountMeta::new(buffer.pubkey(), false),
  1305. AccountMeta::new_readonly(keypair.pubkey(), true),
  1306. AccountMeta::new_readonly(sysvar::rent::ID, false),
  1307. ];
  1308. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  1309. data.append(&mut IdlInstruction::CreateBuffer.try_to_vec()?);
  1310. Instruction {
  1311. program_id: *program_id,
  1312. accounts,
  1313. data,
  1314. }
  1315. };
  1316. // Build the transaction.
  1317. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1318. let tx = Transaction::new_signed_with_payer(
  1319. &[create_account_ix, create_buffer_ix],
  1320. Some(&keypair.pubkey()),
  1321. &[&keypair, &buffer],
  1322. recent_hash,
  1323. );
  1324. // Send the transaction.
  1325. client.send_and_confirm_transaction_with_spinner_and_config(
  1326. &tx,
  1327. CommitmentConfig::confirmed(),
  1328. RpcSendTransactionConfig {
  1329. skip_preflight: true,
  1330. ..RpcSendTransactionConfig::default()
  1331. },
  1332. )?;
  1333. Ok(buffer.pubkey())
  1334. }
  1335. // Serialize and compress the idl.
  1336. fn serialize_idl(idl: &Idl) -> Result<Vec<u8>> {
  1337. let json_bytes = serde_json::to_vec(idl)?;
  1338. let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
  1339. e.write_all(&json_bytes)?;
  1340. e.finish().map_err(Into::into)
  1341. }
  1342. fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8>> {
  1343. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  1344. data.append(&mut ix_inner.try_to_vec()?);
  1345. Ok(data)
  1346. }
  1347. fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
  1348. with_workspace(cfg_override, |cfg, _path, _cargo| {
  1349. println!("Running migration deploy script");
  1350. let url = cfg.provider.cluster.url().to_string();
  1351. let cur_dir = std::env::current_dir()?;
  1352. let module_path = cur_dir.join("migrations/deploy.js");
  1353. let ts_config_exist = Path::new("tsconfig.json").exists();
  1354. let ts_deploy_file_exists = Path::new("migrations/deploy.ts").exists();
  1355. if ts_config_exist && ts_deploy_file_exists {
  1356. let ts_module_path = cur_dir.join("migrations/deploy.ts");
  1357. let exit = std::process::Command::new("tsc")
  1358. .arg(&ts_module_path)
  1359. .stdout(Stdio::inherit())
  1360. .stderr(Stdio::inherit())
  1361. .output()?;
  1362. if !exit.status.success() {
  1363. std::process::exit(exit.status.code().unwrap());
  1364. }
  1365. };
  1366. let deploy_script_host_str =
  1367. template::deploy_script_host(&url, &module_path.display().to_string());
  1368. if !Path::new(".anchor").exists() {
  1369. fs::create_dir(".anchor")?;
  1370. }
  1371. std::env::set_current_dir(".anchor")?;
  1372. std::fs::write("deploy.js", deploy_script_host_str)?;
  1373. let exit = std::process::Command::new("node")
  1374. .arg("deploy.js")
  1375. .stdout(Stdio::inherit())
  1376. .stderr(Stdio::inherit())
  1377. .output()?;
  1378. if ts_config_exist && ts_deploy_file_exists {
  1379. std::fs::remove_file(&module_path)
  1380. .map_err(|_| anyhow!("Unable to remove file {}", module_path.display()))?;
  1381. }
  1382. if !exit.status.success() {
  1383. println!("Deploy failed.");
  1384. std::process::exit(exit.status.code().unwrap());
  1385. }
  1386. println!("Deploy complete.");
  1387. Ok(())
  1388. })
  1389. }
  1390. fn set_workspace_dir_or_exit() {
  1391. let d = match Config::discover(&ConfigOverride {
  1392. cluster: None,
  1393. wallet: None,
  1394. }) {
  1395. Err(_) => {
  1396. println!("Not in anchor workspace.");
  1397. std::process::exit(1);
  1398. }
  1399. Ok(d) => d,
  1400. };
  1401. match d {
  1402. None => {
  1403. println!("Not in anchor workspace.");
  1404. std::process::exit(1);
  1405. }
  1406. Some((_cfg, cfg_path, _inside_cargo)) => {
  1407. match cfg_path.parent() {
  1408. None => {
  1409. println!("Unable to make new program");
  1410. }
  1411. Some(parent) => {
  1412. if std::env::set_current_dir(&parent).is_err() {
  1413. println!("Not in anchor workspace.");
  1414. std::process::exit(1);
  1415. }
  1416. }
  1417. };
  1418. }
  1419. }
  1420. }
  1421. #[cfg(feature = "dev")]
  1422. fn airdrop(cfg_override: &ConfigOverride) -> Result<()> {
  1423. let url = cfg_override
  1424. .cluster
  1425. .unwrap_or_else(|| "https://api.devnet.solana.com".to_string());
  1426. loop {
  1427. let exit = std::process::Command::new("solana")
  1428. .arg("airdrop")
  1429. .arg("10")
  1430. .arg("--url")
  1431. .arg(&url)
  1432. .stdout(Stdio::inherit())
  1433. .stderr(Stdio::inherit())
  1434. .output()
  1435. .expect("Must airdrop");
  1436. if !exit.status.success() {
  1437. println!("There was a problem airdropping: {:?}.", exit);
  1438. std::process::exit(exit.status.code().unwrap_or(1));
  1439. }
  1440. std::thread::sleep(std::time::Duration::from_millis(10000));
  1441. }
  1442. }
  1443. fn cluster(_cmd: ClusterCommand) -> Result<()> {
  1444. println!("Cluster Endpoints:\n");
  1445. println!("* Mainnet - https://solana-api.projectserum.com");
  1446. println!("* Mainnet - https://api.mainnet-beta.solana.com");
  1447. println!("* Devnet - https://api.devnet.solana.com");
  1448. println!("* Testnet - https://api.testnet.solana.com");
  1449. Ok(())
  1450. }
  1451. fn shell(cfg_override: &ConfigOverride) -> Result<()> {
  1452. with_workspace(cfg_override, |cfg, _path, _cargo| {
  1453. let programs = {
  1454. let mut idls: HashMap<String, Idl> = read_all_programs()?
  1455. .iter()
  1456. .map(|program| (program.idl.name.clone(), program.idl.clone()))
  1457. .collect();
  1458. // Insert all manually specified idls into the idl map.
  1459. cfg.clusters.get(&cfg.provider.cluster).map(|programs| {
  1460. let _ = programs
  1461. .iter()
  1462. .map(|(name, pd)| {
  1463. if let Some(idl_fp) = &pd.idl {
  1464. let file_str =
  1465. std::fs::read_to_string(idl_fp).expect("Unable to read IDL file");
  1466. let idl = serde_json::from_str(&file_str).expect("Idl not readable");
  1467. idls.insert(name.clone(), idl);
  1468. }
  1469. })
  1470. .collect::<Vec<_>>();
  1471. });
  1472. match cfg.clusters.get(&cfg.provider.cluster) {
  1473. None => Vec::new(),
  1474. Some(programs) => programs
  1475. .iter()
  1476. .map(|(name, program_deployment)| ProgramWorkspace {
  1477. name: name.to_string(),
  1478. program_id: program_deployment.address,
  1479. idl: match idls.get(name) {
  1480. None => {
  1481. println!("Unable to find IDL for {}", name);
  1482. std::process::exit(1);
  1483. }
  1484. Some(idl) => idl.clone(),
  1485. },
  1486. })
  1487. .collect::<Vec<ProgramWorkspace>>(),
  1488. }
  1489. };
  1490. let js_code = template::node_shell(
  1491. cfg.provider.cluster.url(),
  1492. &cfg.provider.wallet.to_string(),
  1493. programs,
  1494. )?;
  1495. let mut child = std::process::Command::new("node")
  1496. .args(&["-e", &js_code, "-i", "--experimental-repl-await"])
  1497. .stdout(Stdio::inherit())
  1498. .stderr(Stdio::inherit())
  1499. .spawn()
  1500. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  1501. if !child.wait()?.success() {
  1502. println!("Error running node shell");
  1503. return Ok(());
  1504. }
  1505. Ok(())
  1506. })
  1507. }
  1508. fn run(cfg_override: &ConfigOverride, script: String) -> Result<()> {
  1509. with_workspace(cfg_override, |cfg, _path, _cargo| {
  1510. let script = cfg
  1511. .scripts
  1512. .get(&script)
  1513. .ok_or(anyhow!("Unable to find script"))?;
  1514. let exit = std::process::Command::new("bash")
  1515. .arg("-c")
  1516. .arg(&script)
  1517. .stdout(Stdio::inherit())
  1518. .stderr(Stdio::inherit())
  1519. .output()
  1520. .unwrap();
  1521. if !exit.status.success() {
  1522. std::process::exit(exit.status.code().unwrap_or(1));
  1523. }
  1524. Ok(())
  1525. })
  1526. }
  1527. // with_workspace ensures the current working directory is always the top level
  1528. // workspace directory, i.e., where the `Anchor.toml` file is located, before
  1529. // and after the closure invocation.
  1530. //
  1531. // The closure passed into this function must never change the working directory
  1532. // to be outside the workspace. Doing so will have undefined behavior.
  1533. fn with_workspace<R>(
  1534. cfg_override: &ConfigOverride,
  1535. f: impl FnOnce(&Config, PathBuf, Option<PathBuf>) -> R,
  1536. ) -> R {
  1537. set_workspace_dir_or_exit();
  1538. clear_program_keys().unwrap();
  1539. let (cfg, cfg_path, cargo_toml) = Config::discover(cfg_override)
  1540. .expect("Previously set the workspace dir")
  1541. .expect("Anchor.toml must always exist");
  1542. let r = f(&cfg, cfg_path, cargo_toml);
  1543. set_workspace_dir_or_exit();
  1544. clear_program_keys().unwrap();
  1545. r
  1546. }