main.rs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. use crate::config::{read_all_programs, Config, Program};
  2. use anchor_lang::idl::IdlAccount;
  3. use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
  4. use anchor_syn::idl::Idl;
  5. use anyhow::{anyhow, Result};
  6. use clap::Clap;
  7. use flate2::read::ZlibDecoder;
  8. use flate2::write::ZlibEncoder;
  9. use flate2::Compression;
  10. use rand::rngs::OsRng;
  11. use serde::{Deserialize, Serialize};
  12. use solana_client::rpc_client::RpcClient;
  13. use solana_client::rpc_config::RpcSendTransactionConfig;
  14. use solana_program::instruction::{AccountMeta, Instruction};
  15. use solana_sdk::commitment_config::CommitmentConfig;
  16. use solana_sdk::pubkey::Pubkey;
  17. use solana_sdk::signature::Keypair;
  18. use solana_sdk::signature::Signer;
  19. use solana_sdk::transaction::Transaction;
  20. use std::fs::{self, File};
  21. use std::io::prelude::*;
  22. use std::path::{Path, PathBuf};
  23. use std::process::{Child, Stdio};
  24. use std::string::ToString;
  25. mod config;
  26. mod template;
  27. #[derive(Debug, Clap)]
  28. pub struct Opts {
  29. #[clap(subcommand)]
  30. pub command: Command,
  31. }
  32. #[derive(Debug, Clap)]
  33. pub enum Command {
  34. /// Initializes a workspace.
  35. Init { name: String },
  36. /// Builds the workspace.
  37. Build {
  38. /// Output directory for the IDL.
  39. #[clap(short, long)]
  40. idl: Option<String>,
  41. },
  42. /// Runs integration tests against a localnetwork.
  43. Test {
  44. /// Use this flag if you want to run tests against previously deployed
  45. /// programs.
  46. #[clap(short, long)]
  47. skip_deploy: bool,
  48. },
  49. /// Creates a new program.
  50. New { name: String },
  51. /// Commands for interacting with interface definitions.
  52. Idl {
  53. #[clap(subcommand)]
  54. subcmd: IdlCommand,
  55. },
  56. /// Deploys each program in the workspace.
  57. Deploy {
  58. #[clap(short, long)]
  59. url: Option<String>,
  60. #[clap(short, long)]
  61. keypair: Option<String>,
  62. },
  63. /// Runs the deploy migration script.
  64. Migrate {
  65. #[clap(short, long)]
  66. url: Option<String>,
  67. },
  68. /// Deploys, initializes an IDL, and migrates all in one command.
  69. Launch {
  70. #[clap(short, long)]
  71. url: Option<String>,
  72. #[clap(short, long)]
  73. keypair: Option<String>,
  74. },
  75. /// Upgrades a single program. The configured wallet must be the upgrade
  76. /// authority.
  77. Upgrade {
  78. /// The program to upgrade.
  79. #[clap(short, long)]
  80. program_id: Pubkey,
  81. /// Filepath to the new program binary.
  82. program_filepath: String,
  83. },
  84. /// Runs an airdrop loop, continuously funding the configured wallet.
  85. Airdrop {
  86. #[clap(short, long)]
  87. url: Option<String>,
  88. },
  89. }
  90. #[derive(Debug, Clap)]
  91. pub enum IdlCommand {
  92. /// Initializes a program's IDL account. Can only be run once.
  93. Init {
  94. program_id: Pubkey,
  95. #[clap(short, long)]
  96. filepath: String,
  97. },
  98. /// Upgrades the IDL to the new file.
  99. Upgrade {
  100. program_id: Pubkey,
  101. #[clap(short, long)]
  102. filepath: String,
  103. },
  104. /// Sets a new authority on the IDL account.
  105. SetAuthority {
  106. /// Program to change the IDL authority.
  107. #[clap(short, long)]
  108. program_id: Pubkey,
  109. /// New authority of the IDL account.
  110. #[clap(short, long)]
  111. new_authority: Pubkey,
  112. },
  113. /// Command to remove the ability to modify the IDL account. This should
  114. /// likely be used in conjection with eliminating an "upgrade authority" on
  115. /// the program.
  116. EraseAuthority {
  117. #[clap(short, long)]
  118. program_id: Pubkey,
  119. },
  120. /// Outputs the authority for the IDL account.
  121. Authority {
  122. /// The program to view.
  123. program_id: Pubkey,
  124. },
  125. /// Parses an IDL from source.
  126. Parse {
  127. /// Path to the program's interface definition.
  128. #[clap(short, long)]
  129. file: String,
  130. /// Output file for the idl (stdout if not specified).
  131. #[clap(short, long)]
  132. out: Option<String>,
  133. },
  134. /// Fetches an IDL for the given program from a cluster.
  135. Fetch {
  136. program_id: Pubkey,
  137. /// Output file for the idl (stdout if not specified).
  138. #[clap(short, long)]
  139. out: Option<String>,
  140. },
  141. }
  142. fn main() -> Result<()> {
  143. let opts = Opts::parse();
  144. match opts.command {
  145. Command::Init { name } => init(name),
  146. Command::New { name } => new(name),
  147. Command::Build { idl } => build(idl),
  148. Command::Deploy { url, keypair } => deploy(url, keypair),
  149. Command::Upgrade {
  150. program_id,
  151. program_filepath,
  152. } => upgrade(program_id, program_filepath),
  153. Command::Idl { subcmd } => idl(subcmd),
  154. Command::Migrate { url } => migrate(url),
  155. Command::Launch { url, keypair } => launch(url, keypair),
  156. Command::Test { skip_deploy } => test(skip_deploy),
  157. Command::Airdrop { url } => airdrop(url),
  158. }
  159. }
  160. fn init(name: String) -> Result<()> {
  161. let cfg = Config::discover()?;
  162. if cfg.is_some() {
  163. println!("Anchor workspace already initialized");
  164. }
  165. fs::create_dir(name.clone())?;
  166. std::env::set_current_dir(&name)?;
  167. fs::create_dir("app")?;
  168. let cfg = Config::default();
  169. let toml = cfg.to_string();
  170. let mut file = File::create("Anchor.toml")?;
  171. file.write_all(toml.as_bytes())?;
  172. // Build virtual manifest.
  173. let mut virt_manifest = File::create("Cargo.toml")?;
  174. virt_manifest.write_all(template::virtual_manifest().as_bytes())?;
  175. // Build the program.
  176. fs::create_dir("programs")?;
  177. new_program(&name)?;
  178. // Build the test suite.
  179. fs::create_dir("tests")?;
  180. let mut mocha = File::create(&format!("tests/{}.js", name))?;
  181. mocha.write_all(template::mocha(&name).as_bytes())?;
  182. // Build the migrations directory.
  183. fs::create_dir("migrations")?;
  184. let mut deploy = File::create("migrations/deploy.js")?;
  185. deploy.write_all(&template::deploy_script().as_bytes())?;
  186. println!("{} initialized", name);
  187. Ok(())
  188. }
  189. // Creates a new program crate in the `programs/<name>` directory.
  190. fn new(name: String) -> Result<()> {
  191. with_workspace(|_cfg, path, _cargo| {
  192. match path.parent() {
  193. None => {
  194. println!("Unable to make new program");
  195. }
  196. Some(parent) => {
  197. std::env::set_current_dir(&parent)?;
  198. new_program(&name)?;
  199. println!("Created new program.");
  200. }
  201. };
  202. Ok(())
  203. })
  204. }
  205. // Creates a new program crate in the current directory with `name`.
  206. fn new_program(name: &str) -> Result<()> {
  207. fs::create_dir(&format!("programs/{}", name))?;
  208. fs::create_dir(&format!("programs/{}/src/", name))?;
  209. let mut cargo_toml = File::create(&format!("programs/{}/Cargo.toml", name))?;
  210. cargo_toml.write_all(template::cargo_toml(&name).as_bytes())?;
  211. let mut xargo_toml = File::create(&format!("programs/{}/Xargo.toml", name))?;
  212. xargo_toml.write_all(template::xargo_toml().as_bytes())?;
  213. let mut lib_rs = File::create(&format!("programs/{}/src/lib.rs", name))?;
  214. lib_rs.write_all(template::lib_rs(&name).as_bytes())?;
  215. Ok(())
  216. }
  217. fn build(idl: Option<String>) -> Result<()> {
  218. let (cfg, path, cargo) = Config::discover()?.expect("Not in workspace.");
  219. let idl_out = match idl {
  220. Some(idl) => Some(PathBuf::from(idl)),
  221. None => {
  222. let cfg_parent = match path.parent() {
  223. None => return Err(anyhow!("Invalid Anchor.toml")),
  224. Some(parent) => parent,
  225. };
  226. fs::create_dir_all(cfg_parent.join("target/idl"))?;
  227. Some(cfg_parent.join("target/idl"))
  228. }
  229. };
  230. match cargo {
  231. None => build_all(&cfg, path, idl_out)?,
  232. Some(ct) => build_cwd(ct, idl_out)?,
  233. };
  234. set_workspace_dir_or_exit();
  235. Ok(())
  236. }
  237. fn build_all(_cfg: &Config, cfg_path: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
  238. match cfg_path.parent() {
  239. None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
  240. Some(parent) => {
  241. let files = fs::read_dir(parent.join("programs"))?;
  242. for f in files {
  243. let p = f?.path();
  244. build_cwd(p.join("Cargo.toml"), idl_out.clone())?;
  245. }
  246. Ok(())
  247. }
  248. }
  249. }
  250. // Runs the build command outside of a workspace.
  251. fn build_cwd(cargo_toml: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
  252. match cargo_toml.parent() {
  253. None => return Err(anyhow!("Unable to find parent")),
  254. Some(p) => std::env::set_current_dir(&p)?,
  255. };
  256. let exit = std::process::Command::new("cargo")
  257. .arg("build-bpf")
  258. .stdout(Stdio::inherit())
  259. .stderr(Stdio::inherit())
  260. .output()
  261. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  262. if !exit.status.success() {
  263. std::process::exit(exit.status.code().unwrap_or(1));
  264. }
  265. // Always assume idl is located ar src/lib.rs.
  266. let idl = extract_idl("src/lib.rs")?;
  267. let out = match idl_out {
  268. None => PathBuf::from(".").join(&idl.name).with_extension("json"),
  269. Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")),
  270. };
  271. write_idl(&idl, OutFile::File(out))
  272. }
  273. // Fetches an IDL for the given program_id.
  274. fn fetch_idl(program_id: Pubkey) -> Result<Idl> {
  275. let cfg = Config::discover()?.expect("Inside a workspace").0;
  276. let client = RpcClient::new(cfg.cluster.url().to_string());
  277. let idl_addr = IdlAccount::address(&program_id);
  278. let account = client
  279. .get_account_with_commitment(&idl_addr, CommitmentConfig::recent())?
  280. .value
  281. .map_or(Err(anyhow!("Account not found")), Ok)?;
  282. // Cut off account discriminator.
  283. let mut d: &[u8] = &account.data[8..];
  284. let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?;
  285. let mut z = ZlibDecoder::new(&idl_account.data[..]);
  286. let mut s = Vec::new();
  287. z.read_to_end(&mut s)?;
  288. serde_json::from_slice(&s[..]).map_err(Into::into)
  289. }
  290. fn extract_idl(file: &str) -> Result<Idl> {
  291. let file = shellexpand::tilde(file);
  292. anchor_syn::parser::file::parse(&*file)
  293. }
  294. fn idl(subcmd: IdlCommand) -> Result<()> {
  295. match subcmd {
  296. IdlCommand::Init {
  297. program_id,
  298. filepath,
  299. } => idl_init(program_id, filepath),
  300. IdlCommand::Upgrade {
  301. program_id,
  302. filepath,
  303. } => idl_upgrade(program_id, filepath),
  304. IdlCommand::SetAuthority {
  305. program_id,
  306. new_authority,
  307. } => idl_set_authority(program_id, new_authority),
  308. IdlCommand::EraseAuthority { program_id } => idl_erase_authority(program_id),
  309. IdlCommand::Authority { program_id } => idl_authority(program_id),
  310. IdlCommand::Parse { file, out } => idl_parse(file, out),
  311. IdlCommand::Fetch { program_id, out } => idl_fetch(program_id, out),
  312. }
  313. }
  314. fn idl_init(program_id: Pubkey, idl_filepath: String) -> Result<()> {
  315. with_workspace(|cfg, _path, _cargo| {
  316. let keypair = cfg.wallet.to_string();
  317. let bytes = std::fs::read(idl_filepath)?;
  318. let idl: Idl = serde_json::from_reader(&*bytes)?;
  319. let idl_address = create_idl_account(&cfg, &keypair, &program_id, &idl)?;
  320. println!("Idl account created: {:?}", idl_address);
  321. Ok(())
  322. })
  323. }
  324. fn idl_upgrade(program_id: Pubkey, idl_filepath: String) -> Result<()> {
  325. with_workspace(|cfg, _path, _cargo| {
  326. let bytes = std::fs::read(idl_filepath)?;
  327. let idl: Idl = serde_json::from_reader(&*bytes)?;
  328. idl_clear(cfg, &program_id)?;
  329. idl_write(cfg, &program_id, &idl)?;
  330. Ok(())
  331. })
  332. }
  333. fn idl_authority(program_id: Pubkey) -> Result<()> {
  334. with_workspace(|cfg, _path, _cargo| {
  335. let client = RpcClient::new(cfg.cluster.url().to_string());
  336. let idl_address = IdlAccount::address(&program_id);
  337. let account = client.get_account(&idl_address)?;
  338. let mut data: &[u8] = &account.data;
  339. let idl_account: IdlAccount = AccountDeserialize::try_deserialize(&mut data)?;
  340. println!("{:?}", idl_account.authority);
  341. Ok(())
  342. })
  343. }
  344. fn idl_set_authority(program_id: Pubkey, new_authority: Pubkey) -> Result<()> {
  345. with_workspace(|cfg, _path, _cargo| {
  346. // Misc.
  347. let idl_address = IdlAccount::address(&program_id);
  348. let keypair = solana_sdk::signature::read_keypair_file(&cfg.wallet.to_string())
  349. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  350. let client = RpcClient::new(cfg.cluster.url().to_string());
  351. // Instruction data.
  352. let data =
  353. serialize_idl_ix(anchor_lang::idl::IdlInstruction::SetAuthority { new_authority })?;
  354. // Instruction accounts.
  355. let accounts = vec![
  356. AccountMeta::new(idl_address, false),
  357. AccountMeta::new_readonly(keypair.pubkey(), true),
  358. ];
  359. // Instruction.
  360. let ix = Instruction {
  361. program_id,
  362. accounts,
  363. data,
  364. };
  365. // Send transaction.
  366. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  367. let tx = Transaction::new_signed_with_payer(
  368. &[ix],
  369. Some(&keypair.pubkey()),
  370. &[&keypair],
  371. recent_hash,
  372. );
  373. client.send_and_confirm_transaction_with_spinner_and_config(
  374. &tx,
  375. CommitmentConfig::single(),
  376. RpcSendTransactionConfig {
  377. skip_preflight: true,
  378. ..RpcSendTransactionConfig::default()
  379. },
  380. )?;
  381. println!("Authority update complete.");
  382. Ok(())
  383. })
  384. }
  385. fn idl_erase_authority(program_id: Pubkey) -> Result<()> {
  386. println!("Are you sure you want to erase the IDL authority: [y/n]");
  387. let stdin = std::io::stdin();
  388. let mut stdin_lines = stdin.lock().lines();
  389. let input = stdin_lines.next().unwrap().unwrap();
  390. if input != "y" {
  391. println!("Not erasing.");
  392. return Ok(());
  393. }
  394. // Program will treat the zero authority as erased.
  395. let new_authority = Pubkey::new_from_array([0u8; 32]);
  396. idl_set_authority(program_id, new_authority)?;
  397. Ok(())
  398. }
  399. // Clears out *all* IDL data. The authority for the IDL must be the configured
  400. // wallet.
  401. fn idl_clear(cfg: &Config, program_id: &Pubkey) -> Result<()> {
  402. let idl_address = IdlAccount::address(program_id);
  403. let keypair = solana_sdk::signature::read_keypair_file(&cfg.wallet.to_string())
  404. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  405. let client = RpcClient::new(cfg.cluster.url().to_string());
  406. let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Clear)?;
  407. let accounts = vec![
  408. AccountMeta::new(idl_address, false),
  409. AccountMeta::new_readonly(keypair.pubkey(), true),
  410. ];
  411. let ix = Instruction {
  412. program_id: *program_id,
  413. accounts,
  414. data,
  415. };
  416. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  417. let tx = Transaction::new_signed_with_payer(
  418. &[ix],
  419. Some(&keypair.pubkey()),
  420. &[&keypair],
  421. recent_hash,
  422. );
  423. client.send_and_confirm_transaction_with_spinner_and_config(
  424. &tx,
  425. CommitmentConfig::single(),
  426. RpcSendTransactionConfig {
  427. skip_preflight: true,
  428. ..RpcSendTransactionConfig::default()
  429. },
  430. )?;
  431. Ok(())
  432. }
  433. // Write the idl to the account buffer, chopping up the IDL into pieces
  434. // and sending multiple transactions in the event the IDL doesn't fit into
  435. // a single transaction.
  436. fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl) -> Result<()> {
  437. // Misc.
  438. let idl_address = IdlAccount::address(program_id);
  439. let keypair = solana_sdk::signature::read_keypair_file(&cfg.wallet.to_string())
  440. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  441. let client = RpcClient::new(cfg.cluster.url().to_string());
  442. // Serialize and compress the idl.
  443. let idl_data = {
  444. let json_bytes = serde_json::to_vec(idl)?;
  445. let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
  446. e.write_all(&json_bytes)?;
  447. e.finish()?
  448. };
  449. const MAX_WRITE_SIZE: usize = 1000;
  450. let mut offset = 0;
  451. while offset < idl_data.len() {
  452. // Instruction data.
  453. let data = {
  454. let start = offset;
  455. let end = std::cmp::min(offset + MAX_WRITE_SIZE, idl_data.len());
  456. serialize_idl_ix(anchor_lang::idl::IdlInstruction::Write {
  457. data: idl_data[start..end].to_vec(),
  458. })?
  459. };
  460. // Instruction accounts.
  461. let accounts = vec![
  462. AccountMeta::new(idl_address, false),
  463. AccountMeta::new_readonly(keypair.pubkey(), true),
  464. ];
  465. // Instruction.
  466. let ix = Instruction {
  467. program_id: *program_id,
  468. accounts,
  469. data,
  470. };
  471. // Send transaction.
  472. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  473. let tx = Transaction::new_signed_with_payer(
  474. &[ix],
  475. Some(&keypair.pubkey()),
  476. &[&keypair],
  477. recent_hash,
  478. );
  479. client.send_and_confirm_transaction_with_spinner_and_config(
  480. &tx,
  481. CommitmentConfig::single(),
  482. RpcSendTransactionConfig {
  483. skip_preflight: true,
  484. ..RpcSendTransactionConfig::default()
  485. },
  486. )?;
  487. offset += MAX_WRITE_SIZE;
  488. }
  489. Ok(())
  490. }
  491. fn idl_parse(file: String, out: Option<String>) -> Result<()> {
  492. let idl = extract_idl(&file)?;
  493. let out = match out {
  494. None => OutFile::Stdout,
  495. Some(out) => OutFile::File(PathBuf::from(out)),
  496. };
  497. write_idl(&idl, out)
  498. }
  499. fn idl_fetch(program_id: Pubkey, out: Option<String>) -> Result<()> {
  500. let idl = fetch_idl(program_id)?;
  501. let out = match out {
  502. None => OutFile::Stdout,
  503. Some(out) => OutFile::File(PathBuf::from(out)),
  504. };
  505. write_idl(&idl, out)
  506. }
  507. fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
  508. let idl_json = serde_json::to_string_pretty(idl)?;
  509. match out {
  510. OutFile::Stdout => println!("{}", idl_json),
  511. OutFile::File(out) => std::fs::write(out, idl_json)?,
  512. };
  513. Ok(())
  514. }
  515. enum OutFile {
  516. Stdout,
  517. File(PathBuf),
  518. }
  519. // Builds, deploys, and tests all workspace programs in a single command.
  520. fn test(skip_deploy: bool) -> Result<()> {
  521. with_workspace(|cfg, _path, _cargo| {
  522. // Bootup validator, if needed.
  523. let validator_handle = match cfg.cluster.url() {
  524. "http://127.0.0.1:8899" => Some(start_test_validator()?),
  525. _ => None,
  526. };
  527. // Deploy programs.
  528. if !skip_deploy {
  529. deploy(None, None)?;
  530. }
  531. // Run the tests.
  532. if let Err(e) = std::process::Command::new("mocha")
  533. .arg("-t")
  534. .arg("1000000")
  535. .arg("tests/")
  536. .env("ANCHOR_PROVIDER_URL", cfg.cluster.url())
  537. .stdout(Stdio::inherit())
  538. .stderr(Stdio::inherit())
  539. .output()
  540. {
  541. if let Some(mut validator_handle) = validator_handle {
  542. validator_handle.kill()?;
  543. }
  544. return Err(anyhow::format_err!("{}", e.to_string()));
  545. }
  546. if let Some(mut validator_handle) = validator_handle {
  547. validator_handle.kill()?;
  548. }
  549. Ok(())
  550. })
  551. }
  552. #[derive(Debug, Serialize, Deserialize)]
  553. pub struct IdlTestMetadata {
  554. address: String,
  555. }
  556. fn start_test_validator() -> Result<Child> {
  557. fs::create_dir_all(".anchor")?;
  558. let test_ledger_filename = ".anchor/test-ledger";
  559. let test_ledger_log_filename = ".anchor/test-ledger-log.txt";
  560. if Path::new(test_ledger_filename).exists() {
  561. std::fs::remove_dir_all(test_ledger_filename)?;
  562. }
  563. if Path::new(test_ledger_log_filename).exists() {
  564. std::fs::remove_file(test_ledger_log_filename)?;
  565. }
  566. // Start a validator for testing.
  567. let test_validator_stdout = File::create(test_ledger_log_filename)?;
  568. let test_validator_stderr = test_validator_stdout.try_clone()?;
  569. let validator_handle = std::process::Command::new("solana-test-validator")
  570. .arg("--ledger")
  571. .arg(test_ledger_filename)
  572. .stdout(Stdio::from(test_validator_stdout))
  573. .stderr(Stdio::from(test_validator_stderr))
  574. .spawn()
  575. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  576. // Wait for the validator to be ready.
  577. let client = RpcClient::new("http://localhost:8899".to_string());
  578. let mut count = 0;
  579. let ms_wait = 5000;
  580. while count < ms_wait {
  581. let r = client.get_recent_blockhash();
  582. if r.is_ok() {
  583. break;
  584. }
  585. std::thread::sleep(std::time::Duration::from_millis(1));
  586. count += 1;
  587. }
  588. if count == 5000 {
  589. println!("Unable to start test validator.");
  590. std::process::exit(1);
  591. }
  592. Ok(validator_handle)
  593. }
  594. // TODO: Testing and deploys should use separate sections of metadata.
  595. // Similarly, each network should have separate metadata.
  596. fn deploy(url: Option<String>, keypair: Option<String>) -> Result<()> {
  597. _deploy(url, keypair).map(|_| ())
  598. }
  599. fn _deploy(url: Option<String>, keypair: Option<String>) -> Result<Vec<(Pubkey, Program)>> {
  600. with_workspace(|cfg, _path, _cargo| {
  601. build(None)?;
  602. // Fallback to config vars if not provided via CLI.
  603. let url = url.unwrap_or(cfg.cluster.url().to_string());
  604. let keypair = keypair.unwrap_or(cfg.wallet.to_string());
  605. // Deploy the programs.
  606. println!("Deploying workspace: {}", url);
  607. println!("Upgrade authority: {}", keypair);
  608. let mut programs = Vec::new();
  609. for mut program in read_all_programs()? {
  610. let binary_path = program.binary_path().display().to_string();
  611. println!("Deploying {}...", binary_path);
  612. // Write the program's keypair filepath. This forces a new deploy
  613. // address.
  614. let program_kp = Keypair::generate(&mut OsRng);
  615. let mut file = File::create(program.anchor_keypair_path())?;
  616. file.write_all(format!("{:?}", &program_kp.to_bytes()).as_bytes())?;
  617. // Send deploy transactions.
  618. let exit = std::process::Command::new("solana")
  619. .arg("program")
  620. .arg("deploy")
  621. .arg("--url")
  622. .arg(&url)
  623. .arg("--keypair")
  624. .arg(&keypair)
  625. .arg("--program-id")
  626. .arg(program.anchor_keypair_path().display().to_string())
  627. .arg(&binary_path)
  628. .stdout(Stdio::inherit())
  629. .stderr(Stdio::inherit())
  630. .output()
  631. .expect("Must deploy");
  632. if !exit.status.success() {
  633. println!("There was a problem deploying: {:?}.", exit);
  634. std::process::exit(exit.status.code().unwrap_or(1));
  635. }
  636. // Add program address to the IDL.
  637. program.idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
  638. address: program_kp.pubkey().to_string(),
  639. })?);
  640. // Persist it.
  641. let idl_out = PathBuf::from("target/idl")
  642. .join(&program.idl.name)
  643. .with_extension("json");
  644. write_idl(&program.idl, OutFile::File(idl_out))?;
  645. programs.push((program_kp.pubkey(), program))
  646. }
  647. println!("Deploy success");
  648. Ok(programs)
  649. })
  650. }
  651. fn upgrade(program_id: Pubkey, program_filepath: String) -> Result<()> {
  652. let path: PathBuf = program_filepath.parse().unwrap();
  653. let program_filepath = path.canonicalize()?.display().to_string();
  654. with_workspace(|cfg, _path, _cargo| {
  655. let exit = std::process::Command::new("solana")
  656. .arg("program")
  657. .arg("deploy")
  658. .arg("--url")
  659. .arg(cfg.cluster.url())
  660. .arg("--keypair")
  661. .arg(&cfg.wallet.to_string())
  662. .arg("--program-id")
  663. .arg(program_id.to_string())
  664. .arg(&program_filepath)
  665. .stdout(Stdio::inherit())
  666. .stderr(Stdio::inherit())
  667. .output()
  668. .expect("Must deploy");
  669. if !exit.status.success() {
  670. println!("There was a problem deploying: {:?}.", exit);
  671. std::process::exit(exit.status.code().unwrap_or(1));
  672. }
  673. Ok(())
  674. })
  675. }
  676. fn launch(url: Option<String>, keypair: Option<String>) -> Result<()> {
  677. // Build and deploy.
  678. let programs = _deploy(url.clone(), keypair.clone())?;
  679. with_workspace(|cfg, _path, _cargo| {
  680. let url = url.unwrap_or(cfg.cluster.url().to_string());
  681. let keypair = keypair.unwrap_or(cfg.wallet.to_string());
  682. // Add metadata to all IDLs.
  683. for (address, program) in programs {
  684. // Store the IDL on chain.
  685. let idl_address = create_idl_account(&cfg, &keypair, &address, &program.idl)?;
  686. println!("IDL account created: {}", idl_address.to_string());
  687. }
  688. // Run migration script.
  689. if Path::new("migrations/deploy.js").exists() {
  690. migrate(Some(url))?;
  691. }
  692. Ok(())
  693. })
  694. }
  695. // with_workspace ensures the current working directory is always the top level
  696. // workspace directory, i.e., where the `Anchor.toml` file is located, before
  697. // and after the closure invocation.
  698. //
  699. // The closure passed into this function must never change the working directory
  700. // to be outside the workspace. Doing so will have undefined behavior.
  701. fn with_workspace<R>(f: impl FnOnce(&Config, PathBuf, Option<PathBuf>) -> R) -> R {
  702. set_workspace_dir_or_exit();
  703. clear_program_keys().unwrap();
  704. let (cfg, cfg_path, cargo_toml) = Config::discover()
  705. .expect("Previously set the workspace dir")
  706. .expect("Anchor.toml must always exist");
  707. let r = f(&cfg, cfg_path, cargo_toml);
  708. set_workspace_dir_or_exit();
  709. clear_program_keys().unwrap();
  710. r
  711. }
  712. // The Solana CLI doesn't redeploy a program if this file exists.
  713. // So remove it to make all commands explicit.
  714. fn clear_program_keys() -> Result<()> {
  715. for program in read_all_programs().expect("Programs dir doesn't exist") {
  716. let anchor_keypair_path = program.anchor_keypair_path();
  717. if Path::exists(&anchor_keypair_path) {
  718. std::fs::remove_file(anchor_keypair_path).expect("Always remove");
  719. }
  720. }
  721. Ok(())
  722. }
  723. fn create_idl_account(
  724. cfg: &Config,
  725. keypair_path: &str,
  726. program_id: &Pubkey,
  727. idl: &Idl,
  728. ) -> Result<Pubkey> {
  729. // Misc.
  730. let idl_address = IdlAccount::address(program_id);
  731. let keypair = solana_sdk::signature::read_keypair_file(keypair_path)
  732. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  733. let client = RpcClient::new(cfg.cluster.url().to_string());
  734. // Serialize and compress the idl.
  735. let idl_data = {
  736. let json_bytes = serde_json::to_vec(idl)?;
  737. let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
  738. e.write_all(&json_bytes)?;
  739. e.finish()?
  740. };
  741. // Run `Create instruction.
  742. {
  743. let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Create {
  744. data_len: (idl_data.len() as u64) * 2, // Double for future growth.
  745. })?;
  746. let program_signer = Pubkey::find_program_address(&[], program_id).0;
  747. let accounts = vec![
  748. AccountMeta::new_readonly(keypair.pubkey(), true),
  749. AccountMeta::new(idl_address, false),
  750. AccountMeta::new_readonly(program_signer, false),
  751. AccountMeta::new_readonly(solana_program::system_program::ID, false),
  752. AccountMeta::new_readonly(*program_id, false),
  753. AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
  754. ];
  755. let ix = Instruction {
  756. program_id: *program_id,
  757. accounts,
  758. data,
  759. };
  760. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  761. let tx = Transaction::new_signed_with_payer(
  762. &[ix],
  763. Some(&keypair.pubkey()),
  764. &[&keypair],
  765. recent_hash,
  766. );
  767. client.send_and_confirm_transaction_with_spinner_and_config(
  768. &tx,
  769. CommitmentConfig::single(),
  770. RpcSendTransactionConfig {
  771. skip_preflight: true,
  772. ..RpcSendTransactionConfig::default()
  773. },
  774. )?;
  775. }
  776. idl_write(cfg, program_id, idl)?;
  777. Ok(idl_address)
  778. }
  779. fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8>> {
  780. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  781. data.append(&mut ix_inner.try_to_vec()?);
  782. Ok(data)
  783. }
  784. fn migrate(url: Option<String>) -> Result<()> {
  785. with_workspace(|cfg, _path, _cargo| {
  786. println!("Running migration deploy script");
  787. let url = url.unwrap_or(cfg.cluster.url().to_string());
  788. let cur_dir = std::env::current_dir()?;
  789. let module_path = format!("{}/migrations/deploy.js", cur_dir.display());
  790. let deploy_script_host_str = template::deploy_script_host(&url, &module_path);
  791. std::env::set_current_dir(".anchor")?;
  792. std::fs::write("deploy.js", deploy_script_host_str)?;
  793. if let Err(_e) = std::process::Command::new("node")
  794. .arg("deploy.js")
  795. .stdout(Stdio::inherit())
  796. .stderr(Stdio::inherit())
  797. .output()
  798. {
  799. std::process::exit(1);
  800. }
  801. println!("Deploy complete.");
  802. Ok(())
  803. })
  804. }
  805. fn set_workspace_dir_or_exit() {
  806. let d = match Config::discover() {
  807. Err(_) => {
  808. println!("Not in anchor workspace.");
  809. std::process::exit(1);
  810. }
  811. Ok(d) => d,
  812. };
  813. match d {
  814. None => {
  815. println!("Not in anchor workspace.");
  816. std::process::exit(1);
  817. }
  818. Some((_cfg, cfg_path, _inside_cargo)) => {
  819. match cfg_path.parent() {
  820. None => {
  821. println!("Unable to make new program");
  822. }
  823. Some(parent) => match std::env::set_current_dir(&parent) {
  824. Err(_) => {
  825. println!("Not in anchor workspace.");
  826. std::process::exit(1);
  827. }
  828. Ok(_) => {}
  829. },
  830. };
  831. }
  832. }
  833. }
  834. fn airdrop(url: Option<String>) -> Result<()> {
  835. let url = url.unwrap_or("https://devnet.solana.com".to_string());
  836. loop {
  837. let exit = std::process::Command::new("solana")
  838. .arg("airdrop")
  839. .arg("10")
  840. .arg("--url")
  841. .arg(&url)
  842. .stdout(Stdio::inherit())
  843. .stderr(Stdio::inherit())
  844. .output()
  845. .expect("Must airdrop");
  846. if !exit.status.success() {
  847. println!("There was a problem airdropping: {:?}.", exit);
  848. std::process::exit(exit.status.code().unwrap_or(1));
  849. }
  850. std::thread::sleep(std::time::Duration::from_millis(10000));
  851. }
  852. }