lib.rs 93 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853
  1. use crate::config::{
  2. AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride, Manifest, ProgramDeployment,
  3. ProgramWorkspace, Test, WithPath,
  4. };
  5. use anchor_client::Cluster;
  6. use anchor_lang::idl::{IdlAccount, IdlInstruction};
  7. use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
  8. use anchor_syn::idl::Idl;
  9. use anyhow::{anyhow, Context, Result};
  10. use clap::Parser;
  11. use flate2::read::GzDecoder;
  12. use flate2::read::ZlibDecoder;
  13. use flate2::write::{GzEncoder, ZlibEncoder};
  14. use flate2::Compression;
  15. use heck::SnakeCase;
  16. use rand::rngs::OsRng;
  17. use reqwest::blocking::multipart::{Form, Part};
  18. use reqwest::blocking::Client;
  19. use semver::{Version, VersionReq};
  20. use serde::{Deserialize, Serialize};
  21. use solana_client::rpc_client::RpcClient;
  22. use solana_client::rpc_config::RpcSendTransactionConfig;
  23. use solana_program::instruction::{AccountMeta, Instruction};
  24. use solana_sdk::account_utils::StateMut;
  25. use solana_sdk::bpf_loader;
  26. use solana_sdk::bpf_loader_deprecated;
  27. use solana_sdk::bpf_loader_upgradeable::{self, UpgradeableLoaderState};
  28. use solana_sdk::commitment_config::CommitmentConfig;
  29. use solana_sdk::pubkey::Pubkey;
  30. use solana_sdk::signature::Keypair;
  31. use solana_sdk::signature::Signer;
  32. use solana_sdk::sysvar;
  33. use solana_sdk::transaction::Transaction;
  34. use std::collections::BTreeMap;
  35. use std::collections::HashMap;
  36. use std::ffi::OsString;
  37. use std::fs::{self, File};
  38. use std::io::prelude::*;
  39. use std::path::{Path, PathBuf};
  40. use std::process::{Child, Stdio};
  41. use std::string::ToString;
  42. use tar::Archive;
  43. pub mod config;
  44. pub mod template;
  45. // Version of the docker image.
  46. pub const VERSION: &str = env!("CARGO_PKG_VERSION");
  47. pub const DOCKER_BUILDER_VERSION: &str = VERSION;
  48. #[derive(Debug, Parser)]
  49. #[clap(version = VERSION)]
  50. pub struct Opts {
  51. #[clap(flatten)]
  52. pub cfg_override: ConfigOverride,
  53. #[clap(subcommand)]
  54. pub command: Command,
  55. }
  56. #[derive(Debug, Parser)]
  57. pub enum Command {
  58. /// Initializes a workspace.
  59. Init {
  60. name: String,
  61. #[clap(short, long)]
  62. javascript: bool,
  63. },
  64. /// Builds the workspace.
  65. Build {
  66. /// Output directory for the IDL.
  67. #[clap(short, long)]
  68. idl: Option<String>,
  69. /// Output directory for the TypeScript IDL.
  70. #[clap(short = 't', long)]
  71. idl_ts: Option<String>,
  72. /// True if the build artifact needs to be deterministic and verifiable.
  73. #[clap(short, long)]
  74. verifiable: bool,
  75. #[clap(short, long)]
  76. program_name: Option<String>,
  77. /// Version of the Solana toolchain to use. For --verifiable builds
  78. /// only.
  79. #[clap(short, long)]
  80. solana_version: Option<String>,
  81. /// Docker image to use. For --verifiable builds only.
  82. #[clap(short, long)]
  83. docker_image: Option<String>,
  84. /// Bootstrap docker image from scratch, installing all requirements for
  85. /// verifiable builds. Only works for debian-based images.
  86. #[clap(arg_enum, short, long, default_value = "none")]
  87. bootstrap: BootstrapMode,
  88. /// Arguments to pass to the underlying `cargo build-bpf` command
  89. #[clap(
  90. required = false,
  91. takes_value = true,
  92. multiple_values = true,
  93. last = true
  94. )]
  95. cargo_args: Vec<String>,
  96. },
  97. /// Expands macros (wrapper around cargo expand)
  98. ///
  99. /// Use it in a program folder to expand program
  100. ///
  101. /// Use it in a workspace but outside a program
  102. /// folder to expand the entire workspace
  103. Expand {
  104. /// Expand only this program
  105. #[clap(short, long)]
  106. program_name: Option<String>,
  107. /// Arguments to pass to the underlying `cargo expand` command
  108. #[clap(
  109. required = false,
  110. takes_value = true,
  111. multiple_values = true,
  112. last = true
  113. )]
  114. cargo_args: Vec<String>,
  115. },
  116. /// Verifies the on-chain bytecode matches the locally compiled artifact.
  117. /// Run this command inside a program subdirectory, i.e., in the dir
  118. /// containing the program's Cargo.toml.
  119. Verify {
  120. /// The deployed program to compare against.
  121. program_id: Pubkey,
  122. #[clap(short, long)]
  123. program_name: Option<String>,
  124. /// Version of the Solana toolchain to use. For --verifiable builds
  125. /// only.
  126. #[clap(short, long)]
  127. solana_version: Option<String>,
  128. /// Docker image to use. For --verifiable builds only.
  129. #[clap(short, long)]
  130. docker_image: Option<String>,
  131. /// Bootstrap docker image from scratch, installing all requirements for
  132. /// verifiable builds. Only works for debian-based images.
  133. #[clap(arg_enum, short, long, default_value = "none")]
  134. bootstrap: BootstrapMode,
  135. /// Arguments to pass to the underlying `cargo build-bpf` command.
  136. #[clap(
  137. required = false,
  138. takes_value = true,
  139. multiple_values = true,
  140. last = true
  141. )]
  142. cargo_args: Vec<String>,
  143. },
  144. /// Runs integration tests against a localnetwork.
  145. Test {
  146. /// Use this flag if you want to run tests against previously deployed
  147. /// programs.
  148. #[clap(long)]
  149. skip_deploy: bool,
  150. /// Flag to skip starting a local validator, if the configured cluster
  151. /// url is a localnet.
  152. #[clap(long)]
  153. skip_local_validator: bool,
  154. /// Flag to skip building the program in the workspace,
  155. /// use this to save time when running test and the program code is not altered.
  156. #[clap(long)]
  157. skip_build: bool,
  158. /// Flag to keep the local validator running after tests
  159. /// to be able to check the transactions.
  160. #[clap(long)]
  161. detach: bool,
  162. #[clap(multiple_values = true)]
  163. args: Vec<String>,
  164. /// Arguments to pass to the underlying `cargo build-bpf` command.
  165. #[clap(
  166. required = false,
  167. takes_value = true,
  168. multiple_values = true,
  169. last = true
  170. )]
  171. cargo_args: Vec<String>,
  172. },
  173. /// Creates a new program.
  174. New { name: String },
  175. /// Commands for interacting with interface definitions.
  176. Idl {
  177. #[clap(subcommand)]
  178. subcmd: IdlCommand,
  179. },
  180. /// Deploys each program in the workspace.
  181. Deploy {
  182. #[clap(short, long)]
  183. program_name: Option<String>,
  184. },
  185. /// Runs the deploy migration script.
  186. Migrate,
  187. /// Deploys, initializes an IDL, and migrates all in one command.
  188. /// Upgrades a single program. The configured wallet must be the upgrade
  189. /// authority.
  190. Upgrade {
  191. /// The program to upgrade.
  192. #[clap(short, long)]
  193. program_id: Pubkey,
  194. /// Filepath to the new program binary.
  195. program_filepath: String,
  196. },
  197. #[cfg(feature = "dev")]
  198. /// Runs an airdrop loop, continuously funding the configured wallet.
  199. Airdrop {
  200. #[clap(short, long)]
  201. url: Option<String>,
  202. },
  203. /// Cluster commands.
  204. Cluster {
  205. #[clap(subcommand)]
  206. subcmd: ClusterCommand,
  207. },
  208. /// Starts a node shell with an Anchor client setup according to the local
  209. /// config.
  210. Shell,
  211. /// Runs the script defined by the current workspace's Anchor.toml.
  212. Run {
  213. /// The name of the script to run.
  214. script: String,
  215. },
  216. /// Saves an api token from the registry locally.
  217. Login {
  218. /// API access token.
  219. token: String,
  220. },
  221. /// Publishes a verified build to the Anchor registry.
  222. Publish {
  223. /// The name of the program to publish.
  224. program: String,
  225. /// Arguments to pass to the underlying `cargo build-bpf` command.
  226. #[clap(
  227. required = false,
  228. takes_value = true,
  229. multiple_values = true,
  230. last = true
  231. )]
  232. cargo_args: Vec<String>,
  233. },
  234. /// Keypair commands.
  235. Keys {
  236. #[clap(subcommand)]
  237. subcmd: KeysCommand,
  238. },
  239. /// Localnet commands.
  240. Localnet {
  241. /// Flag to skip building the program in the workspace,
  242. /// use this to save time when running test and the program code is not altered.
  243. #[clap(long)]
  244. skip_build: bool,
  245. /// Use this flag if you want to run tests against previously deployed
  246. /// programs.
  247. #[clap(long)]
  248. skip_deploy: bool,
  249. /// Arguments to pass to the underlying `cargo build-bpf` command.
  250. #[clap(
  251. required = false,
  252. takes_value = true,
  253. multiple_values = true,
  254. last = true
  255. )]
  256. cargo_args: Vec<String>,
  257. },
  258. }
  259. #[derive(Debug, Parser)]
  260. pub enum KeysCommand {
  261. List,
  262. }
  263. #[derive(Debug, Parser)]
  264. pub enum IdlCommand {
  265. /// Initializes a program's IDL account. Can only be run once.
  266. Init {
  267. program_id: Pubkey,
  268. #[clap(short, long)]
  269. filepath: String,
  270. },
  271. /// Writes an IDL into a buffer account. This can be used with SetBuffer
  272. /// to perform an upgrade.
  273. WriteBuffer {
  274. program_id: Pubkey,
  275. #[clap(short, long)]
  276. filepath: String,
  277. },
  278. /// Sets a new IDL buffer for the program.
  279. SetBuffer {
  280. program_id: Pubkey,
  281. /// Address of the buffer account to set as the idl on the program.
  282. #[clap(short, long)]
  283. buffer: Pubkey,
  284. },
  285. /// Upgrades the IDL to the new file. An alias for first writing and then
  286. /// then setting the idl buffer account.
  287. Upgrade {
  288. program_id: Pubkey,
  289. #[clap(short, long)]
  290. filepath: String,
  291. },
  292. /// Sets a new authority on the IDL account.
  293. SetAuthority {
  294. /// The IDL account buffer to set the authority of. If none is given,
  295. /// then the canonical IDL account is used.
  296. address: Option<Pubkey>,
  297. /// Program to change the IDL authority.
  298. #[clap(short, long)]
  299. program_id: Pubkey,
  300. /// New authority of the IDL account.
  301. #[clap(short, long)]
  302. new_authority: Pubkey,
  303. },
  304. /// Command to remove the ability to modify the IDL account. This should
  305. /// likely be used in conjection with eliminating an "upgrade authority" on
  306. /// the program.
  307. EraseAuthority {
  308. #[clap(short, long)]
  309. program_id: Pubkey,
  310. },
  311. /// Outputs the authority for the IDL account.
  312. Authority {
  313. /// The program to view.
  314. program_id: Pubkey,
  315. },
  316. /// Parses an IDL from source.
  317. Parse {
  318. /// Path to the program's interface definition.
  319. #[clap(short, long)]
  320. file: String,
  321. /// Output file for the IDL (stdout if not specified).
  322. #[clap(short, long)]
  323. out: Option<String>,
  324. /// Output file for the TypeScript IDL.
  325. #[clap(short = 't', long)]
  326. out_ts: Option<String>,
  327. },
  328. /// Fetches an IDL for the given address from a cluster.
  329. /// The address can be a program, IDL account, or IDL buffer.
  330. Fetch {
  331. address: Pubkey,
  332. /// Output file for the idl (stdout if not specified).
  333. #[clap(short, long)]
  334. out: Option<String>,
  335. },
  336. }
  337. #[derive(Debug, Parser)]
  338. pub enum ClusterCommand {
  339. /// Prints common cluster urls.
  340. List,
  341. }
  342. pub fn entry(opts: Opts) -> Result<()> {
  343. match opts.command {
  344. Command::Init { name, javascript } => init(&opts.cfg_override, name, javascript),
  345. Command::New { name } => new(&opts.cfg_override, name),
  346. Command::Build {
  347. idl,
  348. idl_ts,
  349. verifiable,
  350. program_name,
  351. solana_version,
  352. docker_image,
  353. bootstrap,
  354. cargo_args,
  355. } => build(
  356. &opts.cfg_override,
  357. idl,
  358. idl_ts,
  359. verifiable,
  360. program_name,
  361. solana_version,
  362. docker_image,
  363. bootstrap,
  364. None,
  365. None,
  366. cargo_args,
  367. ),
  368. Command::Verify {
  369. program_id,
  370. program_name,
  371. solana_version,
  372. docker_image,
  373. bootstrap,
  374. cargo_args,
  375. } => verify(
  376. &opts.cfg_override,
  377. program_id,
  378. program_name,
  379. solana_version,
  380. docker_image,
  381. bootstrap,
  382. cargo_args,
  383. ),
  384. Command::Deploy { program_name } => deploy(&opts.cfg_override, program_name),
  385. Command::Expand {
  386. program_name,
  387. cargo_args,
  388. } => expand(&opts.cfg_override, program_name, &cargo_args),
  389. Command::Upgrade {
  390. program_id,
  391. program_filepath,
  392. } => upgrade(&opts.cfg_override, program_id, program_filepath),
  393. Command::Idl { subcmd } => idl(&opts.cfg_override, subcmd),
  394. Command::Migrate => migrate(&opts.cfg_override),
  395. Command::Test {
  396. skip_deploy,
  397. skip_local_validator,
  398. skip_build,
  399. detach,
  400. args,
  401. cargo_args,
  402. } => test(
  403. &opts.cfg_override,
  404. skip_deploy,
  405. skip_local_validator,
  406. skip_build,
  407. detach,
  408. args,
  409. cargo_args,
  410. ),
  411. #[cfg(feature = "dev")]
  412. Command::Airdrop { .. } => airdrop(&opts.cfg_override),
  413. Command::Cluster { subcmd } => cluster(subcmd),
  414. Command::Shell => shell(&opts.cfg_override),
  415. Command::Run { script } => run(&opts.cfg_override, script),
  416. Command::Login { token } => login(&opts.cfg_override, token),
  417. Command::Publish {
  418. program,
  419. cargo_args,
  420. } => publish(&opts.cfg_override, program, cargo_args),
  421. Command::Keys { subcmd } => keys(&opts.cfg_override, subcmd),
  422. Command::Localnet {
  423. skip_build,
  424. skip_deploy,
  425. cargo_args,
  426. } => localnet(&opts.cfg_override, skip_build, skip_deploy, cargo_args),
  427. }
  428. }
  429. fn init(cfg_override: &ConfigOverride, name: String, javascript: bool) -> Result<()> {
  430. if Config::discover(cfg_override)?.is_some() {
  431. return Err(anyhow!("Workspace already initialized"));
  432. }
  433. // The list is taken from https://doc.rust-lang.org/reference/keywords.html.
  434. let key_words = [
  435. "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
  436. "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
  437. "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe",
  438. "use", "where", "while", "async", "await", "dyn", "abstract", "become", "box", "do",
  439. "final", "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", "try",
  440. "unique",
  441. ];
  442. if key_words.contains(&name[..].into()) {
  443. return Err(anyhow!(
  444. "{} is a reserved word in rust, name your project something else!",
  445. name
  446. ));
  447. } else if name.chars().next().unwrap().is_numeric() {
  448. return Err(anyhow!(
  449. "Cannot start project name with numbers, name your project something else!"
  450. ));
  451. }
  452. fs::create_dir(name.clone())?;
  453. std::env::set_current_dir(&name)?;
  454. fs::create_dir("app")?;
  455. let mut cfg = Config::default();
  456. cfg.scripts.insert(
  457. "test".to_owned(),
  458. if javascript {
  459. "yarn run mocha -t 1000000 tests/"
  460. } else {
  461. "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
  462. }
  463. .to_owned(),
  464. );
  465. let mut localnet = BTreeMap::new();
  466. localnet.insert(
  467. name.to_snake_case(),
  468. ProgramDeployment {
  469. address: template::default_program_id(),
  470. path: None,
  471. idl: None,
  472. },
  473. );
  474. cfg.programs.insert(Cluster::Localnet, localnet);
  475. let toml = cfg.to_string();
  476. let mut file = File::create("Anchor.toml")?;
  477. file.write_all(toml.as_bytes())?;
  478. // Build virtual manifest.
  479. let mut virt_manifest = File::create("Cargo.toml")?;
  480. virt_manifest.write_all(template::virtual_manifest().as_bytes())?;
  481. // Initialize .gitignore file
  482. let mut virt_manifest = File::create(".gitignore")?;
  483. virt_manifest.write_all(template::git_ignore().as_bytes())?;
  484. // Build the program.
  485. fs::create_dir("programs")?;
  486. new_program(&name)?;
  487. // Build the test suite.
  488. fs::create_dir("tests")?;
  489. // Build the migrations directory.
  490. fs::create_dir("migrations")?;
  491. if javascript {
  492. // Build javascript config
  493. let mut package_json = File::create("package.json")?;
  494. package_json.write_all(template::package_json().as_bytes())?;
  495. let mut mocha = File::create(&format!("tests/{}.js", name))?;
  496. mocha.write_all(template::mocha(&name).as_bytes())?;
  497. let mut deploy = File::create("migrations/deploy.js")?;
  498. deploy.write_all(template::deploy_script().as_bytes())?;
  499. } else {
  500. // Build typescript config
  501. let mut ts_config = File::create("tsconfig.json")?;
  502. ts_config.write_all(template::ts_config().as_bytes())?;
  503. let mut ts_package_json = File::create("package.json")?;
  504. ts_package_json.write_all(template::ts_package_json().as_bytes())?;
  505. let mut deploy = File::create("migrations/deploy.ts")?;
  506. deploy.write_all(template::ts_deploy_script().as_bytes())?;
  507. let mut mocha = File::create(&format!("tests/{}.ts", name))?;
  508. mocha.write_all(template::ts_mocha(&name).as_bytes())?;
  509. }
  510. // Install node modules.
  511. let yarn_result = std::process::Command::new("yarn")
  512. .stdout(Stdio::inherit())
  513. .stderr(Stdio::inherit())
  514. .output()
  515. .map_err(|e| anyhow::format_err!("yarn install failed: {}", e.to_string()))?;
  516. if !yarn_result.status.success() {
  517. println!("Failed yarn install will attempt to npm install");
  518. std::process::Command::new("npm")
  519. .stdout(Stdio::inherit())
  520. .stderr(Stdio::inherit())
  521. .output()
  522. .map_err(|e| anyhow::format_err!("npm install failed: {}", e.to_string()))?;
  523. println!("Failed to install node dependencies")
  524. }
  525. println!("{} initialized", name);
  526. Ok(())
  527. }
  528. // Creates a new program crate in the `programs/<name>` directory.
  529. fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
  530. with_workspace(cfg_override, |cfg| {
  531. match cfg.path().parent() {
  532. None => {
  533. println!("Unable to make new program");
  534. }
  535. Some(parent) => {
  536. std::env::set_current_dir(&parent)?;
  537. new_program(&name)?;
  538. println!("Created new program.");
  539. }
  540. };
  541. Ok(())
  542. })
  543. }
  544. // Creates a new program crate in the current directory with `name`.
  545. fn new_program(name: &str) -> Result<()> {
  546. fs::create_dir(&format!("programs/{}", name))?;
  547. fs::create_dir(&format!("programs/{}/src/", name))?;
  548. let mut cargo_toml = File::create(&format!("programs/{}/Cargo.toml", name))?;
  549. cargo_toml.write_all(template::cargo_toml(name).as_bytes())?;
  550. let mut xargo_toml = File::create(&format!("programs/{}/Xargo.toml", name))?;
  551. xargo_toml.write_all(template::xargo_toml().as_bytes())?;
  552. let mut lib_rs = File::create(&format!("programs/{}/src/lib.rs", name))?;
  553. lib_rs.write_all(template::lib_rs(name).as_bytes())?;
  554. Ok(())
  555. }
  556. pub fn expand(
  557. cfg_override: &ConfigOverride,
  558. program_name: Option<String>,
  559. cargo_args: &[String],
  560. ) -> Result<()> {
  561. // Change to the workspace member directory, if needed.
  562. if let Some(program_name) = program_name.as_ref() {
  563. cd_member(cfg_override, program_name)?;
  564. }
  565. let workspace_cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  566. let cfg_parent = workspace_cfg.path().parent().expect("Invalid Anchor.toml");
  567. let cargo = Manifest::discover()?;
  568. let expansions_path = cfg_parent.join(".anchor/expanded-macros");
  569. fs::create_dir_all(&expansions_path)?;
  570. match cargo {
  571. // No Cargo.toml found, expand entire workspace
  572. None => expand_all(&workspace_cfg, expansions_path, cargo_args),
  573. // Cargo.toml is at root of workspace, expand entire workspace
  574. Some(cargo) if cargo.path().parent() == workspace_cfg.path().parent() => {
  575. expand_all(&workspace_cfg, expansions_path, cargo_args)
  576. }
  577. // Reaching this arm means Cargo.toml belongs to a single package. Expand it.
  578. Some(cargo) => expand_program(
  579. // If we found Cargo.toml, it must be in a directory so unwrap is safe
  580. cargo.path().parent().unwrap().to_path_buf(),
  581. expansions_path,
  582. cargo_args,
  583. ),
  584. }
  585. }
  586. fn expand_all(
  587. workspace_cfg: &WithPath<Config>,
  588. expansions_path: PathBuf,
  589. cargo_args: &[String],
  590. ) -> Result<()> {
  591. let cur_dir = std::env::current_dir()?;
  592. for p in workspace_cfg.get_program_list()? {
  593. expand_program(p, expansions_path.clone(), cargo_args)?;
  594. }
  595. std::env::set_current_dir(cur_dir)?;
  596. Ok(())
  597. }
  598. fn expand_program(
  599. program_path: PathBuf,
  600. expansions_path: PathBuf,
  601. cargo_args: &[String],
  602. ) -> Result<()> {
  603. let cargo = Manifest::from_path(program_path.join("Cargo.toml"))
  604. .map_err(|_| anyhow!("Could not find Cargo.toml for program"))?;
  605. let target_dir_arg = {
  606. let mut target_dir_arg = OsString::from("--target-dir=");
  607. target_dir_arg.push(expansions_path.join("expand-target"));
  608. target_dir_arg
  609. };
  610. let package_name = &cargo
  611. .package
  612. .as_ref()
  613. .ok_or_else(|| anyhow!("Cargo config is missing a package"))?
  614. .name;
  615. let program_expansions_path = expansions_path.join(package_name);
  616. fs::create_dir_all(&program_expansions_path)?;
  617. let exit = std::process::Command::new("cargo")
  618. .arg("expand")
  619. .arg(target_dir_arg)
  620. .arg(&format!("--package={}", package_name))
  621. .args(cargo_args)
  622. .stderr(Stdio::inherit())
  623. .output()
  624. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  625. if !exit.status.success() {
  626. eprintln!("'anchor expand' failed. Perhaps you have not installed 'cargo-expand'? https://github.com/dtolnay/cargo-expand#installation");
  627. std::process::exit(exit.status.code().unwrap_or(1));
  628. }
  629. let version = cargo.version();
  630. let time = chrono::Utc::now().to_string().replace(' ', "_");
  631. let file_path =
  632. program_expansions_path.join(format!("{}-{}-{}.rs", package_name, version, time));
  633. fs::write(&file_path, &exit.stdout).map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  634. println!(
  635. "Expanded {} into file {}\n",
  636. package_name,
  637. file_path.to_string_lossy()
  638. );
  639. Ok(())
  640. }
  641. #[allow(clippy::too_many_arguments)]
  642. pub fn build(
  643. cfg_override: &ConfigOverride,
  644. idl: Option<String>,
  645. idl_ts: Option<String>,
  646. verifiable: bool,
  647. program_name: Option<String>,
  648. solana_version: Option<String>,
  649. docker_image: Option<String>,
  650. bootstrap: BootstrapMode,
  651. stdout: Option<File>, // Used for the package registry server.
  652. stderr: Option<File>, // Used for the package registry server.
  653. cargo_args: Vec<String>,
  654. ) -> Result<()> {
  655. // Change to the workspace member directory, if needed.
  656. if let Some(program_name) = program_name.as_ref() {
  657. cd_member(cfg_override, program_name)?;
  658. }
  659. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  660. let build_config = BuildConfig {
  661. verifiable,
  662. solana_version: solana_version.or_else(|| cfg.solana_version.clone()),
  663. docker_image: docker_image.unwrap_or_else(|| cfg.docker()),
  664. bootstrap,
  665. };
  666. let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
  667. let cargo = Manifest::discover()?;
  668. let idl_out = match idl {
  669. Some(idl) => Some(PathBuf::from(idl)),
  670. None => Some(cfg_parent.join("target/idl")),
  671. };
  672. fs::create_dir_all(idl_out.as_ref().unwrap())?;
  673. let idl_ts_out = match idl_ts {
  674. Some(idl_ts) => Some(PathBuf::from(idl_ts)),
  675. None => Some(cfg_parent.join("target/types")),
  676. };
  677. fs::create_dir_all(idl_ts_out.as_ref().unwrap())?;
  678. if !&cfg.workspace.types.is_empty() {
  679. fs::create_dir_all(cfg_parent.join(&cfg.workspace.types))?;
  680. };
  681. match cargo {
  682. // No Cargo.toml so build the entire workspace.
  683. None => build_all(
  684. &cfg,
  685. cfg.path(),
  686. idl_out,
  687. idl_ts_out,
  688. &build_config,
  689. stdout,
  690. stderr,
  691. cargo_args,
  692. )?,
  693. // If the Cargo.toml is at the root, build the entire workspace.
  694. Some(cargo) if cargo.path().parent() == cfg.path().parent() => build_all(
  695. &cfg,
  696. cfg.path(),
  697. idl_out,
  698. idl_ts_out,
  699. &build_config,
  700. stdout,
  701. stderr,
  702. cargo_args,
  703. )?,
  704. // Cargo.toml represents a single package. Build it.
  705. Some(cargo) => build_cwd(
  706. &cfg,
  707. cargo.path().to_path_buf(),
  708. idl_out,
  709. idl_ts_out,
  710. &build_config,
  711. stdout,
  712. stderr,
  713. cargo_args,
  714. )?,
  715. }
  716. set_workspace_dir_or_exit();
  717. Ok(())
  718. }
  719. #[allow(clippy::too_many_arguments)]
  720. fn build_all(
  721. cfg: &WithPath<Config>,
  722. cfg_path: &Path,
  723. idl_out: Option<PathBuf>,
  724. idl_ts_out: Option<PathBuf>,
  725. build_config: &BuildConfig,
  726. stdout: Option<File>, // Used for the package registry server.
  727. stderr: Option<File>, // Used for the package registry server.
  728. cargo_args: Vec<String>,
  729. ) -> Result<()> {
  730. let cur_dir = std::env::current_dir()?;
  731. let r = match cfg_path.parent() {
  732. None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
  733. Some(_parent) => {
  734. for p in cfg.get_program_list()? {
  735. build_cwd(
  736. cfg,
  737. p.join("Cargo.toml"),
  738. idl_out.clone(),
  739. idl_ts_out.clone(),
  740. build_config,
  741. stdout.as_ref().map(|f| f.try_clone()).transpose()?,
  742. stderr.as_ref().map(|f| f.try_clone()).transpose()?,
  743. cargo_args.clone(),
  744. )?;
  745. }
  746. Ok(())
  747. }
  748. };
  749. std::env::set_current_dir(cur_dir)?;
  750. r
  751. }
  752. // Runs the build command outside of a workspace.
  753. #[allow(clippy::too_many_arguments)]
  754. fn build_cwd(
  755. cfg: &WithPath<Config>,
  756. cargo_toml: PathBuf,
  757. idl_out: Option<PathBuf>,
  758. idl_ts_out: Option<PathBuf>,
  759. build_config: &BuildConfig,
  760. stdout: Option<File>,
  761. stderr: Option<File>,
  762. cargo_args: Vec<String>,
  763. ) -> Result<()> {
  764. match cargo_toml.parent() {
  765. None => return Err(anyhow!("Unable to find parent")),
  766. Some(p) => std::env::set_current_dir(&p)?,
  767. };
  768. match build_config.verifiable {
  769. false => _build_cwd(cfg, idl_out, idl_ts_out, cargo_args),
  770. true => build_cwd_verifiable(cfg, cargo_toml, build_config, stdout, stderr, cargo_args),
  771. }
  772. }
  773. // Builds an anchor program in a docker image and copies the build artifacts
  774. // into the `target/` directory.
  775. fn build_cwd_verifiable(
  776. cfg: &WithPath<Config>,
  777. cargo_toml: PathBuf,
  778. build_config: &BuildConfig,
  779. stdout: Option<File>,
  780. stderr: Option<File>,
  781. cargo_args: Vec<String>,
  782. ) -> Result<()> {
  783. // Create output dirs.
  784. let workspace_dir = cfg.path().parent().unwrap().canonicalize()?;
  785. fs::create_dir_all(workspace_dir.join("target/verifiable"))?;
  786. fs::create_dir_all(workspace_dir.join("target/idl"))?;
  787. fs::create_dir_all(workspace_dir.join("target/types"))?;
  788. if !&cfg.workspace.types.is_empty() {
  789. fs::create_dir_all(workspace_dir.join(&cfg.workspace.types))?;
  790. }
  791. let container_name = "anchor-program";
  792. // Build the binary in docker.
  793. let result = docker_build(
  794. cfg,
  795. container_name,
  796. cargo_toml,
  797. build_config,
  798. stdout,
  799. stderr,
  800. cargo_args,
  801. );
  802. match &result {
  803. Err(e) => {
  804. eprintln!("Error during Docker build: {:?}", e);
  805. }
  806. Ok(_) => {
  807. // Build the idl.
  808. println!("Extracting the IDL");
  809. if let Ok(Some(idl)) = extract_idl(cfg, "src/lib.rs") {
  810. // Write out the JSON file.
  811. println!("Writing the IDL file");
  812. let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.name));
  813. write_idl(&idl, OutFile::File(out_file))?;
  814. // Write out the TypeScript type.
  815. println!("Writing the .ts file");
  816. let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.name));
  817. fs::write(&ts_file, template::idl_ts(&idl)?)?;
  818. // Copy out the TypeScript type.
  819. if !&cfg.workspace.types.is_empty() {
  820. fs::copy(
  821. ts_file,
  822. workspace_dir
  823. .join(&cfg.workspace.types)
  824. .join(idl.name)
  825. .with_extension("ts"),
  826. )?;
  827. }
  828. }
  829. println!("Build success");
  830. }
  831. }
  832. result
  833. }
  834. fn docker_build(
  835. cfg: &WithPath<Config>,
  836. container_name: &str,
  837. cargo_toml: PathBuf,
  838. build_config: &BuildConfig,
  839. stdout: Option<File>,
  840. stderr: Option<File>,
  841. cargo_args: Vec<String>,
  842. ) -> Result<()> {
  843. let binary_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
  844. // Docker vars.
  845. let workdir = Path::new("/workdir");
  846. let volume_mount = format!(
  847. "{}:{}",
  848. cfg.path().parent().unwrap().canonicalize()?.display(),
  849. workdir.to_str().unwrap(),
  850. );
  851. println!("Using image {:?}", build_config.docker_image);
  852. // Start the docker image running detached in the background.
  853. let target_dir = workdir.join("docker-target");
  854. println!("Run docker image");
  855. let exit = std::process::Command::new("docker")
  856. .args(&[
  857. "run",
  858. "-it",
  859. "-d",
  860. "--name",
  861. container_name,
  862. "--env",
  863. &format!(
  864. "CARGO_TARGET_DIR={}",
  865. target_dir.as_path().to_str().unwrap()
  866. ),
  867. "-v",
  868. &volume_mount,
  869. "-w",
  870. workdir.to_str().unwrap(),
  871. &build_config.docker_image,
  872. "bash",
  873. ])
  874. .stdout(Stdio::inherit())
  875. .stderr(Stdio::inherit())
  876. .output()
  877. .map_err(|e| anyhow::format_err!("Docker build failed: {}", e.to_string()))?;
  878. if !exit.status.success() {
  879. return Err(anyhow!("Failed to build program"));
  880. }
  881. let result = docker_prep(container_name, build_config).and_then(|_| {
  882. let cfg_parent = cfg.path().parent().unwrap();
  883. docker_build_bpf(
  884. container_name,
  885. cargo_toml.as_path(),
  886. cfg_parent,
  887. target_dir.as_path(),
  888. binary_name,
  889. stdout,
  890. stderr,
  891. cargo_args,
  892. )
  893. });
  894. // Cleanup regardless of errors
  895. docker_cleanup(container_name, target_dir.as_path())?;
  896. // Done.
  897. result
  898. }
  899. fn docker_prep(container_name: &str, build_config: &BuildConfig) -> Result<()> {
  900. // Set the solana version in the container, if given. Otherwise use the
  901. // default.
  902. match build_config.bootstrap {
  903. BootstrapMode::Debian => {
  904. // Install build requirements
  905. docker_exec(container_name, &["apt", "update"])?;
  906. docker_exec(
  907. container_name,
  908. &["apt", "install", "-y", "curl", "build-essential"],
  909. )?;
  910. // Install Rust
  911. docker_exec(
  912. container_name,
  913. &["curl", "https://sh.rustup.rs", "-sfo", "rustup.sh"],
  914. )?;
  915. docker_exec(container_name, &["sh", "rustup.sh", "-y"])?;
  916. docker_exec(container_name, &["rm", "-f", "rustup.sh"])?;
  917. }
  918. BootstrapMode::None => {}
  919. }
  920. if let Some(solana_version) = &build_config.solana_version {
  921. println!("Using solana version: {}", solana_version);
  922. // Install Solana CLI
  923. docker_exec(
  924. container_name,
  925. &[
  926. "curl",
  927. "-sSfL",
  928. &format!("https://release.solana.com/v{0}/install", solana_version,),
  929. "-o",
  930. "solana_installer.sh",
  931. ],
  932. )?;
  933. docker_exec(container_name, &["sh", "solana_installer.sh"])?;
  934. docker_exec(container_name, &["rm", "-f", "solana_installer.sh"])?;
  935. }
  936. Ok(())
  937. }
  938. #[allow(clippy::too_many_arguments)]
  939. fn docker_build_bpf(
  940. container_name: &str,
  941. cargo_toml: &Path,
  942. cfg_parent: &Path,
  943. target_dir: &Path,
  944. binary_name: String,
  945. stdout: Option<File>,
  946. stderr: Option<File>,
  947. cargo_args: Vec<String>,
  948. ) -> Result<()> {
  949. let manifest_path =
  950. pathdiff::diff_paths(cargo_toml.canonicalize()?, cfg_parent.canonicalize()?)
  951. .ok_or_else(|| anyhow!("Unable to diff paths"))?;
  952. println!(
  953. "Building {} manifest: {:?}",
  954. binary_name,
  955. manifest_path.display()
  956. );
  957. // Execute the build.
  958. let exit = std::process::Command::new("docker")
  959. .args(&[
  960. "exec",
  961. "--env",
  962. "PATH=/root/.local/share/solana/install/active_release/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  963. container_name,
  964. "cargo",
  965. "build-bpf",
  966. "--manifest-path",
  967. &manifest_path.display().to_string(),
  968. ])
  969. .args(cargo_args)
  970. .stdout(match stdout {
  971. None => Stdio::inherit(),
  972. Some(f) => f.into(),
  973. })
  974. .stderr(match stderr {
  975. None => Stdio::inherit(),
  976. Some(f) => f.into(),
  977. })
  978. .output()
  979. .map_err(|e| anyhow::format_err!("Docker build failed: {}", e.to_string()))?;
  980. if !exit.status.success() {
  981. return Err(anyhow!("Failed to build program"));
  982. }
  983. // Copy the binary out of the docker image.
  984. println!("Copying out the build artifacts");
  985. let out_file = cfg_parent
  986. .canonicalize()?
  987. .join(format!("target/verifiable/{}.so", binary_name))
  988. .display()
  989. .to_string();
  990. // This requires the target directory of any built program to be located at
  991. // the root of the workspace.
  992. let mut bin_path = target_dir.join("deploy");
  993. bin_path.push(format!("{}.so", binary_name));
  994. let bin_artifact = format!(
  995. "{}:{}",
  996. container_name,
  997. bin_path.as_path().to_str().unwrap()
  998. );
  999. let exit = std::process::Command::new("docker")
  1000. .args(&["cp", &bin_artifact, &out_file])
  1001. .stdout(Stdio::inherit())
  1002. .stderr(Stdio::inherit())
  1003. .output()
  1004. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  1005. if !exit.status.success() {
  1006. Err(anyhow!(
  1007. "Failed to copy binary out of docker. Is the target directory set correctly?"
  1008. ))
  1009. } else {
  1010. Ok(())
  1011. }
  1012. }
  1013. fn docker_cleanup(container_name: &str, target_dir: &Path) -> Result<()> {
  1014. // Wipe the generated docker-target dir.
  1015. println!("Cleaning up the docker target directory");
  1016. docker_exec(container_name, &["rm", "-rf", target_dir.to_str().unwrap()])?;
  1017. // Remove the docker image.
  1018. println!("Removing the docker container");
  1019. let exit = std::process::Command::new("docker")
  1020. .args(&["rm", "-f", container_name])
  1021. .stdout(Stdio::inherit())
  1022. .stderr(Stdio::inherit())
  1023. .output()
  1024. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  1025. if !exit.status.success() {
  1026. println!("Unable to remove the docker container");
  1027. std::process::exit(exit.status.code().unwrap_or(1));
  1028. }
  1029. Ok(())
  1030. }
  1031. fn docker_exec(container_name: &str, args: &[&str]) -> Result<()> {
  1032. let exit = std::process::Command::new("docker")
  1033. .args([&["exec", container_name], args].concat())
  1034. .stdout(Stdio::inherit())
  1035. .stderr(Stdio::inherit())
  1036. .output()
  1037. .map_err(|e| anyhow!("Failed to run command \"{:?}\": {:?}", args, e))?;
  1038. if !exit.status.success() {
  1039. Err(anyhow!("Failed to run command: {:?}", args))
  1040. } else {
  1041. Ok(())
  1042. }
  1043. }
  1044. fn _build_cwd(
  1045. cfg: &WithPath<Config>,
  1046. idl_out: Option<PathBuf>,
  1047. idl_ts_out: Option<PathBuf>,
  1048. cargo_args: Vec<String>,
  1049. ) -> Result<()> {
  1050. let exit = std::process::Command::new("cargo")
  1051. .arg("build-bpf")
  1052. .args(cargo_args)
  1053. .stdout(Stdio::inherit())
  1054. .stderr(Stdio::inherit())
  1055. .output()
  1056. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  1057. if !exit.status.success() {
  1058. std::process::exit(exit.status.code().unwrap_or(1));
  1059. }
  1060. // Always assume idl is located at src/lib.rs.
  1061. if let Some(idl) = extract_idl(cfg, "src/lib.rs")? {
  1062. // JSON out path.
  1063. let out = match idl_out {
  1064. None => PathBuf::from(".").join(&idl.name).with_extension("json"),
  1065. Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")),
  1066. };
  1067. // TS out path.
  1068. let ts_out = match idl_ts_out {
  1069. None => PathBuf::from(".").join(&idl.name).with_extension("ts"),
  1070. Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("ts")),
  1071. };
  1072. // Write out the JSON file.
  1073. write_idl(&idl, OutFile::File(out))?;
  1074. // Write out the TypeScript type.
  1075. fs::write(&ts_out, template::idl_ts(&idl)?)?;
  1076. // Copy out the TypeScript type.
  1077. let cfg_parent = cfg.path().parent().expect("Invalid Anchor.toml");
  1078. if !&cfg.workspace.types.is_empty() {
  1079. fs::copy(
  1080. &ts_out,
  1081. cfg_parent
  1082. .join(&cfg.workspace.types)
  1083. .join(&idl.name)
  1084. .with_extension("ts"),
  1085. )?;
  1086. }
  1087. }
  1088. Ok(())
  1089. }
  1090. fn verify(
  1091. cfg_override: &ConfigOverride,
  1092. program_id: Pubkey,
  1093. program_name: Option<String>,
  1094. solana_version: Option<String>,
  1095. docker_image: Option<String>,
  1096. bootstrap: BootstrapMode,
  1097. cargo_args: Vec<String>,
  1098. ) -> Result<()> {
  1099. // Change to the workspace member directory, if needed.
  1100. if let Some(program_name) = program_name.as_ref() {
  1101. cd_member(cfg_override, program_name)?;
  1102. }
  1103. // Proceed with the command.
  1104. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  1105. let cargo = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
  1106. // Build the program we want to verify.
  1107. let cur_dir = std::env::current_dir()?;
  1108. build(
  1109. cfg_override,
  1110. None, // idl
  1111. None, // idl ts
  1112. true, // verifiable
  1113. None, // program name
  1114. solana_version.or_else(|| cfg.solana_version.clone()), // solana version
  1115. docker_image, // docker image
  1116. bootstrap, // bootstrap docker image
  1117. None, // stdout
  1118. None, // stderr
  1119. cargo_args,
  1120. )?;
  1121. std::env::set_current_dir(&cur_dir)?;
  1122. // Verify binary.
  1123. let binary_name = cargo.lib_name()?;
  1124. let bin_path = cfg
  1125. .path()
  1126. .parent()
  1127. .ok_or_else(|| anyhow!("Unable to find workspace root"))?
  1128. .join("target/verifiable/")
  1129. .join(format!("{}.so", binary_name));
  1130. let url = cluster_url(&cfg);
  1131. let bin_ver = verify_bin(program_id, &bin_path, &url)?;
  1132. if !bin_ver.is_verified {
  1133. println!("Error: Binaries don't match");
  1134. std::process::exit(1);
  1135. }
  1136. // Verify IDL (only if it's not a buffer account).
  1137. if let Some(local_idl) = extract_idl(&cfg, "src/lib.rs")? {
  1138. if bin_ver.state != BinVerificationState::Buffer {
  1139. let deployed_idl = fetch_idl(cfg_override, program_id)?;
  1140. if local_idl != deployed_idl {
  1141. println!("Error: IDLs don't match");
  1142. std::process::exit(1);
  1143. }
  1144. }
  1145. }
  1146. println!("{} is verified.", program_id);
  1147. Ok(())
  1148. }
  1149. fn cd_member(cfg_override: &ConfigOverride, program_name: &str) -> Result<()> {
  1150. // Change directories to the given `program_name`, if given.
  1151. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  1152. for program in cfg.read_all_programs()? {
  1153. let cargo_toml = program.path.join("Cargo.toml");
  1154. if !cargo_toml.exists() {
  1155. return Err(anyhow!(
  1156. "Did not find Cargo.toml at the path: {}",
  1157. program.path.display()
  1158. ));
  1159. }
  1160. let p_lib_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
  1161. if program_name == p_lib_name {
  1162. std::env::set_current_dir(&program.path)?;
  1163. return Ok(());
  1164. }
  1165. }
  1166. return Err(anyhow!("{} is not part of the workspace", program_name,));
  1167. }
  1168. pub fn verify_bin(program_id: Pubkey, bin_path: &Path, cluster: &str) -> Result<BinVerification> {
  1169. let client = RpcClient::new(cluster.to_string());
  1170. // Get the deployed build artifacts.
  1171. let (deployed_bin, state) = {
  1172. let account = client
  1173. .get_account_with_commitment(&program_id, CommitmentConfig::default())?
  1174. .value
  1175. .map_or(Err(anyhow!("Account not found")), Ok)?;
  1176. if account.owner == bpf_loader::id() || account.owner == bpf_loader_deprecated::id() {
  1177. let bin = account.data.to_vec();
  1178. let state = BinVerificationState::ProgramData {
  1179. slot: 0, // Need to look through the transaction history.
  1180. upgrade_authority_address: None,
  1181. };
  1182. (bin, state)
  1183. } else if account.owner == bpf_loader_upgradeable::id() {
  1184. match account.state()? {
  1185. UpgradeableLoaderState::Program {
  1186. programdata_address,
  1187. } => {
  1188. let account = client
  1189. .get_account_with_commitment(
  1190. &programdata_address,
  1191. CommitmentConfig::default(),
  1192. )?
  1193. .value
  1194. .map_or(Err(anyhow!("Account not found")), Ok)?;
  1195. let bin = account.data
  1196. [UpgradeableLoaderState::programdata_data_offset().unwrap_or(0)..]
  1197. .to_vec();
  1198. if let UpgradeableLoaderState::ProgramData {
  1199. slot,
  1200. upgrade_authority_address,
  1201. } = account.state()?
  1202. {
  1203. let state = BinVerificationState::ProgramData {
  1204. slot,
  1205. upgrade_authority_address,
  1206. };
  1207. (bin, state)
  1208. } else {
  1209. return Err(anyhow!("Expected program data"));
  1210. }
  1211. }
  1212. UpgradeableLoaderState::Buffer { .. } => {
  1213. let offset = UpgradeableLoaderState::buffer_data_offset().unwrap_or(0);
  1214. (
  1215. account.data[offset..].to_vec(),
  1216. BinVerificationState::Buffer,
  1217. )
  1218. }
  1219. _ => {
  1220. return Err(anyhow!(
  1221. "Invalid program id, not a buffer or program account"
  1222. ))
  1223. }
  1224. }
  1225. } else {
  1226. return Err(anyhow!(
  1227. "Invalid program id, not owned by any loader program"
  1228. ));
  1229. }
  1230. };
  1231. let mut local_bin = {
  1232. let mut f = File::open(bin_path)?;
  1233. let mut contents = vec![];
  1234. f.read_to_end(&mut contents)?;
  1235. contents
  1236. };
  1237. // The deployed program probably has zero bytes appended. The default is
  1238. // 2x the binary size in case of an upgrade.
  1239. if local_bin.len() < deployed_bin.len() {
  1240. local_bin.append(&mut vec![0; deployed_bin.len() - local_bin.len()]);
  1241. }
  1242. // Finally, check the bytes.
  1243. let is_verified = local_bin == deployed_bin;
  1244. Ok(BinVerification { state, is_verified })
  1245. }
  1246. #[derive(PartialEq)]
  1247. pub struct BinVerification {
  1248. pub state: BinVerificationState,
  1249. pub is_verified: bool,
  1250. }
  1251. #[derive(PartialEq)]
  1252. pub enum BinVerificationState {
  1253. Buffer,
  1254. ProgramData {
  1255. slot: u64,
  1256. upgrade_authority_address: Option<Pubkey>,
  1257. },
  1258. }
  1259. // Fetches an IDL for the given program_id.
  1260. fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<Idl> {
  1261. let cfg = Config::discover(cfg_override)?.expect("Inside a workspace");
  1262. let url = cluster_url(&cfg);
  1263. let client = RpcClient::new(url);
  1264. let mut account = client
  1265. .get_account_with_commitment(&idl_addr, CommitmentConfig::processed())?
  1266. .value
  1267. .map_or(Err(anyhow!("Account not found")), Ok)?;
  1268. if account.executable {
  1269. let idl_addr = IdlAccount::address(&idl_addr);
  1270. account = client
  1271. .get_account_with_commitment(&idl_addr, CommitmentConfig::processed())?
  1272. .value
  1273. .map_or(Err(anyhow!("Account not found")), Ok)?;
  1274. }
  1275. // Cut off account discriminator.
  1276. let mut d: &[u8] = &account.data[8..];
  1277. let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?;
  1278. let mut z = ZlibDecoder::new(&idl_account.data[..]);
  1279. let mut s = Vec::new();
  1280. z.read_to_end(&mut s)?;
  1281. serde_json::from_slice(&s[..]).map_err(Into::into)
  1282. }
  1283. fn extract_idl(cfg: &WithPath<Config>, file: &str) -> Result<Option<Idl>> {
  1284. let file = shellexpand::tilde(file);
  1285. let manifest_from_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap());
  1286. let cargo = Manifest::discover_from_path(manifest_from_path)?
  1287. .ok_or_else(|| anyhow!("Cargo.toml not found"))?;
  1288. anchor_syn::idl::file::parse(&*file, cargo.version(), cfg.features.seeds)
  1289. }
  1290. fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
  1291. match subcmd {
  1292. IdlCommand::Init {
  1293. program_id,
  1294. filepath,
  1295. } => idl_init(cfg_override, program_id, filepath),
  1296. IdlCommand::WriteBuffer {
  1297. program_id,
  1298. filepath,
  1299. } => idl_write_buffer(cfg_override, program_id, filepath).map(|_| ()),
  1300. IdlCommand::SetBuffer { program_id, buffer } => {
  1301. idl_set_buffer(cfg_override, program_id, buffer)
  1302. }
  1303. IdlCommand::Upgrade {
  1304. program_id,
  1305. filepath,
  1306. } => idl_upgrade(cfg_override, program_id, filepath),
  1307. IdlCommand::SetAuthority {
  1308. program_id,
  1309. address,
  1310. new_authority,
  1311. } => idl_set_authority(cfg_override, program_id, address, new_authority),
  1312. IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id),
  1313. IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
  1314. IdlCommand::Parse { file, out, out_ts } => idl_parse(cfg_override, file, out, out_ts),
  1315. IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
  1316. }
  1317. }
  1318. fn idl_init(cfg_override: &ConfigOverride, program_id: Pubkey, idl_filepath: String) -> Result<()> {
  1319. with_workspace(cfg_override, |cfg| {
  1320. let keypair = cfg.provider.wallet.to_string();
  1321. let bytes = fs::read(idl_filepath)?;
  1322. let idl: Idl = serde_json::from_reader(&*bytes)?;
  1323. let idl_address = create_idl_account(cfg, &keypair, &program_id, &idl)?;
  1324. println!("Idl account created: {:?}", idl_address);
  1325. Ok(())
  1326. })
  1327. }
  1328. fn idl_write_buffer(
  1329. cfg_override: &ConfigOverride,
  1330. program_id: Pubkey,
  1331. idl_filepath: String,
  1332. ) -> Result<Pubkey> {
  1333. with_workspace(cfg_override, |cfg| {
  1334. let keypair = cfg.provider.wallet.to_string();
  1335. let bytes = fs::read(idl_filepath)?;
  1336. let idl: Idl = serde_json::from_reader(&*bytes)?;
  1337. let idl_buffer = create_idl_buffer(cfg, &keypair, &program_id, &idl)?;
  1338. idl_write(cfg, &program_id, &idl, idl_buffer)?;
  1339. println!("Idl buffer created: {:?}", idl_buffer);
  1340. Ok(idl_buffer)
  1341. })
  1342. }
  1343. fn idl_set_buffer(cfg_override: &ConfigOverride, program_id: Pubkey, buffer: Pubkey) -> Result<()> {
  1344. with_workspace(cfg_override, |cfg| {
  1345. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  1346. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1347. let url = cluster_url(cfg);
  1348. let client = RpcClient::new(url);
  1349. // Instruction to set the buffer onto the IdlAccount.
  1350. let set_buffer_ix = {
  1351. let accounts = vec![
  1352. AccountMeta::new(buffer, false),
  1353. AccountMeta::new(IdlAccount::address(&program_id), false),
  1354. AccountMeta::new(keypair.pubkey(), true),
  1355. ];
  1356. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  1357. data.append(&mut IdlInstruction::SetBuffer.try_to_vec()?);
  1358. Instruction {
  1359. program_id,
  1360. accounts,
  1361. data,
  1362. }
  1363. };
  1364. // Build the transaction.
  1365. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1366. let tx = Transaction::new_signed_with_payer(
  1367. &[set_buffer_ix],
  1368. Some(&keypair.pubkey()),
  1369. &[&keypair],
  1370. recent_hash,
  1371. );
  1372. // Send the transaction.
  1373. client.send_and_confirm_transaction_with_spinner_and_config(
  1374. &tx,
  1375. CommitmentConfig::confirmed(),
  1376. RpcSendTransactionConfig {
  1377. skip_preflight: true,
  1378. ..RpcSendTransactionConfig::default()
  1379. },
  1380. )?;
  1381. Ok(())
  1382. })
  1383. }
  1384. fn idl_upgrade(
  1385. cfg_override: &ConfigOverride,
  1386. program_id: Pubkey,
  1387. idl_filepath: String,
  1388. ) -> Result<()> {
  1389. let buffer = idl_write_buffer(cfg_override, program_id, idl_filepath)?;
  1390. idl_set_buffer(cfg_override, program_id, buffer)
  1391. }
  1392. fn idl_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
  1393. with_workspace(cfg_override, |cfg| {
  1394. let url = cluster_url(cfg);
  1395. let client = RpcClient::new(url);
  1396. let idl_address = {
  1397. let account = client
  1398. .get_account_with_commitment(&program_id, CommitmentConfig::processed())?
  1399. .value
  1400. .map_or(Err(anyhow!("Account not found")), Ok)?;
  1401. if account.executable {
  1402. IdlAccount::address(&program_id)
  1403. } else {
  1404. program_id
  1405. }
  1406. };
  1407. let account = client.get_account(&idl_address)?;
  1408. let mut data: &[u8] = &account.data;
  1409. let idl_account: IdlAccount = AccountDeserialize::try_deserialize(&mut data)?;
  1410. println!("{:?}", idl_account.authority);
  1411. Ok(())
  1412. })
  1413. }
  1414. fn idl_set_authority(
  1415. cfg_override: &ConfigOverride,
  1416. program_id: Pubkey,
  1417. address: Option<Pubkey>,
  1418. new_authority: Pubkey,
  1419. ) -> Result<()> {
  1420. with_workspace(cfg_override, |cfg| {
  1421. // Misc.
  1422. let idl_address = match address {
  1423. None => IdlAccount::address(&program_id),
  1424. Some(addr) => addr,
  1425. };
  1426. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  1427. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1428. let url = cluster_url(cfg);
  1429. let client = RpcClient::new(url);
  1430. // Instruction data.
  1431. let data =
  1432. serialize_idl_ix(anchor_lang::idl::IdlInstruction::SetAuthority { new_authority })?;
  1433. // Instruction accounts.
  1434. let accounts = vec![
  1435. AccountMeta::new(idl_address, false),
  1436. AccountMeta::new_readonly(keypair.pubkey(), true),
  1437. ];
  1438. // Instruction.
  1439. let ix = Instruction {
  1440. program_id,
  1441. accounts,
  1442. data,
  1443. };
  1444. // Send transaction.
  1445. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1446. let tx = Transaction::new_signed_with_payer(
  1447. &[ix],
  1448. Some(&keypair.pubkey()),
  1449. &[&keypair],
  1450. recent_hash,
  1451. );
  1452. client.send_and_confirm_transaction_with_spinner_and_config(
  1453. &tx,
  1454. CommitmentConfig::confirmed(),
  1455. RpcSendTransactionConfig {
  1456. skip_preflight: true,
  1457. ..RpcSendTransactionConfig::default()
  1458. },
  1459. )?;
  1460. println!("Authority update complete.");
  1461. Ok(())
  1462. })
  1463. }
  1464. fn idl_erase_authority(cfg_override: &ConfigOverride, program_id: Pubkey) -> Result<()> {
  1465. println!("Are you sure you want to erase the IDL authority: [y/n]");
  1466. let stdin = std::io::stdin();
  1467. let mut stdin_lines = stdin.lock().lines();
  1468. let input = stdin_lines.next().unwrap().unwrap();
  1469. if input != "y" {
  1470. println!("Not erasing.");
  1471. return Ok(());
  1472. }
  1473. // Program will treat the zero authority as erased.
  1474. let new_authority = Pubkey::new_from_array([0u8; 32]);
  1475. idl_set_authority(cfg_override, program_id, None, new_authority)?;
  1476. Ok(())
  1477. }
  1478. // Write the idl to the account buffer, chopping up the IDL into pieces
  1479. // and sending multiple transactions in the event the IDL doesn't fit into
  1480. // a single transaction.
  1481. fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey) -> Result<()> {
  1482. // Remove the metadata before deploy.
  1483. let mut idl = idl.clone();
  1484. idl.metadata = None;
  1485. // Misc.
  1486. let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
  1487. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  1488. let url = cluster_url(cfg);
  1489. let client = RpcClient::new(url);
  1490. // Serialize and compress the idl.
  1491. let idl_data = {
  1492. let json_bytes = serde_json::to_vec(&idl)?;
  1493. let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
  1494. e.write_all(&json_bytes)?;
  1495. e.finish()?
  1496. };
  1497. const MAX_WRITE_SIZE: usize = 1000;
  1498. let mut offset = 0;
  1499. while offset < idl_data.len() {
  1500. // Instruction data.
  1501. let data = {
  1502. let start = offset;
  1503. let end = std::cmp::min(offset + MAX_WRITE_SIZE, idl_data.len());
  1504. serialize_idl_ix(anchor_lang::idl::IdlInstruction::Write {
  1505. data: idl_data[start..end].to_vec(),
  1506. })?
  1507. };
  1508. // Instruction accounts.
  1509. let accounts = vec![
  1510. AccountMeta::new(idl_address, false),
  1511. AccountMeta::new_readonly(keypair.pubkey(), true),
  1512. ];
  1513. // Instruction.
  1514. let ix = Instruction {
  1515. program_id: *program_id,
  1516. accounts,
  1517. data,
  1518. };
  1519. // Send transaction.
  1520. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  1521. let tx = Transaction::new_signed_with_payer(
  1522. &[ix],
  1523. Some(&keypair.pubkey()),
  1524. &[&keypair],
  1525. recent_hash,
  1526. );
  1527. client.send_and_confirm_transaction_with_spinner_and_config(
  1528. &tx,
  1529. CommitmentConfig::confirmed(),
  1530. RpcSendTransactionConfig {
  1531. skip_preflight: true,
  1532. ..RpcSendTransactionConfig::default()
  1533. },
  1534. )?;
  1535. offset += MAX_WRITE_SIZE;
  1536. }
  1537. Ok(())
  1538. }
  1539. fn idl_parse(
  1540. cfg_override: &ConfigOverride,
  1541. file: String,
  1542. out: Option<String>,
  1543. out_ts: Option<String>,
  1544. ) -> Result<()> {
  1545. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  1546. let idl = extract_idl(&cfg, &file)?.ok_or_else(|| anyhow!("IDL not parsed"))?;
  1547. let out = match out {
  1548. None => OutFile::Stdout,
  1549. Some(out) => OutFile::File(PathBuf::from(out)),
  1550. };
  1551. write_idl(&idl, out)?;
  1552. // Write out the TypeScript IDL.
  1553. if let Some(out) = out_ts {
  1554. fs::write(out, template::idl_ts(&idl)?)?;
  1555. }
  1556. Ok(())
  1557. }
  1558. fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
  1559. let idl = fetch_idl(cfg_override, address)?;
  1560. let out = match out {
  1561. None => OutFile::Stdout,
  1562. Some(out) => OutFile::File(PathBuf::from(out)),
  1563. };
  1564. write_idl(&idl, out)
  1565. }
  1566. fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
  1567. let idl_json = serde_json::to_string_pretty(idl)?;
  1568. match out {
  1569. OutFile::Stdout => println!("{}", idl_json),
  1570. OutFile::File(out) => fs::write(out, idl_json)?,
  1571. };
  1572. Ok(())
  1573. }
  1574. enum OutFile {
  1575. Stdout,
  1576. File(PathBuf),
  1577. }
  1578. // Builds, deploys, and tests all workspace programs in a single command.
  1579. fn test(
  1580. cfg_override: &ConfigOverride,
  1581. skip_deploy: bool,
  1582. skip_local_validator: bool,
  1583. skip_build: bool,
  1584. detach: bool,
  1585. extra_args: Vec<String>,
  1586. cargo_args: Vec<String>,
  1587. ) -> Result<()> {
  1588. with_workspace(cfg_override, |cfg| {
  1589. // Build if needed.
  1590. if !skip_build {
  1591. build(
  1592. cfg_override,
  1593. None,
  1594. None,
  1595. false,
  1596. None,
  1597. None,
  1598. None,
  1599. BootstrapMode::None,
  1600. None,
  1601. None,
  1602. cargo_args,
  1603. )?;
  1604. }
  1605. // Run the deploy against the cluster in two cases:
  1606. //
  1607. // 1. The cluster is not localnet.
  1608. // 2. The cluster is localnet, but we're not booting a local validator.
  1609. //
  1610. // In either case, skip the deploy if the user specifies.
  1611. let is_localnet = cfg.provider.cluster == Cluster::Localnet;
  1612. if (!is_localnet || skip_local_validator) && !skip_deploy {
  1613. deploy(cfg_override, None)?;
  1614. }
  1615. // Start local test validator, if needed.
  1616. let mut validator_handle = None;
  1617. if is_localnet && (!skip_local_validator) {
  1618. let flags = match skip_deploy {
  1619. true => None,
  1620. false => Some(validator_flags(cfg)?),
  1621. };
  1622. validator_handle = Some(start_test_validator(cfg, flags, true)?);
  1623. }
  1624. let url = cluster_url(cfg);
  1625. let node_options = format!(
  1626. "{} {}",
  1627. match std::env::var_os("NODE_OPTIONS") {
  1628. Some(value) => value
  1629. .into_string()
  1630. .map_err(std::env::VarError::NotUnicode)?,
  1631. None => "".to_owned(),
  1632. },
  1633. get_node_dns_option()?,
  1634. );
  1635. // Setup log reader.
  1636. let log_streams = stream_logs(cfg, &url);
  1637. // Run the tests.
  1638. let test_result: Result<_> = {
  1639. let cmd = cfg
  1640. .scripts
  1641. .get("test")
  1642. .expect("Not able to find command for `test`")
  1643. .clone();
  1644. let mut args: Vec<&str> = cmd
  1645. .split(' ')
  1646. .chain(extra_args.iter().map(|arg| arg.as_str()))
  1647. .collect();
  1648. let program = args.remove(0);
  1649. std::process::Command::new(program)
  1650. .args(args)
  1651. .env("ANCHOR_PROVIDER_URL", url)
  1652. .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
  1653. .env("NODE_OPTIONS", node_options)
  1654. .stdout(Stdio::inherit())
  1655. .stderr(Stdio::inherit())
  1656. .output()
  1657. .map_err(anyhow::Error::from)
  1658. .context(cmd)
  1659. };
  1660. // Keep validator running if needed.
  1661. if test_result.is_ok() && detach {
  1662. println!("Local validator still running. Press Ctrl + C quit.");
  1663. std::io::stdin().lock().lines().next().unwrap().unwrap();
  1664. }
  1665. // Check all errors and shut down.
  1666. if let Some(mut child) = validator_handle {
  1667. if let Err(err) = child.kill() {
  1668. println!("Failed to kill subprocess {}: {}", child.id(), err);
  1669. }
  1670. }
  1671. for mut child in log_streams? {
  1672. if let Err(err) = child.kill() {
  1673. println!("Failed to kill subprocess {}: {}", child.id(), err);
  1674. }
  1675. }
  1676. // Must exist *after* shutting down the validator and log streams.
  1677. match test_result {
  1678. Ok(exit) => {
  1679. if !exit.status.success() {
  1680. std::process::exit(exit.status.code().unwrap());
  1681. }
  1682. }
  1683. Err(err) => {
  1684. println!("Failed to run test: {:#}", err)
  1685. }
  1686. }
  1687. Ok(())
  1688. })
  1689. }
  1690. // Returns the solana-test-validator flags. This will embed the workspace
  1691. // programs in the genesis block so we don't have to deploy every time. It also
  1692. // allows control of other solana-test-validator features.
  1693. fn validator_flags(cfg: &WithPath<Config>) -> Result<Vec<String>> {
  1694. let programs = cfg.programs.get(&Cluster::Localnet);
  1695. let mut flags = Vec::new();
  1696. for mut program in cfg.read_all_programs()? {
  1697. let binary_path = program.binary_path().display().to_string();
  1698. // Use the [programs.cluster] override and fallback to the keypair
  1699. // files if no override is given.
  1700. let address = programs
  1701. .and_then(|m| m.get(&program.lib_name))
  1702. .map(|deployment| Ok(deployment.address.to_string()))
  1703. .unwrap_or_else(|| program.pubkey().map(|p| p.to_string()))?;
  1704. flags.push("--bpf-program".to_string());
  1705. flags.push(address.clone());
  1706. flags.push(binary_path);
  1707. if let Some(mut idl) = program.idl.as_mut() {
  1708. // Add program address to the IDL.
  1709. idl.metadata = Some(serde_json::to_value(IdlTestMetadata { address })?);
  1710. // Persist it.
  1711. let idl_out = PathBuf::from("target/idl")
  1712. .join(&idl.name)
  1713. .with_extension("json");
  1714. write_idl(idl, OutFile::File(idl_out))?;
  1715. }
  1716. }
  1717. if let Some(test) = cfg.test.as_ref() {
  1718. if let Some(genesis) = &test.genesis {
  1719. for entry in genesis {
  1720. let program_path = Path::new(&entry.program);
  1721. if !program_path.exists() {
  1722. return Err(anyhow!(
  1723. "Program in genesis configuration does not exist at path: {}",
  1724. program_path.display()
  1725. ));
  1726. }
  1727. flags.push("--bpf-program".to_string());
  1728. flags.push(entry.address.clone());
  1729. flags.push(entry.program.clone());
  1730. }
  1731. }
  1732. if let Some(validator) = &test.validator {
  1733. for (key, value) in serde_json::to_value(validator)?.as_object().unwrap() {
  1734. if key == "ledger" {
  1735. // Ledger flag is a special case as it is passed separately to the rest of
  1736. // these validator flags.
  1737. continue;
  1738. };
  1739. if key == "account" {
  1740. for entry in value.as_array().unwrap() {
  1741. // Push the account flag for each array entry
  1742. flags.push("--account".to_string());
  1743. flags.push(entry["address"].as_str().unwrap().to_string());
  1744. flags.push(entry["filename"].as_str().unwrap().to_string());
  1745. }
  1746. } else if key == "clone" {
  1747. for entry in value.as_array().unwrap() {
  1748. // Push the clone flag for each array entry
  1749. flags.push("--clone".to_string());
  1750. flags.push(entry["address"].as_str().unwrap().to_string());
  1751. }
  1752. } else {
  1753. // Remaining validator flags are non-array types
  1754. flags.push(format!("--{}", key.replace('_', "-")));
  1755. if let serde_json::Value::String(v) = value {
  1756. flags.push(v.to_string());
  1757. } else {
  1758. flags.push(value.to_string());
  1759. }
  1760. }
  1761. }
  1762. }
  1763. }
  1764. Ok(flags)
  1765. }
  1766. fn stream_logs(config: &WithPath<Config>, rpc_url: &str) -> Result<Vec<std::process::Child>> {
  1767. let program_logs_dir = ".anchor/program-logs";
  1768. if Path::new(program_logs_dir).exists() {
  1769. fs::remove_dir_all(program_logs_dir)?;
  1770. }
  1771. fs::create_dir_all(program_logs_dir)?;
  1772. let mut handles = vec![];
  1773. for program in config.read_all_programs()? {
  1774. let mut file = File::open(&format!("target/idl/{}.json", program.lib_name))?;
  1775. let mut contents = vec![];
  1776. file.read_to_end(&mut contents)?;
  1777. let idl: Idl = serde_json::from_slice(&contents)?;
  1778. let metadata = idl
  1779. .metadata
  1780. .ok_or_else(|| anyhow!("Program address not found."))?;
  1781. let metadata: IdlTestMetadata = serde_json::from_value(metadata)?;
  1782. let log_file = File::create(format!(
  1783. "{}/{}.{}.log",
  1784. program_logs_dir, metadata.address, program.lib_name,
  1785. ))?;
  1786. let stdio = std::process::Stdio::from(log_file);
  1787. let child = std::process::Command::new("solana")
  1788. .arg("logs")
  1789. .arg(metadata.address)
  1790. .arg("--url")
  1791. .arg(rpc_url)
  1792. .stdout(stdio)
  1793. .spawn()?;
  1794. handles.push(child);
  1795. }
  1796. if let Some(test) = config.test.as_ref() {
  1797. if let Some(genesis) = &test.genesis {
  1798. for entry in genesis {
  1799. let log_file = File::create(format!("{}/{}.log", program_logs_dir, entry.address))?;
  1800. let stdio = std::process::Stdio::from(log_file);
  1801. let child = std::process::Command::new("solana")
  1802. .arg("logs")
  1803. .arg(entry.address.clone())
  1804. .arg("--url")
  1805. .arg(rpc_url)
  1806. .stdout(stdio)
  1807. .spawn()?;
  1808. handles.push(child);
  1809. }
  1810. }
  1811. }
  1812. Ok(handles)
  1813. }
  1814. #[derive(Debug, Serialize, Deserialize)]
  1815. pub struct IdlTestMetadata {
  1816. address: String,
  1817. }
  1818. fn start_test_validator(
  1819. cfg: &Config,
  1820. flags: Option<Vec<String>>,
  1821. test_log_stdout: bool,
  1822. ) -> Result<Child> {
  1823. //
  1824. let (test_ledger_directory, test_ledger_log_filename) = test_validator_file_paths(cfg);
  1825. // Start a validator for testing.
  1826. let (test_validator_stdout, test_validator_stderr) = match test_log_stdout {
  1827. true => {
  1828. let test_validator_stdout_file = File::create(&test_ledger_log_filename)?;
  1829. let test_validator_sterr_file = test_validator_stdout_file.try_clone()?;
  1830. (
  1831. Stdio::from(test_validator_stdout_file),
  1832. Stdio::from(test_validator_sterr_file),
  1833. )
  1834. }
  1835. false => (Stdio::inherit(), Stdio::inherit()),
  1836. };
  1837. let rpc_url = test_validator_rpc_url(cfg);
  1838. let mut validator_handle = std::process::Command::new("solana-test-validator")
  1839. .arg("--ledger")
  1840. .arg(test_ledger_directory)
  1841. .arg("--mint")
  1842. .arg(cfg.wallet_kp()?.pubkey().to_string())
  1843. .args(flags.unwrap_or_default())
  1844. .stdout(test_validator_stdout)
  1845. .stderr(test_validator_stderr)
  1846. .spawn()
  1847. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  1848. // Wait for the validator to be ready.
  1849. let client = RpcClient::new(rpc_url);
  1850. let mut count = 0;
  1851. let ms_wait = cfg
  1852. .test
  1853. .as_ref()
  1854. .and_then(|test| test.startup_wait)
  1855. .unwrap_or(5_000);
  1856. while count < ms_wait {
  1857. let r = client.get_recent_blockhash();
  1858. if r.is_ok() {
  1859. break;
  1860. }
  1861. std::thread::sleep(std::time::Duration::from_millis(1));
  1862. count += 1;
  1863. }
  1864. if count == ms_wait {
  1865. eprintln!(
  1866. "Unable to get recent blockhash. Test validator does not look started. Check {} for errors. Consider increasing [test.startup_wait] in Anchor.toml.",
  1867. test_ledger_log_filename
  1868. );
  1869. validator_handle.kill()?;
  1870. std::process::exit(1);
  1871. }
  1872. Ok(validator_handle)
  1873. }
  1874. // Return the URL that solana-test-validator should be running on given the
  1875. // configuration
  1876. fn test_validator_rpc_url(cfg: &Config) -> String {
  1877. match &cfg.test.as_ref() {
  1878. Some(Test {
  1879. validator: Some(validator),
  1880. ..
  1881. }) => format!("http://{}:{}", validator.bind_address, validator.rpc_port),
  1882. _ => "http://localhost:8899".to_string(),
  1883. }
  1884. }
  1885. // Setup and return paths to the solana-test-validator ledger directory and log
  1886. // files given the configuration
  1887. fn test_validator_file_paths(cfg: &Config) -> (String, String) {
  1888. let ledger_directory = match &cfg.test.as_ref() {
  1889. Some(Test {
  1890. validator: Some(validator),
  1891. ..
  1892. }) => &validator.ledger,
  1893. _ => ".anchor/test-ledger",
  1894. };
  1895. if !Path::new(&ledger_directory).is_relative() {
  1896. // Prevent absolute paths to avoid someone using / or similar, as the
  1897. // directory gets removed
  1898. eprintln!("Ledger directory {} must be relative", ledger_directory);
  1899. std::process::exit(1);
  1900. }
  1901. if Path::new(&ledger_directory).exists() {
  1902. fs::remove_dir_all(&ledger_directory).unwrap();
  1903. }
  1904. fs::create_dir_all(&ledger_directory).unwrap();
  1905. (
  1906. ledger_directory.to_string(),
  1907. format!("{}/test-ledger-log.txt", ledger_directory),
  1908. )
  1909. }
  1910. fn cluster_url(cfg: &Config) -> String {
  1911. let is_localnet = cfg.provider.cluster == Cluster::Localnet;
  1912. match is_localnet {
  1913. // Cluster is Localnet, assume the intent is to use the configuration
  1914. // for solana-test-validator
  1915. true => test_validator_rpc_url(cfg),
  1916. false => cfg.provider.cluster.url().to_string(),
  1917. }
  1918. }
  1919. fn deploy(cfg_override: &ConfigOverride, program_str: Option<String>) -> Result<()> {
  1920. with_workspace(cfg_override, |cfg| {
  1921. let url = cluster_url(cfg);
  1922. let keypair = cfg.provider.wallet.to_string();
  1923. // Deploy the programs.
  1924. println!("Deploying workspace: {}", url);
  1925. println!("Upgrade authority: {}", keypair);
  1926. for mut program in cfg.read_all_programs()? {
  1927. if let Some(single_prog_str) = &program_str {
  1928. let program_name = program.path.file_name().unwrap().to_str().unwrap();
  1929. if single_prog_str.as_str() != program_name {
  1930. continue;
  1931. }
  1932. }
  1933. let binary_path = program.binary_path().display().to_string();
  1934. println!(
  1935. "Deploying program {:?}...",
  1936. program.path.file_name().unwrap().to_str().unwrap()
  1937. );
  1938. println!("Program path: {}...", binary_path);
  1939. let file = program.keypair_file()?;
  1940. // Send deploy transactions.
  1941. let exit = std::process::Command::new("solana")
  1942. .arg("program")
  1943. .arg("deploy")
  1944. .arg("--url")
  1945. .arg(&url)
  1946. .arg("--keypair")
  1947. .arg(&keypair)
  1948. .arg("--program-id")
  1949. .arg(file.path().display().to_string())
  1950. .arg(&binary_path)
  1951. .stdout(Stdio::inherit())
  1952. .stderr(Stdio::inherit())
  1953. .output()
  1954. .expect("Must deploy");
  1955. if !exit.status.success() {
  1956. println!("There was a problem deploying: {:?}.", exit);
  1957. std::process::exit(exit.status.code().unwrap_or(1));
  1958. }
  1959. let program_pubkey = program.pubkey()?;
  1960. if let Some(mut idl) = program.idl.as_mut() {
  1961. // Add program address to the IDL.
  1962. idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
  1963. address: program_pubkey.to_string(),
  1964. })?);
  1965. // Persist it.
  1966. let idl_out = PathBuf::from("target/idl")
  1967. .join(&idl.name)
  1968. .with_extension("json");
  1969. write_idl(idl, OutFile::File(idl_out))?;
  1970. }
  1971. }
  1972. println!("Deploy success");
  1973. Ok(())
  1974. })
  1975. }
  1976. fn upgrade(
  1977. cfg_override: &ConfigOverride,
  1978. program_id: Pubkey,
  1979. program_filepath: String,
  1980. ) -> Result<()> {
  1981. let path: PathBuf = program_filepath.parse().unwrap();
  1982. let program_filepath = path.canonicalize()?.display().to_string();
  1983. with_workspace(cfg_override, |cfg| {
  1984. let url = cluster_url(cfg);
  1985. let exit = std::process::Command::new("solana")
  1986. .arg("program")
  1987. .arg("deploy")
  1988. .arg("--url")
  1989. .arg(url)
  1990. .arg("--keypair")
  1991. .arg(&cfg.provider.wallet.to_string())
  1992. .arg("--program-id")
  1993. .arg(program_id.to_string())
  1994. .arg(&program_filepath)
  1995. .stdout(Stdio::inherit())
  1996. .stderr(Stdio::inherit())
  1997. .output()
  1998. .expect("Must deploy");
  1999. if !exit.status.success() {
  2000. println!("There was a problem deploying: {:?}.", exit);
  2001. std::process::exit(exit.status.code().unwrap_or(1));
  2002. }
  2003. Ok(())
  2004. })
  2005. }
  2006. fn create_idl_account(
  2007. cfg: &Config,
  2008. keypair_path: &str,
  2009. program_id: &Pubkey,
  2010. idl: &Idl,
  2011. ) -> Result<Pubkey> {
  2012. // Misc.
  2013. let idl_address = IdlAccount::address(program_id);
  2014. let keypair = solana_sdk::signature::read_keypair_file(keypair_path)
  2015. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  2016. let url = cluster_url(cfg);
  2017. let client = RpcClient::new(url);
  2018. let idl_data = serialize_idl(idl)?;
  2019. // Run `Create instruction.
  2020. {
  2021. let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Create {
  2022. data_len: (idl_data.len() as u64) * 2, // Double for future growth.
  2023. })?;
  2024. let program_signer = Pubkey::find_program_address(&[], program_id).0;
  2025. let accounts = vec![
  2026. AccountMeta::new_readonly(keypair.pubkey(), true),
  2027. AccountMeta::new(idl_address, false),
  2028. AccountMeta::new_readonly(program_signer, false),
  2029. AccountMeta::new_readonly(solana_program::system_program::ID, false),
  2030. AccountMeta::new_readonly(*program_id, false),
  2031. AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
  2032. ];
  2033. let ix = Instruction {
  2034. program_id: *program_id,
  2035. accounts,
  2036. data,
  2037. };
  2038. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  2039. let tx = Transaction::new_signed_with_payer(
  2040. &[ix],
  2041. Some(&keypair.pubkey()),
  2042. &[&keypair],
  2043. recent_hash,
  2044. );
  2045. client.send_and_confirm_transaction_with_spinner_and_config(
  2046. &tx,
  2047. CommitmentConfig::confirmed(),
  2048. RpcSendTransactionConfig {
  2049. skip_preflight: true,
  2050. ..RpcSendTransactionConfig::default()
  2051. },
  2052. )?;
  2053. }
  2054. // Write directly to the IDL account buffer.
  2055. idl_write(cfg, program_id, idl, IdlAccount::address(program_id))?;
  2056. Ok(idl_address)
  2057. }
  2058. fn create_idl_buffer(
  2059. cfg: &Config,
  2060. keypair_path: &str,
  2061. program_id: &Pubkey,
  2062. idl: &Idl,
  2063. ) -> Result<Pubkey> {
  2064. let keypair = solana_sdk::signature::read_keypair_file(keypair_path)
  2065. .map_err(|_| anyhow!("Unable to read keypair file"))?;
  2066. let url = cluster_url(cfg);
  2067. let client = RpcClient::new(url);
  2068. let buffer = Keypair::generate(&mut OsRng);
  2069. // Creates the new buffer account with the system program.
  2070. let create_account_ix = {
  2071. let space = 8 + 32 + 4 + serialize_idl(idl)?.len() as usize;
  2072. let lamports = client.get_minimum_balance_for_rent_exemption(space)?;
  2073. solana_sdk::system_instruction::create_account(
  2074. &keypair.pubkey(),
  2075. &buffer.pubkey(),
  2076. lamports,
  2077. space as u64,
  2078. program_id,
  2079. )
  2080. };
  2081. // Program instruction to create the buffer.
  2082. let create_buffer_ix = {
  2083. let accounts = vec![
  2084. AccountMeta::new(buffer.pubkey(), false),
  2085. AccountMeta::new_readonly(keypair.pubkey(), true),
  2086. AccountMeta::new_readonly(sysvar::rent::ID, false),
  2087. ];
  2088. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  2089. data.append(&mut IdlInstruction::CreateBuffer.try_to_vec()?);
  2090. Instruction {
  2091. program_id: *program_id,
  2092. accounts,
  2093. data,
  2094. }
  2095. };
  2096. // Build the transaction.
  2097. let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
  2098. let tx = Transaction::new_signed_with_payer(
  2099. &[create_account_ix, create_buffer_ix],
  2100. Some(&keypair.pubkey()),
  2101. &[&keypair, &buffer],
  2102. recent_hash,
  2103. );
  2104. // Send the transaction.
  2105. client.send_and_confirm_transaction_with_spinner_and_config(
  2106. &tx,
  2107. CommitmentConfig::confirmed(),
  2108. RpcSendTransactionConfig {
  2109. skip_preflight: true,
  2110. ..RpcSendTransactionConfig::default()
  2111. },
  2112. )?;
  2113. Ok(buffer.pubkey())
  2114. }
  2115. // Serialize and compress the idl.
  2116. fn serialize_idl(idl: &Idl) -> Result<Vec<u8>> {
  2117. let json_bytes = serde_json::to_vec(idl)?;
  2118. let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
  2119. e.write_all(&json_bytes)?;
  2120. e.finish().map_err(Into::into)
  2121. }
  2122. fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8>> {
  2123. let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
  2124. data.append(&mut ix_inner.try_to_vec()?);
  2125. Ok(data)
  2126. }
  2127. fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
  2128. with_workspace(cfg_override, |cfg| {
  2129. println!("Running migration deploy script");
  2130. let url = cluster_url(cfg);
  2131. let cur_dir = std::env::current_dir()?;
  2132. let use_ts =
  2133. Path::new("tsconfig.json").exists() && Path::new("migrations/deploy.ts").exists();
  2134. if !Path::new(".anchor").exists() {
  2135. fs::create_dir(".anchor")?;
  2136. }
  2137. std::env::set_current_dir(".anchor")?;
  2138. let exit = if use_ts {
  2139. let module_path = cur_dir.join("migrations/deploy.ts");
  2140. let deploy_script_host_str =
  2141. template::deploy_ts_script_host(&url, &module_path.display().to_string());
  2142. fs::write("deploy.ts", deploy_script_host_str)?;
  2143. std::process::Command::new("ts-node")
  2144. .arg("deploy.ts")
  2145. .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
  2146. .stdout(Stdio::inherit())
  2147. .stderr(Stdio::inherit())
  2148. .output()?
  2149. } else {
  2150. let module_path = cur_dir.join("migrations/deploy.js");
  2151. let deploy_script_host_str =
  2152. template::deploy_js_script_host(&url, &module_path.display().to_string());
  2153. fs::write("deploy.js", deploy_script_host_str)?;
  2154. std::process::Command::new("node")
  2155. .arg("deploy.js")
  2156. .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
  2157. .stdout(Stdio::inherit())
  2158. .stderr(Stdio::inherit())
  2159. .output()?
  2160. };
  2161. if !exit.status.success() {
  2162. println!("Deploy failed.");
  2163. std::process::exit(exit.status.code().unwrap());
  2164. }
  2165. println!("Deploy complete.");
  2166. Ok(())
  2167. })
  2168. }
  2169. fn set_workspace_dir_or_exit() {
  2170. let d = match Config::discover(&ConfigOverride::default()) {
  2171. Err(err) => {
  2172. println!("Workspace configuration error: {}", err);
  2173. std::process::exit(1);
  2174. }
  2175. Ok(d) => d,
  2176. };
  2177. match d {
  2178. None => {
  2179. println!("Not in anchor workspace.");
  2180. std::process::exit(1);
  2181. }
  2182. Some(cfg) => {
  2183. match cfg.path().parent() {
  2184. None => {
  2185. println!("Unable to make new program");
  2186. }
  2187. Some(parent) => {
  2188. if std::env::set_current_dir(&parent).is_err() {
  2189. println!("Not in anchor workspace.");
  2190. std::process::exit(1);
  2191. }
  2192. }
  2193. };
  2194. }
  2195. }
  2196. }
  2197. #[cfg(feature = "dev")]
  2198. fn airdrop(cfg_override: &ConfigOverride) -> Result<()> {
  2199. let url = cfg_override
  2200. .cluster
  2201. .as_ref()
  2202. .unwrap_or_else(|| &Cluster::Devnet)
  2203. .url();
  2204. loop {
  2205. let exit = std::process::Command::new("solana")
  2206. .arg("airdrop")
  2207. .arg("10")
  2208. .arg("--url")
  2209. .arg(&url)
  2210. .stdout(Stdio::inherit())
  2211. .stderr(Stdio::inherit())
  2212. .output()
  2213. .expect("Must airdrop");
  2214. if !exit.status.success() {
  2215. println!("There was a problem airdropping: {:?}.", exit);
  2216. std::process::exit(exit.status.code().unwrap_or(1));
  2217. }
  2218. std::thread::sleep(std::time::Duration::from_millis(10000));
  2219. }
  2220. }
  2221. fn cluster(_cmd: ClusterCommand) -> Result<()> {
  2222. println!("Cluster Endpoints:\n");
  2223. println!("* Mainnet - https://solana-api.projectserum.com");
  2224. println!("* Mainnet - https://api.mainnet-beta.solana.com");
  2225. println!("* Devnet - https://api.devnet.solana.com");
  2226. println!("* Testnet - https://api.testnet.solana.com");
  2227. Ok(())
  2228. }
  2229. fn shell(cfg_override: &ConfigOverride) -> Result<()> {
  2230. with_workspace(cfg_override, |cfg| {
  2231. let programs = {
  2232. // Create idl map from all workspace programs.
  2233. let mut idls: HashMap<String, Idl> = cfg
  2234. .read_all_programs()?
  2235. .iter()
  2236. .filter(|program| program.idl.is_some())
  2237. .map(|program| {
  2238. (
  2239. program.idl.as_ref().unwrap().name.clone(),
  2240. program.idl.clone().unwrap(),
  2241. )
  2242. })
  2243. .collect();
  2244. // Insert all manually specified idls into the idl map.
  2245. if let Some(programs) = cfg.programs.get(&cfg.provider.cluster) {
  2246. let _ = programs
  2247. .iter()
  2248. .map(|(name, pd)| {
  2249. if let Some(idl_fp) = &pd.idl {
  2250. let file_str =
  2251. fs::read_to_string(idl_fp).expect("Unable to read IDL file");
  2252. let idl = serde_json::from_str(&file_str).expect("Idl not readable");
  2253. idls.insert(name.clone(), idl);
  2254. }
  2255. })
  2256. .collect::<Vec<_>>();
  2257. }
  2258. // Finalize program list with all programs with IDLs.
  2259. match cfg.programs.get(&cfg.provider.cluster) {
  2260. None => Vec::new(),
  2261. Some(programs) => programs
  2262. .iter()
  2263. .filter_map(|(name, program_deployment)| {
  2264. Some(ProgramWorkspace {
  2265. name: name.to_string(),
  2266. program_id: program_deployment.address,
  2267. idl: match idls.get(name) {
  2268. None => return None,
  2269. Some(idl) => idl.clone(),
  2270. },
  2271. })
  2272. })
  2273. .collect::<Vec<ProgramWorkspace>>(),
  2274. }
  2275. };
  2276. let url = cluster_url(cfg);
  2277. let js_code = template::node_shell(&url, &cfg.provider.wallet.to_string(), programs)?;
  2278. let mut child = std::process::Command::new("node")
  2279. .args(&["-e", &js_code, "-i", "--experimental-repl-await"])
  2280. .stdout(Stdio::inherit())
  2281. .stderr(Stdio::inherit())
  2282. .spawn()
  2283. .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
  2284. if !child.wait()?.success() {
  2285. println!("Error running node shell");
  2286. return Ok(());
  2287. }
  2288. Ok(())
  2289. })
  2290. }
  2291. fn run(cfg_override: &ConfigOverride, script: String) -> Result<()> {
  2292. with_workspace(cfg_override, |cfg| {
  2293. let url = cluster_url(cfg);
  2294. let script = cfg
  2295. .scripts
  2296. .get(&script)
  2297. .ok_or_else(|| anyhow!("Unable to find script"))?;
  2298. let exit = std::process::Command::new("bash")
  2299. .arg("-c")
  2300. .arg(&script)
  2301. .env("ANCHOR_PROVIDER_URL", url)
  2302. .env("ANCHOR_WALLET", cfg.provider.wallet.to_string())
  2303. .stdout(Stdio::inherit())
  2304. .stderr(Stdio::inherit())
  2305. .output()
  2306. .unwrap();
  2307. if !exit.status.success() {
  2308. std::process::exit(exit.status.code().unwrap_or(1));
  2309. }
  2310. Ok(())
  2311. })
  2312. }
  2313. fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> {
  2314. let dir = shellexpand::tilde("~/.config/anchor");
  2315. if !Path::new(&dir.to_string()).exists() {
  2316. fs::create_dir(dir.to_string())?;
  2317. }
  2318. std::env::set_current_dir(dir.to_string())?;
  2319. // Freely overwrite the entire file since it's not used for anything else.
  2320. let mut file = File::create("credentials")?;
  2321. file.write_all(template::credentials(&token).as_bytes())?;
  2322. Ok(())
  2323. }
  2324. fn publish(
  2325. cfg_override: &ConfigOverride,
  2326. program_name: String,
  2327. cargo_args: Vec<String>,
  2328. ) -> Result<()> {
  2329. // Discover the various workspace configs.
  2330. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  2331. let program = cfg
  2332. .get_program(&program_name)?
  2333. .ok_or_else(|| anyhow!("Workspace member not found"))?;
  2334. let program_cargo_lock = pathdiff::diff_paths(
  2335. program.path().join("Cargo.lock"),
  2336. cfg.path().parent().unwrap(),
  2337. )
  2338. .ok_or_else(|| anyhow!("Unable to diff Cargo.lock path"))?;
  2339. let cargo_lock = Path::new("Cargo.lock");
  2340. // There must be a Cargo.lock
  2341. if !program_cargo_lock.exists() && !cargo_lock.exists() {
  2342. return Err(anyhow!("Cargo.lock must exist for a verifiable build"));
  2343. }
  2344. println!("Publishing will make your code public. Are you sure? Enter (yes)/no:");
  2345. let answer = std::io::stdin().lock().lines().next().unwrap().unwrap();
  2346. if answer != "yes" {
  2347. println!("Aborting");
  2348. return Ok(());
  2349. }
  2350. let anchor_package = AnchorPackage::from(program_name.clone(), &cfg)?;
  2351. let anchor_package_bytes = serde_json::to_vec(&anchor_package)?;
  2352. // Set directory to top of the workspace.
  2353. let workspace_dir = cfg.path().parent().unwrap();
  2354. std::env::set_current_dir(workspace_dir)?;
  2355. // Create the workspace tarball.
  2356. let dot_anchor = workspace_dir.join(".anchor");
  2357. fs::create_dir_all(&dot_anchor)?;
  2358. let tarball_filename = dot_anchor.join(format!("{}.tar.gz", program_name));
  2359. let tar_gz = File::create(&tarball_filename)?;
  2360. let enc = GzEncoder::new(tar_gz, Compression::default());
  2361. let mut tar = tar::Builder::new(enc);
  2362. // Files that will always be included if they exist.
  2363. println!("PACKING: Anchor.toml");
  2364. tar.append_path("Anchor.toml")?;
  2365. if cargo_lock.exists() {
  2366. println!("PACKING: Cargo.lock");
  2367. tar.append_path(cargo_lock)?;
  2368. }
  2369. if Path::new("Cargo.toml").exists() {
  2370. println!("PACKING: Cargo.toml");
  2371. tar.append_path("Cargo.toml")?;
  2372. }
  2373. if Path::new("LICENSE").exists() {
  2374. println!("PACKING: LICENSE");
  2375. tar.append_path("LICENSE")?;
  2376. }
  2377. if Path::new("README.md").exists() {
  2378. println!("PACKING: README.md");
  2379. tar.append_path("README.md")?;
  2380. }
  2381. // All workspace programs.
  2382. for path in cfg.get_program_list()? {
  2383. let mut dirs = walkdir::WalkDir::new(&path)
  2384. .into_iter()
  2385. .filter_entry(|e| !is_hidden(e));
  2386. // Skip the parent dir.
  2387. let _ = dirs.next().unwrap()?;
  2388. for entry in dirs {
  2389. let e = entry.map_err(|e| anyhow!("{:?}", e))?;
  2390. let e = pathdiff::diff_paths(e.path(), cfg.path().parent().unwrap())
  2391. .ok_or_else(|| anyhow!("Unable to diff paths"))?;
  2392. let path_str = e.display().to_string();
  2393. // Skip target dir.
  2394. if !path_str.contains("target/") && !path_str.contains("/target") {
  2395. // Only add the file if it's not empty.
  2396. let metadata = fs::File::open(&e)?.metadata()?;
  2397. if metadata.len() > 0 {
  2398. println!("PACKING: {}", e.display());
  2399. if e.is_dir() {
  2400. tar.append_dir_all(&e, &e)?;
  2401. } else {
  2402. tar.append_path(&e)?;
  2403. }
  2404. }
  2405. }
  2406. }
  2407. }
  2408. // Tar pack complete.
  2409. tar.into_inner()?;
  2410. // Create tmp directory for workspace.
  2411. let ws_dir = dot_anchor.join("workspace");
  2412. if Path::exists(&ws_dir) {
  2413. fs::remove_dir_all(&ws_dir)?;
  2414. }
  2415. fs::create_dir_all(&ws_dir)?;
  2416. // Unpack the archive into the new workspace directory.
  2417. std::env::set_current_dir(&ws_dir)?;
  2418. unpack_archive(&tarball_filename)?;
  2419. // Build the program before sending it to the server.
  2420. build(
  2421. cfg_override,
  2422. None,
  2423. None,
  2424. true,
  2425. Some(program_name),
  2426. None,
  2427. None,
  2428. BootstrapMode::None,
  2429. None,
  2430. None,
  2431. cargo_args,
  2432. )?;
  2433. // Success. Now we can finally upload to the server without worrying
  2434. // about a build failure.
  2435. // Upload the tarball to the server.
  2436. let token = registry_api_token(cfg_override)?;
  2437. let form = Form::new()
  2438. .part("manifest", Part::bytes(anchor_package_bytes))
  2439. .part("workspace", {
  2440. let file = File::open(&tarball_filename)?;
  2441. Part::reader(file)
  2442. });
  2443. let client = Client::new();
  2444. let resp = client
  2445. .post(&format!("{}/api/v0/build", cfg.registry.url))
  2446. .bearer_auth(token)
  2447. .multipart(form)
  2448. .send()?;
  2449. if resp.status() == 200 {
  2450. println!("Build triggered");
  2451. } else {
  2452. println!(
  2453. "{:?}",
  2454. resp.text().unwrap_or_else(|_| "Server error".to_string())
  2455. );
  2456. }
  2457. Ok(())
  2458. }
  2459. // Unpacks the tarball into the current directory.
  2460. fn unpack_archive(tar_path: impl AsRef<Path>) -> Result<()> {
  2461. let tar = GzDecoder::new(std::fs::File::open(tar_path)?);
  2462. let mut archive = Archive::new(tar);
  2463. archive.unpack(".")?;
  2464. archive.into_inner();
  2465. Ok(())
  2466. }
  2467. fn registry_api_token(_cfg_override: &ConfigOverride) -> Result<String> {
  2468. #[derive(Debug, Deserialize)]
  2469. struct Registry {
  2470. token: String,
  2471. }
  2472. #[derive(Debug, Deserialize)]
  2473. struct Credentials {
  2474. registry: Registry,
  2475. }
  2476. let filename = shellexpand::tilde("~/.config/anchor/credentials");
  2477. let mut file = File::open(filename.to_string())?;
  2478. let mut contents = String::new();
  2479. file.read_to_string(&mut contents)?;
  2480. let credentials_toml: Credentials = toml::from_str(&contents)?;
  2481. Ok(credentials_toml.registry.token)
  2482. }
  2483. fn keys(cfg_override: &ConfigOverride, cmd: KeysCommand) -> Result<()> {
  2484. match cmd {
  2485. KeysCommand::List => keys_list(cfg_override),
  2486. }
  2487. }
  2488. fn keys_list(cfg_override: &ConfigOverride) -> Result<()> {
  2489. let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
  2490. for program in cfg.read_all_programs()? {
  2491. let pubkey = program.pubkey()?;
  2492. println!("{}: {}", program.lib_name, pubkey);
  2493. }
  2494. Ok(())
  2495. }
  2496. fn localnet(
  2497. cfg_override: &ConfigOverride,
  2498. skip_build: bool,
  2499. skip_deploy: bool,
  2500. cargo_args: Vec<String>,
  2501. ) -> Result<()> {
  2502. with_workspace(cfg_override, |cfg| {
  2503. // Build if needed.
  2504. if !skip_build {
  2505. build(
  2506. cfg_override,
  2507. None,
  2508. None,
  2509. false,
  2510. None,
  2511. None,
  2512. None,
  2513. BootstrapMode::None,
  2514. None,
  2515. None,
  2516. cargo_args,
  2517. )?;
  2518. }
  2519. let flags = match skip_deploy {
  2520. true => None,
  2521. false => Some(validator_flags(cfg)?),
  2522. };
  2523. let validator_handle = &mut start_test_validator(cfg, flags, false)?;
  2524. // Setup log reader.
  2525. let url = test_validator_rpc_url(cfg);
  2526. let log_streams = stream_logs(cfg, &url);
  2527. std::io::stdin().lock().lines().next().unwrap().unwrap();
  2528. // Check all errors and shut down.
  2529. if let Err(err) = validator_handle.kill() {
  2530. println!(
  2531. "Failed to kill subprocess {}: {}",
  2532. validator_handle.id(),
  2533. err
  2534. );
  2535. }
  2536. for mut child in log_streams? {
  2537. if let Err(err) = child.kill() {
  2538. println!("Failed to kill subprocess {}: {}", child.id(), err);
  2539. }
  2540. }
  2541. Ok(())
  2542. })
  2543. }
  2544. // with_workspace ensures the current working directory is always the top level
  2545. // workspace directory, i.e., where the `Anchor.toml` file is located, before
  2546. // and after the closure invocation.
  2547. //
  2548. // The closure passed into this function must never change the working directory
  2549. // to be outside the workspace. Doing so will have undefined behavior.
  2550. fn with_workspace<R>(cfg_override: &ConfigOverride, f: impl FnOnce(&WithPath<Config>) -> R) -> R {
  2551. set_workspace_dir_or_exit();
  2552. let cfg = Config::discover(cfg_override)
  2553. .expect("Previously set the workspace dir")
  2554. .expect("Anchor.toml must always exist");
  2555. let r = f(&cfg);
  2556. set_workspace_dir_or_exit();
  2557. r
  2558. }
  2559. fn is_hidden(entry: &walkdir::DirEntry) -> bool {
  2560. entry
  2561. .file_name()
  2562. .to_str()
  2563. .map(|s| s == "." || s.starts_with('.') || s == "target")
  2564. .unwrap_or(false)
  2565. }
  2566. fn get_node_version() -> Result<Version> {
  2567. let node_version = std::process::Command::new("node")
  2568. .arg("--version")
  2569. .stderr(Stdio::inherit())
  2570. .output()
  2571. .map_err(|e| anyhow::format_err!("node failed: {}", e.to_string()))?;
  2572. let output = std::str::from_utf8(&node_version.stdout)?
  2573. .strip_prefix('v')
  2574. .unwrap()
  2575. .trim();
  2576. Version::parse(output).map_err(Into::into)
  2577. }
  2578. fn get_node_dns_option() -> Result<&'static str> {
  2579. let version = get_node_version()?;
  2580. let req = VersionReq::parse(">=16.4.0").unwrap();
  2581. let option = match req.matches(&version) {
  2582. true => "--dns-result-order=ipv4first",
  2583. false => "",
  2584. };
  2585. Ok(option)
  2586. }