lib.rs 133 KB

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