lib.rs 70 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162
  1. use crate::config::{
  2. AnchorPackage, Config, ConfigOverride, Manifest, Program, ProgramWorkspace, WithPath,
  3. };
  4. use anchor_client::Cluster;
  5. use anchor_lang::idl::{IdlAccount, IdlInstruction};
  6. use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
  7. use anchor_syn::idl::Idl;
  8. use anyhow::{anyhow, Context, Result};
  9. use clap::Clap;
  10. use flate2::read::ZlibDecoder;
  11. use flate2::write::{GzEncoder, ZlibEncoder};
  12. use flate2::Compression;
  13. use rand::rngs::OsRng;
  14. use reqwest::blocking::multipart::{Form, Part};
  15. use reqwest::blocking::Client;
  16. use serde::{Deserialize, Serialize};
  17. use solana_client::rpc_client::RpcClient;
  18. use solana_client::rpc_config::RpcSendTransactionConfig;
  19. use solana_program::instruction::{AccountMeta, Instruction};
  20. use solana_sdk::account_utils::StateMut;
  21. use solana_sdk::bpf_loader_upgradeable::UpgradeableLoaderState;
  22. use solana_sdk::commitment_config::CommitmentConfig;
  23. use solana_sdk::pubkey::Pubkey;
  24. use solana_sdk::signature::Keypair;
  25. use solana_sdk::signature::Signer;
  26. use solana_sdk::sysvar;
  27. use solana_sdk::transaction::Transaction;
  28. use std::collections::HashMap;
  29. use std::fs::{self, File};
  30. use std::io::prelude::*;
  31. use std::path::{Path, PathBuf};
  32. use std::process::{Child, Stdio};
  33. use std::string::ToString;
  34. pub mod config;
  35. pub mod template;
  36. // Version of the docker image.
  37. pub const VERSION: &str = env!("CARGO_PKG_VERSION");
  38. pub const DOCKER_BUILDER_VERSION: &str = VERSION;
  39. #[derive(Debug, Clap)]
  40. #[clap(version = VERSION)]
  41. pub struct Opts {
  42. #[clap(flatten)]
  43. pub cfg_override: ConfigOverride,
  44. #[clap(subcommand)]
  45. pub command: Command,
  46. }
  47. #[derive(Debug, Clap)]
  48. pub enum Command {
  49. /// Initializes a workspace.
  50. Init {
  51. name: String,
  52. #[clap(short, long)]
  53. typescript: bool,
  54. },
  55. /// Builds the workspace.
  56. Build {
  57. /// Output directory for the IDL.
  58. #[clap(short, long)]
  59. idl: Option<String>,
  60. /// True if the build artifact needs to be deterministic and verifiable.
  61. #[clap(short, long)]
  62. verifiable: bool,
  63. #[clap(short, long)]
  64. program_name: Option<String>,
  65. /// Version of the Solana toolchain to use. For --verifiable builds
  66. /// only.
  67. #[clap(short, long)]
  68. solana_version: Option<String>,
  69. },
  70. /// Verifies the on-chain bytecode matches the locally compiled artifact.
  71. /// Run this command inside a program subdirectory, i.e., in the dir
  72. /// containing the program's Cargo.toml.
  73. Verify {
  74. /// The deployed program to compare against.
  75. program_id: Pubkey,
  76. #[clap(short, long)]
  77. program_name: Option<String>,
  78. /// Version of the Solana toolchain to use. For --verifiable builds
  79. /// only.
  80. #[clap(short, long)]
  81. solana_version: Option<String>,
  82. },
  83. /// Runs integration tests against a localnetwork.
  84. Test {
  85. /// Use this flag if you want to run tests against previously deployed
  86. /// programs.
  87. #[clap(long)]
  88. skip_deploy: bool,
  89. /// Flag to skip starting a local validator, if the configured cluster
  90. /// url is a localnet.
  91. #[clap(long)]
  92. skip_local_validator: bool,
  93. /// Flag to skip building the program in the workspace,
  94. /// use this to save time when running test and the program code is not altered.
  95. #[clap(long)]
  96. skip_build: bool,
  97. #[clap(multiple_values = true)]
  98. args: Vec<String>,
  99. },
  100. /// Creates a new program.
  101. New { name: String },
  102. /// Commands for interacting with interface definitions.
  103. Idl {
  104. #[clap(subcommand)]
  105. subcmd: IdlCommand,
  106. },
  107. /// Deploys each program in the workspace.
  108. Deploy {
  109. #[clap(short, long)]
  110. program_name: Option<String>,
  111. },
  112. /// Runs the deploy migration script.
  113. Migrate,
  114. /// Deploys, initializes an IDL, and migrates all in one command.
  115. /// Upgrades a single program. The configured wallet must be the upgrade
  116. /// authority.
  117. Upgrade {
  118. /// The program to upgrade.
  119. #[clap(short, long)]
  120. program_id: Pubkey,
  121. /// Filepath to the new program binary.
  122. program_filepath: String,
  123. },
  124. #[cfg(feature = "dev")]
  125. /// Runs an airdrop loop, continuously funding the configured wallet.
  126. Airdrop {
  127. #[clap(short, long)]
  128. url: Option<String>,
  129. },
  130. /// Cluster commands.
  131. Cluster {
  132. #[clap(subcommand)]
  133. subcmd: ClusterCommand,
  134. },
  135. /// Starts a node shell with an Anchor client setup according to the local
  136. /// config.
  137. Shell,
  138. /// Runs the script defined by the current workspace's Anchor.toml.
  139. Run {
  140. /// The name of the script to run.
  141. script: String,
  142. },
  143. /// Saves an api token from the registry locally.
  144. Login {
  145. /// API access token.
  146. token: String,
  147. },
  148. /// Publishes a verified build to the Anchor registry.
  149. Publish {
  150. /// The name of the program to publish.
  151. program: String,
  152. },
  153. }
  154. #[derive(Debug, Clap)]
  155. pub enum IdlCommand {
  156. /// Initializes a program's IDL account. Can only be run once.
  157. Init {
  158. program_id: Pubkey,
  159. #[clap(short, long)]
  160. filepath: String,
  161. },
  162. /// Writes an IDL into a buffer account. This can be used with SetBuffer
  163. /// to perform an upgrade.
  164. WriteBuffer {
  165. program_id: Pubkey,
  166. #[clap(short, long)]
  167. filepath: String,
  168. },
  169. /// Sets a new IDL buffer for the program.
  170. SetBuffer {
  171. program_id: Pubkey,
  172. /// Address of the buffer account to set as the idl on the program.
  173. #[clap(short, long)]
  174. buffer: Pubkey,
  175. },
  176. /// Upgrades the IDL to the new file. An alias for first writing and then
  177. /// then setting the idl buffer account.
  178. Upgrade {
  179. program_id: Pubkey,
  180. #[clap(short, long)]
  181. filepath: String,
  182. },
  183. /// Sets a new authority on the IDL account.
  184. SetAuthority {
  185. /// The IDL account buffer to set the authority of. If none is given,
  186. /// then the canonical IDL account is used.
  187. address: Option<Pubkey>,
  188. /// Program to change the IDL authority.
  189. #[clap(short, long)]
  190. program_id: Pubkey,
  191. /// New authority of the IDL account.
  192. #[clap(short, long)]
  193. new_authority: Pubkey,
  194. },
  195. /// Command to remove the ability to modify the IDL account. This should
  196. /// likely be used in conjection with eliminating an "upgrade authority" on
  197. /// the program.
  198. EraseAuthority {
  199. #[clap(short, long)]
  200. program_id: Pubkey,
  201. },
  202. /// Outputs the authority for the IDL account.
  203. Authority {
  204. /// The program to view.
  205. program_id: Pubkey,
  206. },
  207. /// Parses an IDL from source.
  208. Parse {
  209. /// Path to the program's interface definition.
  210. #[clap(short, long)]
  211. file: String,
  212. /// Output file for the idl (stdout if not specified).
  213. #[clap(short, long)]
  214. out: Option<String>,
  215. },
  216. /// Fetches an IDL for the given address from a cluster.
  217. /// The address can be a program, IDL account, or IDL buffer.
  218. Fetch {
  219. address: Pubkey,
  220. /// Output file for the idl (stdout if not specified).
  221. #[clap(short, long)]
  222. out: Option<String>,
  223. },
  224. }
  225. #[derive(Debug, Clap)]
  226. pub enum ClusterCommand {
  227. /// Prints common cluster urls.
  228. List,
  229. }
  230. pub fn entry(opts: Opts) -> Result<()> {
  231. match opts.command {
  232. Command::Init { name, typescript } => init(&opts.cfg_override, name, typescript),
  233. Command::New { name } => new(&opts.cfg_override, name),
  234. Command::Build {
  235. idl,
  236. verifiable,
  237. program_name,
  238. solana_version,
  239. } => build(
  240. &opts.cfg_override,
  241. idl,
  242. verifiable,
  243. program_name,
  244. solana_version,
  245. None,
  246. None,
  247. ),
  248. Command::Verify {
  249. program_id,
  250. program_name,
  251. solana_version,
  252. } => verify(&opts.cfg_override, program_id, program_name, solana_version),
  253. Command::Deploy { program_name } => deploy(&opts.cfg_override, program_name),
  254. Command::Upgrade {
  255. program_id,
  256. program_filepath,
  257. } => upgrade(&opts.cfg_override, program_id, program_filepath),
  258. Command::Idl { subcmd } => idl(&opts.cfg_override, subcmd),
  259. Command::Migrate => migrate(&opts.cfg_override),
  260. Command::Test {
  261. skip_deploy,
  262. skip_local_validator,
  263. skip_build,
  264. args,
  265. } => test(
  266. &opts.cfg_override,
  267. skip_deploy,
  268. skip_local_validator,
  269. skip_build,
  270. args,
  271. ),
  272. #[cfg(feature = "dev")]
  273. Command::Airdrop => airdrop(cfg_override),
  274. Command::Cluster { subcmd } => cluster(subcmd),
  275. Command::Shell => shell(&opts.cfg_override),
  276. Command::Run { script } => run(&opts.cfg_override, script),
  277. Command::Login { token } => login(&opts.cfg_override, token),
  278. Command::Publish { program } => publish(&opts.cfg_override, program),
  279. }
  280. }
  281. fn init(cfg_override: &ConfigOverride, name: String, typescript: bool) -> Result<()> {
  282. if Config::discover(cfg_override)?.is_some() {
  283. return Err(anyhow!("Workspace already initialized"));
  284. }
  285. fs::create_dir(name.clone())?;
  286. std::env::set_current_dir(&name)?;
  287. fs::create_dir("app")?;
  288. let mut cfg = Config::default();
  289. cfg.scripts.insert(
  290. "test".to_owned(),
  291. if typescript {
  292. "ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
  293. } else {
  294. "mocha -t 1000000 tests/"
  295. }
  296. .to_owned(),
  297. );
  298. let toml = cfg.to_string();
  299. let mut file = File::create("Anchor.toml")?;
  300. file.write_all(toml.as_bytes())?;
  301. // Build virtual manifest.
  302. let mut virt_manifest = File::create("Cargo.toml")?;
  303. virt_manifest.write_all(template::virtual_manifest().as_bytes())?;
  304. // Initialize .gitignore file
  305. let mut virt_manifest = File::create(".gitignore")?;
  306. virt_manifest.write_all(template::git_ignore().as_bytes())?;
  307. // Build the program.
  308. fs::create_dir("programs")?;
  309. new_program(&name)?;
  310. // Build the test suite.
  311. fs::create_dir("tests")?;
  312. // Build the migrations directory.
  313. fs::create_dir("migrations")?;
  314. if typescript {
  315. // Build typescript config
  316. let mut ts_config = File::create("tsconfig.json")?;
  317. ts_config.write_all(template::ts_config().as_bytes())?;
  318. let mut ts_package_json = File::create("package.json")?;
  319. ts_package_json.write_all(template::ts_package_json().as_bytes())?;
  320. let mut deploy = File::create("migrations/deploy.ts")?;
  321. deploy.write_all(template::ts_deploy_script().as_bytes())?;
  322. let mut mocha = File::create(&format!("tests/{}.ts", name))?;
  323. mocha.write_all(template::ts_mocha(&name).as_bytes())?;
  324. } else {
  325. let mut package_json = File::create("package.json")?;
  326. package_json.write_all(template::package_json().as_bytes())?;
  327. let mut mocha = File::create(&format!("tests/{}.js", name))?;
  328. mocha.write_all(template::mocha(&name).as_bytes())?;
  329. let mut deploy = File::create("migrations/deploy.js")?;
  330. deploy.write_all(template::deploy_script().as_bytes())?;
  331. }
  332. // Install node modules.
  333. let yarn_result = std::process::Command::new("yarn")
  334. .stdout(Stdio::inherit())
  335. .stderr(Stdio::inherit())
  336. .output()
  337. .map_err(|e| anyhow::format_err!("yarn install failed: {}", e.to_string()))?;
  338. if !yarn_result.status.success() {
  339. println!("Failed yarn install will attempt to npm install");
  340. std::process::Command::new("npm")
  341. .stdout(Stdio::inherit())
  342. .stderr(Stdio::inherit())
  343. .output()
  344. .map_err(|e| anyhow::format_err!("npm install failed: {}", e.to_string()))?;
  345. println!("Failed to install node dependencies")
  346. }
  347. println!("{} initialized", name);
  348. Ok(())
  349. }
  350. // Creates a new program crate in the `programs/<name>` directory.
  351. fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
  352. with_workspace(cfg_override, |cfg| {
  353. match cfg.path().parent() {
  354. None => {
  355. println!("Unable to make new program");
  356. }
  357. Some(parent) => {
  358. std::env::set_current_dir(&parent)?;
  359. new_program(&name)?;
  360. println!("Created new program.");
  361. }
  362. };
  363. Ok(())
  364. })
  365. }
  366. // Creates a new program crate in the current directory with `name`.
  367. fn new_program(name: &str) -> Result<()> {
  368. fs::create_dir(&format!("programs/{}", name))?;
  369. fs::create_dir(&format!("programs/{}/src/", name))?;
  370. let mut cargo_toml = File::create(&format!("programs/{}/Cargo.toml", name))?;
  371. cargo_toml.write_all(template::cargo_toml(name).as_bytes())?;
  372. let mut xargo_toml = File::create(&format!("programs/{}/Xargo.toml", name))?;
  373. xargo_toml.write_all(template::xargo_toml().as_bytes())?;
  374. let mut lib_rs = File::create(&format!("programs/{}/src/lib.rs", name))?;
  375. lib_rs.write_all(template::lib_rs(name).as_bytes())?;
  376. Ok(())
  377. }
  378. pub fn build(
  379. cfg_override: &ConfigOverride,
  380. idl: Option<String>,
  381. verifiable: bool,
  382. program_name: Option<String>,
  383. solana_version: Option<String>,
  384. stdout: Option<File>, // Used for the package registry server.
  385. stderr: Option<File>, // Used for the package registry server.
  386. ) -> Result<()> {
  387. // Change to the workspace member directory, if needed.
  388. if let Some(program_name) = program_name.as_ref() {
  389. cd_member(cfg_override, program_name)?;
  390. }
  391. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  392. let cargo = Manifest::discover()?;
  393. let idl_out = match idl {
  394. Some(idl) => Some(PathBuf::from(idl)),
  395. None => {
  396. let cfg_parent = match cfg.path().parent() {
  397. None => return Err(anyhow!("Invalid Anchor.toml")),
  398. Some(parent) => parent,
  399. };
  400. fs::create_dir_all(cfg_parent.join("target/idl"))?;
  401. Some(cfg_parent.join("target/idl"))
  402. }
  403. };
  404. let solana_version = match solana_version.is_some() {
  405. true => solana_version,
  406. false => cfg.solana_version.clone(),
  407. };
  408. match cargo {
  409. // No Cargo.toml so build the entire workspace.
  410. None => build_all(
  411. &cfg,
  412. cfg.path(),
  413. idl_out,
  414. verifiable,
  415. solana_version,
  416. stdout,
  417. stderr,
  418. )?,
  419. // If the Cargo.toml is at the root, build the entire workspace.
  420. Some(cargo) if cargo.path().parent() == cfg.path().parent() => build_all(
  421. &cfg,
  422. cfg.path(),
  423. idl_out,
  424. verifiable,
  425. solana_version,
  426. stdout,
  427. stderr,
  428. )?,
  429. // Cargo.toml represents a single package. Build it.
  430. Some(cargo) => build_cwd(
  431. &cfg,
  432. cargo.path().to_path_buf(),
  433. idl_out,
  434. verifiable,
  435. solana_version,
  436. stdout,
  437. stderr,
  438. )?,
  439. }
  440. set_workspace_dir_or_exit();
  441. Ok(())
  442. }
  443. fn build_all(
  444. cfg: &WithPath<Config>,
  445. cfg_path: &Path,
  446. idl_out: Option<PathBuf>,
  447. verifiable: bool,
  448. solana_version: Option<String>,
  449. stdout: Option<File>, // Used for the package registry server.
  450. stderr: Option<File>, // Used for the package registry server.
  451. ) -> Result<()> {
  452. let cur_dir = std::env::current_dir()?;
  453. let r = match cfg_path.parent() {
  454. None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
  455. Some(_parent) => {
  456. for p in cfg.get_program_list()? {
  457. build_cwd(
  458. cfg,
  459. p.join("Cargo.toml"),
  460. idl_out.clone(),
  461. verifiable,
  462. solana_version.clone(),
  463. stdout.as_ref().map(|f| f.try_clone()).transpose()?,
  464. stderr.as_ref().map(|f| f.try_clone()).transpose()?,
  465. )?;
  466. }
  467. Ok(())
  468. }
  469. };
  470. std::env::set_current_dir(cur_dir)?;
  471. r
  472. }
  473. // Runs the build command outside of a workspace.
  474. fn build_cwd(
  475. cfg: &WithPath<Config>,
  476. cargo_toml: PathBuf,
  477. idl_out: Option<PathBuf>,
  478. verifiable: bool,
  479. solana_version: Option<String>,
  480. stdout: Option<File>,
  481. stderr: Option<File>,
  482. ) -> Result<()> {
  483. match cargo_toml.parent() {
  484. None => return Err(anyhow!("Unable to find parent")),
  485. Some(p) => std::env::set_current_dir(&p)?,
  486. };
  487. match verifiable {
  488. false => _build_cwd(idl_out),
  489. true => build_cwd_verifiable(cfg, cargo_toml, solana_version, stdout, stderr),
  490. }
  491. }
  492. // Builds an anchor program in a docker image and copies the build artifacts
  493. // into the `target/` directory.
  494. fn build_cwd_verifiable(
  495. cfg: &WithPath<Config>,
  496. cargo_toml: PathBuf,
  497. solana_version: Option<String>,
  498. stdout: Option<File>,
  499. stderr: Option<File>,
  500. ) -> Result<()> {
  501. // Create output dirs.
  502. let workspace_dir = cfg.path().parent().unwrap().canonicalize()?;
  503. fs::create_dir_all(workspace_dir.join("target/verifiable"))?;
  504. fs::create_dir_all(workspace_dir.join("target/idl"))?;
  505. let container_name = "anchor-program";
  506. // Build the binary in docker.
  507. let result = docker_build(
  508. cfg,
  509. container_name,
  510. cargo_toml,
  511. solana_version,
  512. stdout,
  513. stderr,
  514. );
  515. // Wipe the generated docker-target dir.
  516. println!("Cleaning up the docker target directory");
  517. let exit = std::process::Command::new("docker")
  518. .args(&[
  519. "exec",
  520. container_name,
  521. "rm",
  522. "-rf",
  523. "/workdir/docker-target",
  524. ])
  525. .stdout(Stdio::inherit())
  526. .stderr(Stdio::inherit())
  527. .output()
  528. .map_err(|e| anyhow::format_err!("Docker rm docker-target failed: {}", e.to_string()))?;
  529. if !exit.status.success() {
  530. return Err(anyhow!("Failed to build program"));
  531. }
  532. // Remove the docker image.
  533. println!("Removing the docker image");
  534. let exit = std::process::Command::new("docker")
  535. .args(&["rm", "-f", container_name])
  536. .stdout(Stdio::inherit())
  537. .stderr(Stdio::inherit())
  538. .output()
  539. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  540. if !exit.status.success() {
  541. println!("Unable to remove docker container");
  542. std::process::exit(exit.status.code().unwrap_or(1));
  543. }
  544. // Build the idl.
  545. if let Ok(Some(idl)) = extract_idl("src/lib.rs") {
  546. println!("Extracting the IDL");
  547. let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.name));
  548. write_idl(&idl, OutFile::File(out_file))?;
  549. }
  550. result
  551. }
  552. fn docker_build(
  553. cfg: &WithPath<Config>,
  554. container_name: &str,
  555. cargo_toml: PathBuf,
  556. solana_version: Option<String>,
  557. stdout: Option<File>,
  558. stderr: Option<File>,
  559. ) -> Result<()> {
  560. let binary_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
  561. // Docker vars.
  562. let image_name = cfg.docker();
  563. let volume_mount = format!(
  564. "{}:/workdir",
  565. cfg.path().parent().unwrap().canonicalize()?.display()
  566. );
  567. println!("Using image {:?}", image_name);
  568. // Start the docker image running detached in the background.
  569. println!("Run docker image");
  570. let exit = std::process::Command::new("docker")
  571. .args(&[
  572. "run",
  573. "-it",
  574. "-d",
  575. "--name",
  576. container_name,
  577. "--env",
  578. "CARGO_TARGET_DIR=/workdir/docker-target",
  579. "-v",
  580. &volume_mount,
  581. &image_name,
  582. "bash",
  583. ])
  584. .stdout(Stdio::inherit())
  585. .stderr(Stdio::inherit())
  586. .output()
  587. .map_err(|e| anyhow::format_err!("Docker build failed: {}", e.to_string()))?;
  588. if !exit.status.success() {
  589. return Err(anyhow!("Failed to build program"));
  590. }
  591. // Set the solana version in the container, if given. Otherwise use the
  592. // default.
  593. if let Some(solana_version) = solana_version {
  594. println!("Using solana version: {}", solana_version);
  595. // Fetch the installer.
  596. let exit = std::process::Command::new("docker")
  597. .args(&[
  598. "exec",
  599. container_name,
  600. "curl",
  601. "-sSfL",
  602. &format!("https://release.solana.com/v{0}/install", solana_version,),
  603. "-o",
  604. "solana_installer.sh",
  605. ])
  606. .stdout(Stdio::inherit())
  607. .stderr(Stdio::inherit())
  608. .output()
  609. .map_err(|e| anyhow!("Failed to set solana version: {:?}", e))?;
  610. if !exit.status.success() {
  611. return Err(anyhow!("Failed to set solana version"));
  612. }
  613. // Run the installer.
  614. let exit = std::process::Command::new("docker")
  615. .args(&["exec", container_name, "sh", "solana_installer.sh"])
  616. .stdout(Stdio::inherit())
  617. .stderr(Stdio::inherit())
  618. .output()
  619. .map_err(|e| anyhow!("Failed to set solana version: {:?}", e))?;
  620. if !exit.status.success() {
  621. return Err(anyhow!("Failed to set solana version"));
  622. }
  623. // Remove the installer.
  624. let exit = std::process::Command::new("docker")
  625. .args(&["exec", container_name, "rm", "-f", "solana_installer.sh"])
  626. .stdout(Stdio::inherit())
  627. .stderr(Stdio::inherit())
  628. .output()
  629. .map_err(|e| anyhow!("Failed to remove installer: {:?}", e))?;
  630. if !exit.status.success() {
  631. return Err(anyhow!("Failed to remove installer"));
  632. }
  633. }
  634. let manifest_path = pathdiff::diff_paths(
  635. cargo_toml.canonicalize()?,
  636. cfg.path().parent().unwrap().canonicalize()?,
  637. )
  638. .ok_or_else(|| anyhow!("Unable to diff paths"))?;
  639. println!(
  640. "Building {} manifest: {:?}",
  641. binary_name,
  642. manifest_path.display().to_string()
  643. );
  644. // Execute the build.
  645. let exit = std::process::Command::new("docker")
  646. .args(&[
  647. "exec",
  648. container_name,
  649. "cargo",
  650. "build-bpf",
  651. "--manifest-path",
  652. &manifest_path.display().to_string(),
  653. ])
  654. .stdout(match stdout {
  655. None => Stdio::inherit(),
  656. Some(f) => f.into(),
  657. })
  658. .stderr(match stderr {
  659. None => Stdio::inherit(),
  660. Some(f) => f.into(),
  661. })
  662. .output()
  663. .map_err(|e| anyhow::format_err!("Docker build failed: {}", e.to_string()))?;
  664. if !exit.status.success() {
  665. return Err(anyhow!("Failed to build program"));
  666. }
  667. // Copy the binary out of the docker image.
  668. println!("Copying out the build artifacts");
  669. let out_file = cfg
  670. .path()
  671. .parent()
  672. .unwrap()
  673. .canonicalize()?
  674. .join(format!("target/verifiable/{}.so", binary_name))
  675. .display()
  676. .to_string();
  677. // This requires the target directory of any built program to be located at
  678. // the root of the workspace.
  679. let bin_artifact = format!(
  680. "{}:/workdir/docker-target/deploy/{}.so",
  681. container_name, binary_name
  682. );
  683. let exit = std::process::Command::new("docker")
  684. .args(&["cp", &bin_artifact, &out_file])
  685. .stdout(Stdio::inherit())
  686. .stderr(Stdio::inherit())
  687. .output()
  688. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  689. if !exit.status.success() {
  690. return Err(anyhow!(
  691. "Failed to copy binary out of docker. Is the target directory set correctly?"
  692. ));
  693. }
  694. // Done.
  695. Ok(())
  696. }
  697. fn _build_cwd(idl_out: Option<PathBuf>) -> Result<()> {
  698. let exit = std::process::Command::new("cargo")
  699. .arg("build-bpf")
  700. .stdout(Stdio::inherit())
  701. .stderr(Stdio::inherit())
  702. .output()
  703. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  704. if !exit.status.success() {
  705. std::process::exit(exit.status.code().unwrap_or(1));
  706. }
  707. // Always assume idl is located ar src/lib.rs.
  708. if let Some(idl) = extract_idl("src/lib.rs")? {
  709. let out = match idl_out {
  710. None => PathBuf::from(".").join(&idl.name).with_extension("json"),
  711. Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")),
  712. };
  713. write_idl(&idl, OutFile::File(out))?;
  714. }
  715. Ok(())
  716. }
  717. fn verify(
  718. cfg_override: &ConfigOverride,
  719. program_id: Pubkey,
  720. program_name: Option<String>,
  721. solana_version: Option<String>,
  722. ) -> Result<()> {
  723. // Change to the workspace member directory, if needed.
  724. if let Some(program_name) = program_name.as_ref() {
  725. cd_member(cfg_override, program_name)?;
  726. }
  727. // Proceed with the command.
  728. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  729. let cargo = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
  730. // Build the program we want to verify.
  731. let cur_dir = std::env::current_dir()?;
  732. build(
  733. cfg_override,
  734. None,
  735. true,
  736. None,
  737. match solana_version.is_some() {
  738. true => solana_version,
  739. false => cfg.solana_version.clone(),
  740. },
  741. None,
  742. None,
  743. )?;
  744. std::env::set_current_dir(&cur_dir)?;
  745. // Verify binary.
  746. let binary_name = cargo.lib_name()?;
  747. let bin_path = cfg
  748. .path()
  749. .parent()
  750. .ok_or_else(|| anyhow!("Unable to find workspace root"))?
  751. .join("target/verifiable/")
  752. .join(format!("{}.so", binary_name));
  753. let bin_ver = verify_bin(program_id, &bin_path, cfg.provider.cluster.url())?;
  754. if !bin_ver.is_verified {
  755. println!("Error: Binaries don't match");
  756. std::process::exit(1);
  757. }
  758. // Verify IDL (only if it's not a buffer account).
  759. if let Some(local_idl) = extract_idl("src/lib.rs")? {
  760. if bin_ver.state != BinVerificationState::Buffer {
  761. let deployed_idl = fetch_idl(cfg_override, program_id)?;
  762. if local_idl != deployed_idl {
  763. println!("Error: IDLs don't match");
  764. std::process::exit(1);
  765. }
  766. }
  767. }
  768. println!("{} is verified.", program_id);
  769. Ok(())
  770. }
  771. fn cd_member(cfg_override: &ConfigOverride, program_name: &str) -> Result<()> {
  772. // Change directories to the given `program_name`, if given.
  773. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  774. for program in cfg.read_all_programs()? {
  775. let cargo_toml = program.path.join("Cargo.toml");
  776. if !cargo_toml.exists() {
  777. return Err(anyhow!(
  778. "Did not find Cargo.toml at the path: {}",
  779. program.path.display()
  780. ));
  781. }
  782. let p_lib_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
  783. if program_name == p_lib_name {
  784. std::env::set_current_dir(&program.path)?;
  785. return Ok(());
  786. }
  787. }
  788. return Err(anyhow!("{} is not part of the workspace", program_name,));
  789. }
  790. pub fn verify_bin(program_id: Pubkey, bin_path: &Path, cluster: &str) -> Result<BinVerification> {
  791. let client = RpcClient::new(cluster.to_string());
  792. // Get the deployed build artifacts.
  793. let (deployed_bin, state) = {
  794. let account = client
  795. .get_account_with_commitment(&program_id, CommitmentConfig::default())?
  796. .value
  797. .map_or(Err(anyhow!("Account not found")), Ok)?;
  798. match account.state()? {
  799. UpgradeableLoaderState::Program {
  800. programdata_address,
  801. } => {
  802. let account = client
  803. .get_account_with_commitment(&programdata_address, CommitmentConfig::default())?
  804. .value
  805. .map_or(Err(anyhow!("Account not found")), Ok)?;
  806. let bin = account.data
  807. [UpgradeableLoaderState::programdata_data_offset().unwrap_or(0)..]
  808. .to_vec();
  809. if let UpgradeableLoaderState::ProgramData {
  810. slot,
  811. upgrade_authority_address,
  812. } = account.state()?
  813. {
  814. let state = BinVerificationState::ProgramData {
  815. slot,
  816. upgrade_authority_address,
  817. };
  818. (bin, state)
  819. } else {
  820. return Err(anyhow!("Expected program data"));
  821. }
  822. }
  823. UpgradeableLoaderState::Buffer { .. } => {
  824. let offset = UpgradeableLoaderState::buffer_data_offset().unwrap_or(0);
  825. (
  826. account.data[offset..].to_vec(),
  827. BinVerificationState::Buffer,
  828. )
  829. }
  830. _ => return Err(anyhow!("Invalid program id")),
  831. }
  832. };
  833. let mut local_bin = {
  834. let mut f = File::open(bin_path)?;
  835. let mut contents = vec![];
  836. f.read_to_end(&mut contents)?;
  837. contents
  838. };
  839. // The deployed program probably has zero bytes appended. The default is
  840. // 2x the binary size in case of an upgrade.
  841. if local_bin.len() < deployed_bin.len() {
  842. local_bin.append(&mut vec![0; deployed_bin.len() - local_bin.len()]);
  843. }
  844. // Finally, check the bytes.
  845. let is_verified = local_bin == deployed_bin;
  846. Ok(BinVerification { state, is_verified })
  847. }
  848. #[derive(PartialEq)]
  849. pub struct BinVerification {
  850. pub state: BinVerificationState,
  851. pub is_verified: bool,
  852. }
  853. #[derive(PartialEq)]
  854. pub enum BinVerificationState {
  855. Buffer,
  856. ProgramData {
  857. slot: u64,
  858. upgrade_authority_address: Option<Pubkey>,
  859. },
  860. }
  861. // Fetches an IDL for the given program_id.
  862. fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<Idl> {
  863. let cfg = Config::discover(cfg_override)?.expect("Inside a workspace");
  864. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  865. let mut account = client
  866. .get_account_with_commitment(&idl_addr, CommitmentConfig::processed())?
  867. .value
  868. .map_or(Err(anyhow!("Account not found")), Ok)?;
  869. if account.executable {
  870. let idl_addr = IdlAccount::address(&idl_addr);
  871. account = client
  872. .get_account_with_commitment(&idl_addr, CommitmentConfig::processed())?
  873. .value
  874. .map_or(Err(anyhow!("Account not found")), Ok)?;
  875. }
  876. // Cut off account discriminator.
  877. let mut d: &[u8] = &account.data[8..];
  878. let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?;
  879. let mut z = ZlibDecoder::new(&idl_account.data[..]);
  880. let mut s = Vec::new();
  881. z.read_to_end(&mut s)?;
  882. serde_json::from_slice(&s[..]).map_err(Into::into)
  883. }
  884. fn extract_idl(file: &str) -> Result<Option<Idl>> {
  885. let file = shellexpand::tilde(file);
  886. anchor_syn::idl::file::parse(&*file)
  887. }
  888. fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
  889. match subcmd {
  890. IdlCommand::Init {
  891. program_id,
  892. filepath,
  893. } => idl_init(cfg_override, program_id, filepath),
  894. IdlCommand::WriteBuffer {
  895. program_id,
  896. filepath,
  897. } => idl_write_buffer(cfg_override, program_id, filepath).map(|_| ()),
  898. IdlCommand::SetBuffer { program_id, buffer } => {
  899. idl_set_buffer(cfg_override, program_id, buffer)
  900. }
  901. IdlCommand::Upgrade {
  902. program_id,
  903. filepath,
  904. } => idl_upgrade(cfg_override, program_id, filepath),
  905. IdlCommand::SetAuthority {
  906. program_id,
  907. address,
  908. new_authority,
  909. } => idl_set_authority(cfg_override, program_id, address, new_authority),
  910. IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id),
  911. IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
  912. IdlCommand::Parse { file, out } => idl_parse(file, out),
  913. IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
  914. }
  915. }
  916. fn idl_init(cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: String) -> Result<()> {
  917. with_workspace(cfg_override, |cfg| {
  918. let keypair = cfg.provider.wallet.to_string();
  919. let bytes = std::fs::read(idl_filepath)?;
  920. let idl: Idl = serde_json::from_reader(&*bytes)?;
  921. let idl_address = create_idl_account(cfg, &keypair, &program_id, &idl)?;
  922. println!("Idl account created: {:?}", idl_address);
  923. Ok(())
  924. })
  925. }
  926. fn idl_write_buffer(
  927. cfg_override: &ConfigOverride,
  928. program_id: Pubkey,
  929. idl_filepath: String,
  930. ) -> Result<Pubkey> {
  931. with_workspace(cfg_override, |cfg| {
  932. let keypair = cfg.provider.wallet.to_string();
  933. let bytes = std::fs::read(idl_filepath)?;
  934. let idl: Idl = serde_json::from_reader(&*bytes)?;
  935. let idl_buffer = create_idl_buffer(cfg, &keypair, &program_id, &idl)?;
  936. idl_write(cfg, &program_id, &idl, idl_buffer)?;
  937. println!("Idl buffer created: {:?}", idl_buffer);
  938. Ok(idl_buffer)
  939. })
  940. }
  941. fn idl_set_buffer(cfg_override: &ConfigOverride, program_id: Pubkey, buffer: Pubkey) -> Result<()> {
  942. with_workspace(cfg_override, |cfg| {
  943. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  944. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  945. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  946. // Instruction to set the buffer onto the IdlAccount.
  947. let set_buffer_ix = {
  948. let accounts = vec![
  949. AccountMeta::new(buffer, false),
  950. AccountMeta::new(IdlAccount::address(&program_id), false),
  951. AccountMeta::new(keypair.pubkey(), true),
  952. ];
  953. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  954. data.append(&mut IdlInstruction::SetBuffer.try_to_vec()?);
  955. Instruction {
  956. program_id,
  957. accounts,
  958. data,
  959. }
  960. };
  961. // Build the transaction.
  962. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  963. let tx = Transaction::new_signed_with_payer(
  964. &[set_buffer_ix],
  965. Some(&keypair.pubkey()),
  966. &[&keypair],
  967. recent_hash,
  968. );
  969. // Send the transaction.
  970. client.send_and_confirm_transaction_with_spinner_and_config(
  971. &tx,
  972. CommitmentConfig::confirmed(),
  973. RpcSendTransactionConfig {
  974. skip_preflight: true,
  975. ..RpcSendTransactionConfig::default()
  976. },
  977. )?;
  978. Ok(())
  979. })
  980. }
  981. fn idl_upgrade(
  982. cfg_override: &ConfigOverride,
  983. program_id: Pubkey,
  984. idl_filepath: String,
  985. ) -> Result<()> {
  986. let buffer = idl_write_buffer(cfg_override, program_id, idl_filepath)?;
  987. idl_set_buffer(cfg_override, program_id, buffer)
  988. }
  989. fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
  990. with_workspace(cfg_override, |cfg| {
  991. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  992. let idl_address = {
  993. let account = client
  994. .get_account_with_commitment(&program_id, CommitmentConfig::processed())?
  995. .value
  996. .map_or(Err(anyhow!("Account not found")), Ok)?;
  997. if account.executable {
  998. IdlAccount::address(&program_id)
  999. } else {
  1000. program_id
  1001. }
  1002. };
  1003. let account = client.get_account(&idl_address)?;
  1004. let mut data: &[u8] = &account.data;
  1005. let idl_account: IdlAccount = AccountDeserialize::try_deserialize(&mut data)?;
  1006. println!("{:?}", idl_account.authority);
  1007. Ok(())
  1008. })
  1009. }
  1010. fn idl_set_authority(
  1011. cfg_override: &ConfigOverride,
  1012. program_id: Pubkey,
  1013. address: Option<Pubkey>,
  1014. new_authority: Pubkey,
  1015. ) -> Result<()> {
  1016. with_workspace(cfg_override, |cfg| {
  1017. // Misc.
  1018. let idl_address = match address {
  1019. None => IdlAccount::address(&program_id),
  1020. Some(addr) => addr,
  1021. };
  1022. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  1023. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1024. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  1025. // Instruction data.
  1026. let data =
  1027. serialize_idl_ix(anchor_lang::idl::IdlInstruction::SetAuthority { new_authority })?;
  1028. // Instruction accounts.
  1029. let accounts = vec![
  1030. AccountMeta::new(idl_address, false),
  1031. AccountMeta::new_readonly(keypair.pubkey(), true),
  1032. ];
  1033. // Instruction.
  1034. let ix = Instruction {
  1035. program_id,
  1036. accounts,
  1037. data,
  1038. };
  1039. // Send transaction.
  1040. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1041. let tx = Transaction::new_signed_with_payer(
  1042. &[ix],
  1043. Some(&keypair.pubkey()),
  1044. &[&keypair],
  1045. recent_hash,
  1046. );
  1047. client.send_and_confirm_transaction_with_spinner_and_config(
  1048. &tx,
  1049. CommitmentConfig::confirmed(),
  1050. RpcSendTransactionConfig {
  1051. skip_preflight: true,
  1052. ..RpcSendTransactionConfig::default()
  1053. },
  1054. )?;
  1055. println!("Authority update complete.");
  1056. Ok(())
  1057. })
  1058. }
  1059. fn idl_erase_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
  1060. println!("Are you sure you want to erase the IDL authority: [y/n]");
  1061. let stdin = std::io::stdin();
  1062. let mut stdin_lines = stdin.lock().lines();
  1063. let input = stdin_lines.next().unwrap().unwrap();
  1064. if input != "y" {
  1065. println!("Not erasing.");
  1066. return Ok(());
  1067. }
  1068. // Program will treat the zero authority as erased.
  1069. let new_authority = Pubkey::new_from_array([0u8; 32]);
  1070. idl_set_authority(cfg_override, program_id, None, new_authority)?;
  1071. Ok(())
  1072. }
  1073. // Write the idl to the account buffer, chopping up the IDL into pieces
  1074. // and sending multiple transactions in the event the IDL doesn't fit into
  1075. // a single transaction.
  1076. fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey) -> Result<()> {
  1077. // Remove the metadata before deploy.
  1078. let mut idl = idl.clone();
  1079. idl.metadata = None;
  1080. // Misc.
  1081. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  1082. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1083. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  1084. // Serialize and compress the idl.
  1085. let idl_data = {
  1086. let json_bytes = serde_json::to_vec(&idl)?;
  1087. let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
  1088. e.write_all(&json_bytes)?;
  1089. e.finish()?
  1090. };
  1091. const MAX_WRITE_SIZE: usize = 1000;
  1092. let mut offset = 0;
  1093. while offset < idl_data.len() {
  1094. // Instruction data.
  1095. let data = {
  1096. let start = offset;
  1097. let end = std::cmp::min(offset + MAX_WRITE_SIZE, idl_data.len());
  1098. serialize_idl_ix(anchor_lang::idl::IdlInstruction::Write {
  1099. data: idl_data[start..end].to_vec(),
  1100. })?
  1101. };
  1102. // Instruction accounts.
  1103. let accounts = vec![
  1104. AccountMeta::new(idl_address, false),
  1105. AccountMeta::new_readonly(keypair.pubkey(), true),
  1106. ];
  1107. // Instruction.
  1108. let ix = Instruction {
  1109. program_id: *program_id,
  1110. accounts,
  1111. data,
  1112. };
  1113. // Send transaction.
  1114. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1115. let tx = Transaction::new_signed_with_payer(
  1116. &[ix],
  1117. Some(&keypair.pubkey()),
  1118. &[&keypair],
  1119. recent_hash,
  1120. );
  1121. client.send_and_confirm_transaction_with_spinner_and_config(
  1122. &tx,
  1123. CommitmentConfig::confirmed(),
  1124. RpcSendTransactionConfig {
  1125. skip_preflight: true,
  1126. ..RpcSendTransactionConfig::default()
  1127. },
  1128. )?;
  1129. offset += MAX_WRITE_SIZE;
  1130. }
  1131. Ok(())
  1132. }
  1133. fn idl_parse(file: String, out: Option<String>) -> Result<()> {
  1134. let idl = extract_idl(&file)?.ok_or_else(|| anyhow!("IDL not parsed"))?;
  1135. let out = match out {
  1136. None => OutFile::Stdout,
  1137. Some(out) => OutFile::File(PathBuf::from(out)),
  1138. };
  1139. write_idl(&idl, out)
  1140. }
  1141. fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
  1142. let idl = fetch_idl(cfg_override, address)?;
  1143. let out = match out {
  1144. None => OutFile::Stdout,
  1145. Some(out) => OutFile::File(PathBuf::from(out)),
  1146. };
  1147. write_idl(&idl, out)
  1148. }
  1149. fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
  1150. let idl_json = serde_json::to_string_pretty(idl)?;
  1151. match out {
  1152. OutFile::Stdout => println!("{}", idl_json),
  1153. OutFile::File(out) => std::fs::write(out, idl_json)?,
  1154. };
  1155. Ok(())
  1156. }
  1157. enum OutFile {
  1158. Stdout,
  1159. File(PathBuf),
  1160. }
  1161. // Builds, deploys, and tests all workspace programs in a single command.
  1162. fn test(
  1163. cfg_override: &ConfigOverride,
  1164. skip_deploy: bool,
  1165. skip_local_validator: bool,
  1166. skip_build: bool,
  1167. extra_args: Vec<String>,
  1168. ) -> Result<()> {
  1169. with_workspace(cfg_override, |cfg| {
  1170. // Build if needed.
  1171. if !skip_build {
  1172. build(cfg_override, None, false, None, None, None, None)?;
  1173. }
  1174. // Run the deploy against the cluster in two cases:
  1175. //
  1176. // 1. The cluster is not localnet.
  1177. // 2. The cluster is localnet, but we're not booting a local validator.
  1178. //
  1179. // In either case, skip the deploy if the user specifies.
  1180. let is_localnet = cfg.provider.cluster == Cluster::Localnet;
  1181. if (!is_localnet || skip_local_validator) && !skip_deploy {
  1182. deploy(cfg_override, None)?;
  1183. }
  1184. // Start local test validator, if needed.
  1185. let mut validator_handle = None;
  1186. if is_localnet && (!skip_local_validator) {
  1187. let flags = match skip_deploy {
  1188. true => None,
  1189. false => Some(genesis_flags(cfg)?),
  1190. };
  1191. validator_handle = Some(start_test_validator(cfg, flags)?);
  1192. }
  1193. // Setup log reader.
  1194. let log_streams = stream_logs(cfg);
  1195. // Run the tests.
  1196. let test_result: Result<_> = {
  1197. let cmd = cfg
  1198. .scripts
  1199. .get("test")
  1200. .expect("Not able to find command for `test`")
  1201. .clone();
  1202. let mut args: Vec<&str> = cmd
  1203. .split(' ')
  1204. .chain(extra_args.iter().map(|arg| arg.as_str()))
  1205. .collect();
  1206. let program = args.remove(0);
  1207. std::process::Command::new(program)
  1208. .args(args)
  1209. .env("ANCHOR_PROVIDER_URL", cfg.provider.cluster.url())
  1210. .stdout(Stdio::inherit())
  1211. .stderr(Stdio::inherit())
  1212. .output()
  1213. .map_err(anyhow::Error::from)
  1214. .context(cmd)
  1215. };
  1216. // Check all errors and shut down.
  1217. if let Some(mut child) = validator_handle {
  1218. if let Err(err) = child.kill() {
  1219. println!("Failed to kill subprocess {}: {}", child.id(), err);
  1220. }
  1221. }
  1222. for mut child in log_streams? {
  1223. if let Err(err) = child.kill() {
  1224. println!("Failed to kill subprocess {}: {}", child.id(), err);
  1225. }
  1226. }
  1227. match test_result {
  1228. Ok(exit) => {
  1229. if !exit.status.success() {
  1230. std::process::exit(exit.status.code().unwrap());
  1231. }
  1232. }
  1233. Err(err) => {
  1234. println!("Failed to run test: {:#}", err)
  1235. }
  1236. }
  1237. Ok(())
  1238. })
  1239. }
  1240. // Returns the solana-test-validator flags to embed the workspace programs
  1241. // in the genesis block. This allows us to run tests without every deploying.
  1242. fn genesis_flags(cfg: &WithPath<Config>) -> Result<Vec<String>> {
  1243. let programs = cfg.programs.get(&Cluster::Localnet);
  1244. let mut flags = Vec::new();
  1245. for mut program in cfg.read_all_programs()? {
  1246. let binary_path = program.binary_path().display().to_string();
  1247. let address = programs
  1248. .and_then(|m| m.get(&program.lib_name))
  1249. .map(|deployment| deployment.address.to_string())
  1250. .unwrap_or_else(|| {
  1251. let kp = Keypair::generate(&mut OsRng);
  1252. kp.pubkey().to_string()
  1253. });
  1254. flags.push("--bpf-program".to_string());
  1255. flags.push(address.clone());
  1256. flags.push(binary_path);
  1257. if let Some(mut idl) = program.idl.as_mut() {
  1258. // Add program address to the IDL.
  1259. idl.metadata = Some(serde_json::to_value(IdlTestMetadata { address })?);
  1260. // Persist it.
  1261. let idl_out = PathBuf::from("target/idl")
  1262. .join(&idl.name)
  1263. .with_extension("json");
  1264. write_idl(idl, OutFile::File(idl_out))?;
  1265. }
  1266. }
  1267. if let Some(test) = cfg.test.as_ref() {
  1268. for entry in &test.genesis {
  1269. flags.push("--bpf-program".to_string());
  1270. flags.push(entry.address.clone());
  1271. flags.push(entry.program.clone());
  1272. }
  1273. }
  1274. Ok(flags)
  1275. }
  1276. fn stream_logs(config: &WithPath<Config>) -> Result<Vec<std::process::Child>> {
  1277. let program_logs_dir = ".anchor/program-logs";
  1278. if Path::new(program_logs_dir).exists() {
  1279. std::fs::remove_dir_all(program_logs_dir)?;
  1280. }
  1281. fs::create_dir_all(program_logs_dir)?;
  1282. let mut handles = vec![];
  1283. for program in config.read_all_programs()? {
  1284. let mut file = File::open(&format!("target/idl/{}.json", program.lib_name))?;
  1285. let mut contents = vec![];
  1286. file.read_to_end(&mut contents)?;
  1287. let idl: Idl = serde_json::from_slice(&contents)?;
  1288. let metadata = idl
  1289. .metadata
  1290. .ok_or_else(|| anyhow!("Program address not found."))?;
  1291. let metadata: IdlTestMetadata = serde_json::from_value(metadata)?;
  1292. let log_file = File::create(format!(
  1293. "{}/{}.{}.log",
  1294. program_logs_dir, metadata.address, program.lib_name,
  1295. ))?;
  1296. let stdio = std::process::Stdio::from(log_file);
  1297. let child = std::process::Command::new("solana")
  1298. .arg("logs")
  1299. .arg(metadata.address)
  1300. .arg("--url")
  1301. .arg(config.provider.cluster.url())
  1302. .stdout(stdio)
  1303. .spawn()?;
  1304. handles.push(child);
  1305. }
  1306. if let Some(test) = config.test.as_ref() {
  1307. for entry in &test.genesis {
  1308. let log_file = File::create(format!("{}/{}.log", program_logs_dir, entry.address))?;
  1309. let stdio = std::process::Stdio::from(log_file);
  1310. let child = std::process::Command::new("solana")
  1311. .arg("logs")
  1312. .arg(entry.address.clone())
  1313. .arg("--url")
  1314. .arg(config.provider.cluster.url())
  1315. .stdout(stdio)
  1316. .spawn()?;
  1317. handles.push(child);
  1318. }
  1319. }
  1320. Ok(handles)
  1321. }
  1322. #[derive(Debug, Serialize, Deserialize)]
  1323. pub struct IdlTestMetadata {
  1324. address: String,
  1325. }
  1326. fn start_test_validator(cfg: &Config, flags: Option<Vec<String>>) -> Result<Child> {
  1327. fs::create_dir_all(".anchor")?;
  1328. let test_ledger_filename = ".anchor/test-ledger";
  1329. let test_ledger_log_filename = ".anchor/test-ledger-log.txt";
  1330. if Path::new(test_ledger_filename).exists() {
  1331. std::fs::remove_dir_all(test_ledger_filename)?;
  1332. }
  1333. if Path::new(test_ledger_log_filename).exists() {
  1334. std::fs::remove_file(test_ledger_log_filename)?;
  1335. }
  1336. // Start a validator for testing.
  1337. let test_validator_stdout = File::create(test_ledger_log_filename)?;
  1338. let test_validator_stderr = test_validator_stdout.try_clone()?;
  1339. let validator_handle = std::process::Command::new("solana-test-validator")
  1340. .arg("--ledger")
  1341. .arg(test_ledger_filename)
  1342. .arg("--mint")
  1343. .arg(cfg.wallet_kp()?.pubkey().to_string())
  1344. .args(flags.unwrap_or_default())
  1345. .stdout(Stdio::from(test_validator_stdout))
  1346. .stderr(Stdio::from(test_validator_stderr))
  1347. .spawn()
  1348. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  1349. // Wait for the validator to be ready.
  1350. let client = RpcClient::new("http://localhost:8899".to_string());
  1351. let mut count = 0;
  1352. let ms_wait = 5000;
  1353. while count < ms_wait {
  1354. let r = client.get_recent_blockhash();
  1355. if r.is_ok() {
  1356. break;
  1357. }
  1358. std::thread::sleep(std::time::Duration::from_millis(1));
  1359. count += 1;
  1360. }
  1361. if count == 5000 {
  1362. println!("Unable to start test validator.");
  1363. std::process::exit(1);
  1364. }
  1365. Ok(validator_handle)
  1366. }
  1367. fn deploy(cfg_override: &ConfigOverride, program_name: Option<String>) -> Result<()> {
  1368. _deploy(cfg_override, program_name).map(|_| ())
  1369. }
  1370. fn _deploy(
  1371. cfg_override: &ConfigOverride,
  1372. program_str: Option<String>,
  1373. ) -> Result<Vec<(Pubkey, Program)>> {
  1374. with_workspace(cfg_override, |cfg| {
  1375. let url = cfg.provider.cluster.url().to_string();
  1376. let keypair = cfg.provider.wallet.to_string();
  1377. // Deploy the programs.
  1378. println!("Deploying workspace: {}", url);
  1379. println!("Upgrade authority: {}", keypair);
  1380. let mut programs = Vec::new();
  1381. for mut program in cfg.read_all_programs()? {
  1382. if let Some(single_prog_str) = &program_str {
  1383. let program_name = program.path.file_name().unwrap().to_str().unwrap();
  1384. if single_prog_str.as_str() != program_name {
  1385. continue;
  1386. }
  1387. }
  1388. let binary_path = program.binary_path().display().to_string();
  1389. println!(
  1390. "Deploying program {:?}...",
  1391. program.path.file_name().unwrap().to_str().unwrap()
  1392. );
  1393. println!("Program path: {}...", binary_path);
  1394. // Write the program's keypair filepath. This forces a new deploy
  1395. // address.
  1396. let program_kp = Keypair::generate(&mut OsRng);
  1397. let mut file = File::create(program.anchor_keypair_path())?;
  1398. file.write_all(format!("{:?}", &program_kp.to_bytes()).as_bytes())?;
  1399. // Send deploy transactions.
  1400. let exit = std::process::Command::new("solana")
  1401. .arg("program")
  1402. .arg("deploy")
  1403. .arg("--url")
  1404. .arg(&url)
  1405. .arg("--keypair")
  1406. .arg(&keypair)
  1407. .arg("--program-id")
  1408. .arg(program.anchor_keypair_path().display().to_string())
  1409. .arg(&binary_path)
  1410. .stdout(Stdio::inherit())
  1411. .stderr(Stdio::inherit())
  1412. .output()
  1413. .expect("Must deploy");
  1414. if !exit.status.success() {
  1415. println!("There was a problem deploying: {:?}.", exit);
  1416. std::process::exit(exit.status.code().unwrap_or(1));
  1417. }
  1418. if let Some(mut idl) = program.idl.as_mut() {
  1419. // Add program address to the IDL.
  1420. idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
  1421. address: program_kp.pubkey().to_string(),
  1422. })?);
  1423. // Persist it.
  1424. let idl_out = PathBuf::from("target/idl")
  1425. .join(&idl.name)
  1426. .with_extension("json");
  1427. write_idl(idl, OutFile::File(idl_out))?;
  1428. }
  1429. programs.push((program_kp.pubkey(), program))
  1430. }
  1431. println!("Deploy success");
  1432. Ok(programs)
  1433. })
  1434. }
  1435. fn upgrade(
  1436. cfg_override: &ConfigOverride,
  1437. program_id: Pubkey,
  1438. program_filepath: String,
  1439. ) -> Result<()> {
  1440. let path: PathBuf = program_filepath.parse().unwrap();
  1441. let program_filepath = path.canonicalize()?.display().to_string();
  1442. with_workspace(cfg_override, |cfg| {
  1443. let exit = std::process::Command::new("solana")
  1444. .arg("program")
  1445. .arg("deploy")
  1446. .arg("--url")
  1447. .arg(cfg.provider.cluster.url())
  1448. .arg("--keypair")
  1449. .arg(&cfg.provider.wallet.to_string())
  1450. .arg("--program-id")
  1451. .arg(program_id.to_string())
  1452. .arg(&program_filepath)
  1453. .stdout(Stdio::inherit())
  1454. .stderr(Stdio::inherit())
  1455. .output()
  1456. .expect("Must deploy");
  1457. if !exit.status.success() {
  1458. println!("There was a problem deploying: {:?}.", exit);
  1459. std::process::exit(exit.status.code().unwrap_or(1));
  1460. }
  1461. Ok(())
  1462. })
  1463. }
  1464. // The Solana CLI doesn't redeploy a program if this file exists.
  1465. // So remove it to make all commands explicit.
  1466. fn clear_program_keys(cfg_override: &ConfigOverride) -> Result<()> {
  1467. let config = Config::discover(cfg_override).unwrap_or_default().unwrap();
  1468. for program in config.read_all_programs()? {
  1469. let anchor_keypair_path = program.anchor_keypair_path();
  1470. if Path::exists(&anchor_keypair_path) {
  1471. std::fs::remove_file(anchor_keypair_path).expect("Always remove");
  1472. }
  1473. }
  1474. Ok(())
  1475. }
  1476. fn create_idl_account(
  1477. cfg: &Config,
  1478. keypair_path: &str,
  1479. program_id: &Pubkey,
  1480. idl: &Idl,
  1481. ) -> Result<Pubkey> {
  1482. // Misc.
  1483. let idl_address = IdlAccount::address(program_id);
  1484. let keypair = solana_sdk::signature::read_keypair_file(keypair_path)
  1485. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1486. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  1487. let idl_data = serialize_idl(idl)?;
  1488. // Run `Create instruction.
  1489. {
  1490. let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Create {
  1491. data_len: (idl_data.len() as u64) * 2, // Double for future growth.
  1492. })?;
  1493. let program_signer = Pubkey::find_program_address(&[], program_id).0;
  1494. let accounts = vec![
  1495. AccountMeta::new_readonly(keypair.pubkey(), true),
  1496. AccountMeta::new(idl_address, false),
  1497. AccountMeta::new_readonly(program_signer, false),
  1498. AccountMeta::new_readonly(solana_program::system_program::ID, false),
  1499. AccountMeta::new_readonly(*program_id, false),
  1500. AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
  1501. ];
  1502. let ix = Instruction {
  1503. program_id: *program_id,
  1504. accounts,
  1505. data,
  1506. };
  1507. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1508. let tx = Transaction::new_signed_with_payer(
  1509. &[ix],
  1510. Some(&keypair.pubkey()),
  1511. &[&keypair],
  1512. recent_hash,
  1513. );
  1514. client.send_and_confirm_transaction_with_spinner_and_config(
  1515. &tx,
  1516. CommitmentConfig::confirmed(),
  1517. RpcSendTransactionConfig {
  1518. skip_preflight: true,
  1519. ..RpcSendTransactionConfig::default()
  1520. },
  1521. )?;
  1522. }
  1523. // Write directly to the IDL account buffer.
  1524. idl_write(cfg, program_id, idl, IdlAccount::address(program_id))?;
  1525. Ok(idl_address)
  1526. }
  1527. fn create_idl_buffer(
  1528. cfg: &Config,
  1529. keypair_path: &str,
  1530. program_id: &Pubkey,
  1531. idl: &Idl,
  1532. ) -> Result<Pubkey> {
  1533. let keypair = solana_sdk::signature::read_keypair_file(keypair_path)
  1534. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1535. let client = RpcClient::new(cfg.provider.cluster.url().to_string());
  1536. let buffer = Keypair::generate(&mut OsRng);
  1537. // Creates the new buffer account with the system program.
  1538. let create_account_ix = {
  1539. let space = 8 + 32 + 4 + serialize_idl(idl)?.len() as usize;
  1540. let lamports = client.get_minimum_balance_for_rent_exemption(space)?;
  1541. solana_sdk::system_instruction::create_account(
  1542. &keypair.pubkey(),
  1543. &buffer.pubkey(),
  1544. lamports,
  1545. space as u64,
  1546. program_id,
  1547. )
  1548. };
  1549. // Program instruction to create the buffer.
  1550. let create_buffer_ix = {
  1551. let accounts = vec![
  1552. AccountMeta::new(buffer.pubkey(), false),
  1553. AccountMeta::new_readonly(keypair.pubkey(), true),
  1554. AccountMeta::new_readonly(sysvar::rent::ID, false),
  1555. ];
  1556. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  1557. data.append(&mut IdlInstruction::CreateBuffer.try_to_vec()?);
  1558. Instruction {
  1559. program_id: *program_id,
  1560. accounts,
  1561. data,
  1562. }
  1563. };
  1564. // Build the transaction.
  1565. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1566. let tx = Transaction::new_signed_with_payer(
  1567. &[create_account_ix, create_buffer_ix],
  1568. Some(&keypair.pubkey()),
  1569. &[&keypair, &buffer],
  1570. recent_hash,
  1571. );
  1572. // Send the transaction.
  1573. client.send_and_confirm_transaction_with_spinner_and_config(
  1574. &tx,
  1575. CommitmentConfig::confirmed(),
  1576. RpcSendTransactionConfig {
  1577. skip_preflight: true,
  1578. ..RpcSendTransactionConfig::default()
  1579. },
  1580. )?;
  1581. Ok(buffer.pubkey())
  1582. }
  1583. // Serialize and compress the idl.
  1584. fn serialize_idl(idl: &Idl) -> Result<Vec<u8>> {
  1585. let json_bytes = serde_json::to_vec(idl)?;
  1586. let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
  1587. e.write_all(&json_bytes)?;
  1588. e.finish().map_err(Into::into)
  1589. }
  1590. fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8>> {
  1591. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  1592. data.append(&mut ix_inner.try_to_vec()?);
  1593. Ok(data)
  1594. }
  1595. fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
  1596. with_workspace(cfg_override, |cfg| {
  1597. println!("Running migration deploy script");
  1598. let url = cfg.provider.cluster.url().to_string();
  1599. let cur_dir = std::env::current_dir()?;
  1600. let use_ts =
  1601. Path::new("tsconfig.json").exists() && Path::new("migrations/deploy.ts").exists();
  1602. if !Path::new(".anchor").exists() {
  1603. fs::create_dir(".anchor")?;
  1604. }
  1605. std::env::set_current_dir(".anchor")?;
  1606. let exit = if use_ts {
  1607. let module_path = cur_dir.join("migrations/deploy.ts");
  1608. let deploy_script_host_str =
  1609. template::deploy_ts_script_host(&url, &module_path.display().to_string());
  1610. std::fs::write("deploy.ts", deploy_script_host_str)?;
  1611. std::process::Command::new("ts-node")
  1612. .arg("deploy.ts")
  1613. .stdout(Stdio::inherit())
  1614. .stderr(Stdio::inherit())
  1615. .output()?
  1616. } else {
  1617. let module_path = cur_dir.join("migrations/deploy.js");
  1618. let deploy_script_host_str =
  1619. template::deploy_js_script_host(&url, &module_path.display().to_string());
  1620. std::fs::write("deploy.js", deploy_script_host_str)?;
  1621. std::process::Command::new("node")
  1622. .arg("deploy.js")
  1623. .stdout(Stdio::inherit())
  1624. .stderr(Stdio::inherit())
  1625. .output()?
  1626. };
  1627. if !exit.status.success() {
  1628. println!("Deploy failed.");
  1629. std::process::exit(exit.status.code().unwrap());
  1630. }
  1631. println!("Deploy complete.");
  1632. Ok(())
  1633. })
  1634. }
  1635. fn set_workspace_dir_or_exit() {
  1636. let d = match Config::discover(&ConfigOverride::default()) {
  1637. Err(_) => {
  1638. println!("Not in anchor workspace.");
  1639. std::process::exit(1);
  1640. }
  1641. Ok(d) => d,
  1642. };
  1643. match d {
  1644. None => {
  1645. println!("Not in anchor workspace.");
  1646. std::process::exit(1);
  1647. }
  1648. Some(cfg) => {
  1649. match cfg.path().parent() {
  1650. None => {
  1651. println!("Unable to make new program");
  1652. }
  1653. Some(parent) => {
  1654. if std::env::set_current_dir(&parent).is_err() {
  1655. println!("Not in anchor workspace.");
  1656. std::process::exit(1);
  1657. }
  1658. }
  1659. };
  1660. }
  1661. }
  1662. }
  1663. #[cfg(feature = "dev")]
  1664. fn airdrop(cfg_override: &ConfigOverride) -> Result<()> {
  1665. let url = cfg_override
  1666. .cluster
  1667. .unwrap_or_else(|| "https://api.devnet.solana.com".to_string());
  1668. loop {
  1669. let exit = std::process::Command::new("solana")
  1670. .arg("airdrop")
  1671. .arg("10")
  1672. .arg("--url")
  1673. .arg(&url)
  1674. .stdout(Stdio::inherit())
  1675. .stderr(Stdio::inherit())
  1676. .output()
  1677. .expect("Must airdrop");
  1678. if !exit.status.success() {
  1679. println!("There was a problem airdropping: {:?}.", exit);
  1680. std::process::exit(exit.status.code().unwrap_or(1));
  1681. }
  1682. std::thread::sleep(std::time::Duration::from_millis(10000));
  1683. }
  1684. }
  1685. fn cluster(_cmd: ClusterCommand) -> Result<()> {
  1686. println!("Cluster Endpoints:\n");
  1687. println!("* Mainnet - https://solana-api.projectserum.com");
  1688. println!("* Mainnet - https://api.mainnet-beta.solana.com");
  1689. println!("* Devnet - https://api.devnet.solana.com");
  1690. println!("* Testnet - https://api.testnet.solana.com");
  1691. Ok(())
  1692. }
  1693. fn shell(cfg_override: &ConfigOverride) -> Result<()> {
  1694. with_workspace(cfg_override, |cfg| {
  1695. let programs = {
  1696. // Create idl map from all workspace programs.
  1697. let mut idls: HashMap<String, Idl> = cfg
  1698. .read_all_programs()?
  1699. .iter()
  1700. .filter(|program| program.idl.is_some())
  1701. .map(|program| {
  1702. (
  1703. program.idl.as_ref().unwrap().name.clone(),
  1704. program.idl.clone().unwrap(),
  1705. )
  1706. })
  1707. .collect();
  1708. // Insert all manually specified idls into the idl map.
  1709. if let Some(programs) = cfg.programs.get(&cfg.provider.cluster) {
  1710. let _ = programs
  1711. .iter()
  1712. .map(|(name, pd)| {
  1713. if let Some(idl_fp) = &pd.idl {
  1714. let file_str =
  1715. std::fs::read_to_string(idl_fp).expect("Unable to read IDL file");
  1716. let idl = serde_json::from_str(&file_str).expect("Idl not readable");
  1717. idls.insert(name.clone(), idl);
  1718. }
  1719. })
  1720. .collect::<Vec<_>>();
  1721. }
  1722. // Finalize program list with all programs with IDLs.
  1723. match cfg.programs.get(&cfg.provider.cluster) {
  1724. None => Vec::new(),
  1725. Some(programs) => programs
  1726. .iter()
  1727. .filter_map(|(name, program_deployment)| {
  1728. Some(ProgramWorkspace {
  1729. name: name.to_string(),
  1730. program_id: program_deployment.address,
  1731. idl: match idls.get(name) {
  1732. None => return None,
  1733. Some(idl) => idl.clone(),
  1734. },
  1735. })
  1736. })
  1737. .collect::<Vec<ProgramWorkspace>>(),
  1738. }
  1739. };
  1740. let js_code = template::node_shell(
  1741. cfg.provider.cluster.url(),
  1742. &cfg.provider.wallet.to_string(),
  1743. programs,
  1744. )?;
  1745. let mut child = std::process::Command::new("node")
  1746. .args(&["-e", &js_code, "-i", "--experimental-repl-await"])
  1747. .stdout(Stdio::inherit())
  1748. .stderr(Stdio::inherit())
  1749. .spawn()
  1750. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  1751. if !child.wait()?.success() {
  1752. println!("Error running node shell");
  1753. return Ok(());
  1754. }
  1755. Ok(())
  1756. })
  1757. }
  1758. fn run(cfg_override: &ConfigOverride, script: String) -> Result<()> {
  1759. with_workspace(cfg_override, |cfg| {
  1760. let script = cfg
  1761. .scripts
  1762. .get(&script)
  1763. .ok_or_else(|| anyhow!("Unable to find script"))?;
  1764. let exit = std::process::Command::new("bash")
  1765. .arg("-c")
  1766. .arg(&script)
  1767. .stdout(Stdio::inherit())
  1768. .stderr(Stdio::inherit())
  1769. .output()
  1770. .unwrap();
  1771. if !exit.status.success() {
  1772. std::process::exit(exit.status.code().unwrap_or(1));
  1773. }
  1774. Ok(())
  1775. })
  1776. }
  1777. fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> {
  1778. let dir = shellexpand::tilde("~/.config/anchor");
  1779. if !Path::new(&dir.to_string()).exists() {
  1780. fs::create_dir(dir.to_string())?;
  1781. }
  1782. std::env::set_current_dir(dir.to_string())?;
  1783. // Freely overwrite the entire file since it's not used for anything else.
  1784. let mut file = File::create("credentials")?;
  1785. file.write_all(template::credentials(&token).as_bytes())?;
  1786. Ok(())
  1787. }
  1788. fn publish(cfg_override: &ConfigOverride, program_name: String) -> Result<()> {
  1789. // Discover the various workspace configs.
  1790. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  1791. let program = cfg
  1792. .get_program(&program_name)?
  1793. .ok_or_else(|| anyhow!("Workspace member not found"))?;
  1794. let program_cargo_lock = pathdiff::diff_paths(
  1795. program.path().join("Cargo.lock"),
  1796. cfg.path().parent().unwrap(),
  1797. )
  1798. .ok_or_else(|| anyhow!("Unable to diff Cargo.lock path"))?;
  1799. let cargo_lock = Path::new("Cargo.lock");
  1800. // There must be a Cargo.lock
  1801. if !program_cargo_lock.exists() && !cargo_lock.exists() {
  1802. return Err(anyhow!("Cargo.lock must exist for a verifiable build"));
  1803. }
  1804. println!("Publishing will make your code public. Are you sure? Enter (yes)/no:");
  1805. let answer = std::io::stdin().lock().lines().next().unwrap().unwrap();
  1806. if answer != "yes" {
  1807. println!("Aborting");
  1808. return Ok(());
  1809. }
  1810. let anchor_package = AnchorPackage::from(program_name.clone(), &cfg)?;
  1811. let anchor_package_bytes = serde_json::to_vec(&anchor_package)?;
  1812. // Build the program before sending it to the server.
  1813. build(
  1814. cfg_override,
  1815. None,
  1816. true,
  1817. Some(program_name.clone()),
  1818. cfg.solana_version.clone(),
  1819. None,
  1820. None,
  1821. )?;
  1822. // Set directory to top of the workspace.
  1823. let workspace_dir = cfg.path().parent().unwrap();
  1824. std::env::set_current_dir(workspace_dir)?;
  1825. // Create the workspace tarball.
  1826. let dot_anchor = workspace_dir.join(".anchor");
  1827. fs::create_dir_all(&dot_anchor)?;
  1828. let tarball_filename = dot_anchor.join(format!("{}.tar.gz", program_name));
  1829. let tar_gz = File::create(&tarball_filename)?;
  1830. let enc = GzEncoder::new(tar_gz, Compression::default());
  1831. let mut tar = tar::Builder::new(enc);
  1832. // Files that will always be included if they exist.
  1833. println!("PACKING: Anchor.toml");
  1834. tar.append_path("Anchor.toml")?;
  1835. if cargo_lock.exists() {
  1836. println!("PACKING: Cargo.lock");
  1837. tar.append_path(cargo_lock)?;
  1838. }
  1839. if Path::new("Cargo.toml").exists() {
  1840. println!("PACKING: Cargo.toml");
  1841. tar.append_path("Cargo.toml")?;
  1842. }
  1843. if Path::new("LICENSE").exists() {
  1844. println!("PACKING: LICENSE");
  1845. tar.append_path("LICENSE")?;
  1846. }
  1847. if Path::new("README.md").exists() {
  1848. println!("PACKING: README.md");
  1849. tar.append_path("README.md")?;
  1850. }
  1851. // All workspace programs.
  1852. for path in cfg.get_program_list()? {
  1853. let mut dirs = walkdir::WalkDir::new(&path)
  1854. .into_iter()
  1855. .filter_entry(|e| !is_hidden(e));
  1856. // Skip the parent dir.
  1857. let _ = dirs.next().unwrap()?;
  1858. for entry in dirs {
  1859. let e = entry.map_err(|e| anyhow!("{:?}", e))?;
  1860. let e = pathdiff::diff_paths(e.path(), cfg.path().parent().unwrap())
  1861. .ok_or_else(|| anyhow!("Unable to diff paths"))?;
  1862. let path_str = e.display().to_string();
  1863. // Skip target dir.
  1864. if !path_str.contains("target/") && !path_str.contains("/target") {
  1865. // Only add the file if it's not empty.
  1866. let metadata = std::fs::File::open(&e)?.metadata()?;
  1867. if metadata.len() > 0 {
  1868. println!("PACKING: {}", e.display().to_string());
  1869. if e.is_dir() {
  1870. tar.append_dir_all(&e, &e)?;
  1871. } else {
  1872. tar.append_path(&e)?;
  1873. }
  1874. }
  1875. }
  1876. }
  1877. }
  1878. // Tar pack complete.
  1879. tar.into_inner()?;
  1880. // Upload the tarball to the server.
  1881. let token = registry_api_token(cfg_override)?;
  1882. let form = Form::new()
  1883. .part("manifest", Part::bytes(anchor_package_bytes))
  1884. .part("workspace", {
  1885. let file = File::open(&tarball_filename)?;
  1886. Part::reader(file)
  1887. });
  1888. let client = Client::new();
  1889. let resp = client
  1890. .post(&format!("{}/api/v0/build", cfg.registry.url))
  1891. .bearer_auth(token)
  1892. .multipart(form)
  1893. .send()?;
  1894. if resp.status() == 200 {
  1895. println!("Build triggered");
  1896. } else {
  1897. println!(
  1898. "{:?}",
  1899. resp.text().unwrap_or_else(|_| "Server error".to_string())
  1900. );
  1901. }
  1902. Ok(())
  1903. }
  1904. fn registry_api_token(_cfg_override: &ConfigOverride) -> Result<String> {
  1905. #[derive(Debug, Deserialize)]
  1906. struct Registry {
  1907. token: String,
  1908. }
  1909. #[derive(Debug, Deserialize)]
  1910. struct Credentials {
  1911. registry: Registry,
  1912. }
  1913. let filename = shellexpand::tilde("~/.config/anchor/credentials");
  1914. let mut file = File::open(filename.to_string())?;
  1915. let mut contents = String::new();
  1916. file.read_to_string(&mut contents)?;
  1917. let credentials_toml: Credentials = toml::from_str(&contents)?;
  1918. Ok(credentials_toml.registry.token)
  1919. }
  1920. // with_workspace ensures the current working directory is always the top level
  1921. // workspace directory, i.e., where the `Anchor.toml` file is located, before
  1922. // and after the closure invocation.
  1923. //
  1924. // The closure passed into this function must never change the working directory
  1925. // to be outside the workspace. Doing so will have undefined behavior.
  1926. fn with_workspace<R>(cfg_override: &ConfigOverride, f: impl FnOnce(&WithPath<Config>) -> R) -> R {
  1927. set_workspace_dir_or_exit();
  1928. clear_program_keys(cfg_override).unwrap();
  1929. let cfg = Config::discover(cfg_override)
  1930. .expect("Previously set the workspace dir")
  1931. .expect("Anchor.toml must always exist");
  1932. let r = f(&cfg);
  1933. set_workspace_dir_or_exit();
  1934. clear_program_keys(cfg_override).unwrap();
  1935. r
  1936. }
  1937. fn is_hidden(entry: &walkdir::DirEntry) -> bool {
  1938. entry
  1939. .file_name()
  1940. .to_str()
  1941. .map(|s| s == "." || s.starts_with('.') || s == "target")
  1942. .unwrap_or(false)
  1943. }