lib.rs 126 KB

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