lib.rs 93 KB

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