lib.rs 106 KB

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