lib.rs 154 KB

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