main.rs 34 KB

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