lib.rs 152 KB

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