config.rs 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418
  1. use crate::is_hidden;
  2. use anchor_client::Cluster;
  3. use anchor_syn::idl::types::Idl;
  4. use anyhow::{anyhow, bail, Context, Error, Result};
  5. use clap::{Parser, ValueEnum};
  6. use dirs::home_dir;
  7. use heck::ToSnakeCase;
  8. use reqwest::Url;
  9. use serde::de::{self, MapAccess, Visitor};
  10. use serde::{Deserialize, Deserializer, Serialize};
  11. use solana_cli_config::{Config as SolanaConfig, CONFIG_FILE};
  12. use solana_sdk::pubkey::Pubkey;
  13. use solana_sdk::signature::{Keypair, Signer};
  14. use solang_parser::pt::{ContractTy, SourceUnitPart};
  15. use std::collections::{BTreeMap, HashMap};
  16. use std::convert::TryFrom;
  17. use std::ffi::OsStr;
  18. use std::fs::{self, File};
  19. use std::io::prelude::*;
  20. use std::marker::PhantomData;
  21. use std::ops::Deref;
  22. use std::path::Path;
  23. use std::path::PathBuf;
  24. use std::str::FromStr;
  25. use std::{fmt, io};
  26. use walkdir::WalkDir;
  27. pub trait Merge: Sized {
  28. fn merge(&mut self, _other: Self) {}
  29. }
  30. #[derive(Default, Debug, Parser)]
  31. pub struct ConfigOverride {
  32. /// Cluster override.
  33. #[clap(global = true, long = "provider.cluster")]
  34. pub cluster: Option<Cluster>,
  35. /// Wallet override.
  36. #[clap(global = true, long = "provider.wallet")]
  37. pub wallet: Option<WalletPath>,
  38. }
  39. #[derive(Debug)]
  40. pub struct WithPath<T> {
  41. inner: T,
  42. path: PathBuf,
  43. }
  44. impl<T> WithPath<T> {
  45. pub fn new(inner: T, path: PathBuf) -> Self {
  46. Self { inner, path }
  47. }
  48. pub fn path(&self) -> &PathBuf {
  49. &self.path
  50. }
  51. pub fn into_inner(self) -> T {
  52. self.inner
  53. }
  54. }
  55. impl<T> std::convert::AsRef<T> for WithPath<T> {
  56. fn as_ref(&self) -> &T {
  57. &self.inner
  58. }
  59. }
  60. #[derive(Debug, Clone, PartialEq)]
  61. pub struct Manifest(cargo_toml::Manifest);
  62. impl Manifest {
  63. pub fn from_path(p: impl AsRef<Path>) -> Result<Self> {
  64. cargo_toml::Manifest::from_path(&p)
  65. .map(Manifest)
  66. .map_err(anyhow::Error::from)
  67. .with_context(|| format!("Error reading manifest from path: {}", p.as_ref().display()))
  68. }
  69. pub fn lib_name(&self) -> Result<String> {
  70. if self.lib.is_some() && self.lib.as_ref().unwrap().name.is_some() {
  71. Ok(self
  72. .lib
  73. .as_ref()
  74. .unwrap()
  75. .name
  76. .as_ref()
  77. .unwrap()
  78. .to_string()
  79. .to_snake_case())
  80. } else {
  81. Ok(self
  82. .package
  83. .as_ref()
  84. .ok_or_else(|| anyhow!("package section not provided"))?
  85. .name
  86. .to_string()
  87. .to_snake_case())
  88. }
  89. }
  90. pub fn version(&self) -> String {
  91. match &self.package {
  92. Some(package) => package.version().to_string(),
  93. _ => "0.0.0".to_string(),
  94. }
  95. }
  96. // Climbs each parent directory from the current dir until we find a Cargo.toml
  97. pub fn discover() -> Result<Option<WithPath<Manifest>>> {
  98. Manifest::discover_from_path(std::env::current_dir()?)
  99. }
  100. // Climbs each parent directory from a given starting directory until we find a Cargo.toml.
  101. pub fn discover_from_path(start_from: PathBuf) -> Result<Option<WithPath<Manifest>>> {
  102. let mut cwd_opt = Some(start_from.as_path());
  103. while let Some(cwd) = cwd_opt {
  104. let mut anchor_toml = false;
  105. for f in fs::read_dir(cwd).with_context(|| {
  106. format!("Error reading the directory with path: {}", cwd.display())
  107. })? {
  108. let p = f
  109. .with_context(|| {
  110. format!("Error reading the directory with path: {}", cwd.display())
  111. })?
  112. .path();
  113. if let Some(filename) = p.file_name() {
  114. if filename.to_str() == Some("Cargo.toml") {
  115. let m = WithPath::new(Manifest::from_path(&p)?, p);
  116. return Ok(Some(m));
  117. }
  118. if filename.to_str() == Some("Anchor.toml") {
  119. anchor_toml = true;
  120. }
  121. }
  122. }
  123. // Not found. Go up a directory level, but don't go up from Anchor.toml
  124. if anchor_toml {
  125. break;
  126. }
  127. cwd_opt = cwd.parent();
  128. }
  129. Ok(None)
  130. }
  131. }
  132. impl Deref for Manifest {
  133. type Target = cargo_toml::Manifest;
  134. fn deref(&self) -> &Self::Target {
  135. &self.0
  136. }
  137. }
  138. impl WithPath<Config> {
  139. pub fn get_rust_program_list(&self) -> Result<Vec<PathBuf>> {
  140. // Canonicalize the workspace filepaths to compare with relative paths.
  141. let (members, exclude) = self.canonicalize_workspace()?;
  142. // Get all candidate programs.
  143. //
  144. // If [workspace.members] exists, then use that.
  145. // Otherwise, default to `programs/*`.
  146. let program_paths: Vec<PathBuf> = {
  147. if members.is_empty() {
  148. let path = self.path().parent().unwrap().join("programs");
  149. if let Ok(entries) = fs::read_dir(path) {
  150. entries
  151. .filter(|entry| entry.as_ref().map(|e| e.path().is_dir()).unwrap_or(false))
  152. .map(|dir| dir.map(|d| d.path().canonicalize().unwrap()))
  153. .collect::<Vec<Result<PathBuf, std::io::Error>>>()
  154. .into_iter()
  155. .collect::<Result<Vec<PathBuf>, std::io::Error>>()?
  156. } else {
  157. Vec::new()
  158. }
  159. } else {
  160. members
  161. }
  162. };
  163. // Filter out everything part of the exclude array.
  164. Ok(program_paths
  165. .into_iter()
  166. .filter(|m| !exclude.contains(m))
  167. .collect())
  168. }
  169. /// Parse all the files with the .sol extension, and get a list of the all
  170. /// contracts defined in them along with their path. One Solidity file may
  171. /// define multiple contracts.
  172. pub fn get_solidity_program_list(&self) -> Result<Vec<(String, PathBuf)>> {
  173. let path = self.path().parent().unwrap().join("solidity");
  174. let mut res = Vec::new();
  175. if let Ok(entries) = fs::read_dir(path) {
  176. for entry in entries {
  177. let path = entry?.path();
  178. if !path.is_file() || path.extension() != Some(OsStr::new("sol")) {
  179. continue;
  180. }
  181. let source = fs::read_to_string(&path)?;
  182. let tree = match solang_parser::parse(&source, 0) {
  183. Ok((tree, _)) => tree,
  184. Err(diag) => {
  185. // The parser can return multiple errors, however this is exceedingly rare.
  186. // Just use the first one, else the formatting will be a mess.
  187. bail!(
  188. "{}: {}: {}",
  189. path.display(),
  190. diag[0].level.to_string(),
  191. diag[0].message
  192. );
  193. }
  194. };
  195. tree.0.iter().for_each(|part| {
  196. if let SourceUnitPart::ContractDefinition(contract) = part {
  197. // Must be a contract, not library/interface/abstract contract
  198. if matches!(&contract.ty, ContractTy::Contract(..)) {
  199. if let Some(name) = &contract.name {
  200. res.push((name.name.clone(), path.clone()));
  201. }
  202. }
  203. }
  204. });
  205. }
  206. }
  207. Ok(res)
  208. }
  209. pub fn read_all_programs(&self) -> Result<Vec<Program>> {
  210. let mut r = vec![];
  211. for path in self.get_rust_program_list()? {
  212. let cargo = Manifest::from_path(path.join("Cargo.toml"))?;
  213. let lib_name = cargo.lib_name()?;
  214. let idl_filepath = format!("target/idl/{lib_name}.json");
  215. let idl = fs::read(idl_filepath)
  216. .ok()
  217. .map(|bytes| serde_json::from_reader(&*bytes))
  218. .transpose()?;
  219. r.push(Program {
  220. lib_name,
  221. solidity: false,
  222. path,
  223. idl,
  224. });
  225. }
  226. for (lib_name, path) in self.get_solidity_program_list()? {
  227. let idl_filepath = format!("target/idl/{lib_name}.json");
  228. let idl = fs::read(idl_filepath)
  229. .ok()
  230. .map(|bytes| serde_json::from_reader(&*bytes))
  231. .transpose()?;
  232. r.push(Program {
  233. lib_name,
  234. solidity: true,
  235. path,
  236. idl,
  237. });
  238. }
  239. Ok(r)
  240. }
  241. /// Read and get all the programs from the workspace.
  242. ///
  243. /// This method will only return the given program if `name` exists.
  244. pub fn get_programs(&self, name: Option<String>) -> Result<Vec<Program>> {
  245. let programs = self.read_all_programs()?;
  246. let programs = match name {
  247. Some(name) => vec![programs
  248. .into_iter()
  249. .find(|program| {
  250. name == program.lib_name
  251. || name == program.path.file_name().unwrap().to_str().unwrap()
  252. })
  253. .ok_or_else(|| anyhow!("Program {name} not found"))?],
  254. None => programs,
  255. };
  256. Ok(programs)
  257. }
  258. /// Get the specified program from the workspace.
  259. pub fn get_program(&self, name: &str) -> Result<Program> {
  260. self.get_programs(Some(name.to_owned()))?
  261. .into_iter()
  262. .next()
  263. .ok_or_else(|| anyhow!("Expected a program"))
  264. }
  265. pub fn canonicalize_workspace(&self) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
  266. let members = self
  267. .workspace
  268. .members
  269. .iter()
  270. .map(|m| {
  271. self.path()
  272. .parent()
  273. .unwrap()
  274. .join(m)
  275. .canonicalize()
  276. .unwrap_or_else(|_| {
  277. panic!("Error reading workspace.members. File {:?} does not exist at path {:?}.", m, self.path)
  278. })
  279. })
  280. .collect();
  281. let exclude = self
  282. .workspace
  283. .exclude
  284. .iter()
  285. .map(|m| {
  286. self.path()
  287. .parent()
  288. .unwrap()
  289. .join(m)
  290. .canonicalize()
  291. .unwrap_or_else(|_| {
  292. panic!("Error reading workspace.exclude. File {:?} does not exist at path {:?}.", m, self.path)
  293. })
  294. })
  295. .collect();
  296. Ok((members, exclude))
  297. }
  298. }
  299. impl<T> std::ops::Deref for WithPath<T> {
  300. type Target = T;
  301. fn deref(&self) -> &Self::Target {
  302. &self.inner
  303. }
  304. }
  305. impl<T> std::ops::DerefMut for WithPath<T> {
  306. fn deref_mut(&mut self) -> &mut Self::Target {
  307. &mut self.inner
  308. }
  309. }
  310. #[derive(Debug, Default)]
  311. pub struct Config {
  312. pub toolchain: ToolchainConfig,
  313. pub features: FeaturesConfig,
  314. pub registry: RegistryConfig,
  315. pub provider: ProviderConfig,
  316. pub programs: ProgramsConfig,
  317. pub scripts: ScriptsConfig,
  318. pub workspace: WorkspaceConfig,
  319. // Separate entry next to test_config because
  320. // "anchor localnet" only has access to the Anchor.toml,
  321. // not the Test.toml files
  322. pub test_validator: Option<TestValidator>,
  323. pub test_config: Option<TestConfig>,
  324. }
  325. #[derive(Default, Clone, Debug, Serialize, Deserialize)]
  326. pub struct ToolchainConfig {
  327. pub anchor_version: Option<String>,
  328. pub solana_version: Option<String>,
  329. }
  330. #[derive(Default, Clone, Debug, Serialize, Deserialize)]
  331. pub struct FeaturesConfig {
  332. #[serde(default)]
  333. pub seeds: bool,
  334. #[serde(default, rename = "skip-lint")]
  335. pub skip_lint: bool,
  336. }
  337. #[derive(Clone, Debug, Serialize, Deserialize)]
  338. pub struct RegistryConfig {
  339. pub url: String,
  340. }
  341. impl Default for RegistryConfig {
  342. fn default() -> Self {
  343. Self {
  344. url: "https://api.apr.dev".to_string(),
  345. }
  346. }
  347. }
  348. #[derive(Debug, Default)]
  349. pub struct ProviderConfig {
  350. pub cluster: Cluster,
  351. pub wallet: WalletPath,
  352. }
  353. pub type ScriptsConfig = BTreeMap<String, String>;
  354. pub type ProgramsConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
  355. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  356. pub struct WorkspaceConfig {
  357. #[serde(default, skip_serializing_if = "Vec::is_empty")]
  358. pub members: Vec<String>,
  359. #[serde(default, skip_serializing_if = "Vec::is_empty")]
  360. pub exclude: Vec<String>,
  361. #[serde(default, skip_serializing_if = "String::is_empty")]
  362. pub types: String,
  363. }
  364. #[derive(ValueEnum, Parser, Clone, PartialEq, Eq, Debug)]
  365. pub enum BootstrapMode {
  366. None,
  367. Debian,
  368. }
  369. #[derive(ValueEnum, Parser, Clone, PartialEq, Eq, Debug)]
  370. pub enum ProgramArch {
  371. Bpf,
  372. Sbf,
  373. }
  374. impl ProgramArch {
  375. pub fn build_subcommand(&self) -> &str {
  376. match self {
  377. Self::Bpf => "build-bpf",
  378. Self::Sbf => "build-sbf",
  379. }
  380. }
  381. }
  382. #[derive(Debug, Clone)]
  383. pub struct BuildConfig {
  384. pub verifiable: bool,
  385. pub solana_version: Option<String>,
  386. pub docker_image: String,
  387. pub bootstrap: BootstrapMode,
  388. }
  389. impl Config {
  390. pub fn add_test_config(
  391. &mut self,
  392. root: impl AsRef<Path>,
  393. test_paths: Vec<PathBuf>,
  394. ) -> Result<()> {
  395. self.test_config = TestConfig::discover(root, test_paths)?;
  396. Ok(())
  397. }
  398. pub fn docker(&self) -> String {
  399. let version = self
  400. .toolchain
  401. .anchor_version
  402. .as_deref()
  403. .unwrap_or(crate::DOCKER_BUILDER_VERSION);
  404. format!("backpackapp/build:v{version}")
  405. }
  406. pub fn discover(cfg_override: &ConfigOverride) -> Result<Option<WithPath<Config>>> {
  407. Config::_discover().map(|opt| {
  408. opt.map(|mut cfg| {
  409. if let Some(cluster) = cfg_override.cluster.clone() {
  410. cfg.provider.cluster = cluster;
  411. }
  412. if let Some(wallet) = cfg_override.wallet.clone() {
  413. cfg.provider.wallet = wallet;
  414. }
  415. cfg
  416. })
  417. })
  418. }
  419. // Climbs each parent directory until we find an Anchor.toml.
  420. fn _discover() -> Result<Option<WithPath<Config>>> {
  421. let _cwd = std::env::current_dir()?;
  422. let mut cwd_opt = Some(_cwd.as_path());
  423. while let Some(cwd) = cwd_opt {
  424. for f in fs::read_dir(cwd).with_context(|| {
  425. format!("Error reading the directory with path: {}", cwd.display())
  426. })? {
  427. let p = f
  428. .with_context(|| {
  429. format!("Error reading the directory with path: {}", cwd.display())
  430. })?
  431. .path();
  432. if let Some(filename) = p.file_name() {
  433. if filename.to_str() == Some("Anchor.toml") {
  434. let cfg = Config::from_path(&p)?;
  435. return Ok(Some(WithPath::new(cfg, p)));
  436. }
  437. }
  438. }
  439. cwd_opt = cwd.parent();
  440. }
  441. Ok(None)
  442. }
  443. fn from_path(p: impl AsRef<Path>) -> Result<Self> {
  444. fs::read_to_string(&p)
  445. .with_context(|| format!("Error reading the file with path: {}", p.as_ref().display()))?
  446. .parse::<Self>()
  447. }
  448. pub fn wallet_kp(&self) -> Result<Keypair> {
  449. solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string())
  450. .map_err(|_| anyhow!("Unable to read keypair file"))
  451. }
  452. }
  453. #[derive(Debug, Serialize, Deserialize)]
  454. struct _Config {
  455. toolchain: Option<ToolchainConfig>,
  456. features: Option<FeaturesConfig>,
  457. programs: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
  458. registry: Option<RegistryConfig>,
  459. provider: Provider,
  460. workspace: Option<WorkspaceConfig>,
  461. scripts: Option<ScriptsConfig>,
  462. test: Option<_TestValidator>,
  463. }
  464. #[derive(Debug, Serialize, Deserialize)]
  465. struct Provider {
  466. #[serde(deserialize_with = "des_cluster")]
  467. cluster: Cluster,
  468. wallet: String,
  469. }
  470. fn des_cluster<'de, D>(deserializer: D) -> Result<Cluster, D::Error>
  471. where
  472. D: Deserializer<'de>,
  473. {
  474. struct StringOrCustomCluster(PhantomData<fn() -> Cluster>);
  475. impl<'de> Visitor<'de> for StringOrCustomCluster {
  476. type Value = Cluster;
  477. fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
  478. formatter.write_str("string or map")
  479. }
  480. fn visit_str<E>(self, value: &str) -> Result<Cluster, E>
  481. where
  482. E: de::Error,
  483. {
  484. value.parse().map_err(de::Error::custom)
  485. }
  486. fn visit_map<M>(self, mut map: M) -> Result<Cluster, M::Error>
  487. where
  488. M: MapAccess<'de>,
  489. {
  490. // Gets keys
  491. if let (Some((http_key, http_value)), Some((ws_key, ws_value))) = (
  492. map.next_entry::<String, String>()?,
  493. map.next_entry::<String, String>()?,
  494. ) {
  495. // Checks keys
  496. if http_key != "http" || ws_key != "ws" {
  497. return Err(de::Error::custom("Invalid key"));
  498. }
  499. // Checks urls
  500. Url::parse(&http_value).map_err(de::Error::custom)?;
  501. Url::parse(&ws_value).map_err(de::Error::custom)?;
  502. Ok(Cluster::Custom(http_value, ws_value))
  503. } else {
  504. Err(de::Error::custom("Invalid entry"))
  505. }
  506. }
  507. }
  508. deserializer.deserialize_any(StringOrCustomCluster(PhantomData))
  509. }
  510. impl ToString for Config {
  511. fn to_string(&self) -> String {
  512. let programs = {
  513. let c = ser_programs(&self.programs);
  514. if c.is_empty() {
  515. None
  516. } else {
  517. Some(c)
  518. }
  519. };
  520. let cfg = _Config {
  521. toolchain: Some(self.toolchain.clone()),
  522. features: Some(self.features.clone()),
  523. registry: Some(self.registry.clone()),
  524. provider: Provider {
  525. cluster: self.provider.cluster.clone(),
  526. wallet: self.provider.wallet.stringify_with_tilde(),
  527. },
  528. test: self.test_validator.clone().map(Into::into),
  529. scripts: match self.scripts.is_empty() {
  530. true => None,
  531. false => Some(self.scripts.clone()),
  532. },
  533. programs,
  534. workspace: (!self.workspace.members.is_empty() || !self.workspace.exclude.is_empty())
  535. .then(|| self.workspace.clone()),
  536. };
  537. toml::to_string(&cfg).expect("Must be well formed")
  538. }
  539. }
  540. impl FromStr for Config {
  541. type Err = Error;
  542. fn from_str(s: &str) -> Result<Self, Self::Err> {
  543. let cfg: _Config = toml::from_str(s)
  544. .map_err(|e| anyhow::format_err!("Unable to deserialize config: {}", e.to_string()))?;
  545. Ok(Config {
  546. toolchain: cfg.toolchain.unwrap_or_default(),
  547. features: cfg.features.unwrap_or_default(),
  548. registry: cfg.registry.unwrap_or_default(),
  549. provider: ProviderConfig {
  550. cluster: cfg.provider.cluster,
  551. wallet: shellexpand::tilde(&cfg.provider.wallet).parse()?,
  552. },
  553. scripts: cfg.scripts.unwrap_or_default(),
  554. test_validator: cfg.test.map(Into::into),
  555. test_config: None,
  556. programs: cfg.programs.map_or(Ok(BTreeMap::new()), deser_programs)?,
  557. workspace: cfg.workspace.unwrap_or_default(),
  558. })
  559. }
  560. }
  561. pub fn get_solana_cfg_url() -> Result<String, io::Error> {
  562. let config_file = CONFIG_FILE.as_ref().ok_or_else(|| {
  563. io::Error::new(
  564. io::ErrorKind::NotFound,
  565. "Default Solana config was not found",
  566. )
  567. })?;
  568. SolanaConfig::load(config_file).map(|config| config.json_rpc_url)
  569. }
  570. fn ser_programs(
  571. programs: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
  572. ) -> BTreeMap<String, BTreeMap<String, serde_json::Value>> {
  573. programs
  574. .iter()
  575. .map(|(cluster, programs)| {
  576. let cluster = cluster.to_string();
  577. let programs = programs
  578. .iter()
  579. .map(|(name, deployment)| {
  580. (
  581. name.clone(),
  582. to_value(&_ProgramDeployment::from(deployment)),
  583. )
  584. })
  585. .collect::<BTreeMap<String, serde_json::Value>>();
  586. (cluster, programs)
  587. })
  588. .collect::<BTreeMap<String, BTreeMap<String, serde_json::Value>>>()
  589. }
  590. fn to_value(dep: &_ProgramDeployment) -> serde_json::Value {
  591. if dep.path.is_none() && dep.idl.is_none() {
  592. return serde_json::Value::String(dep.address.to_string());
  593. }
  594. serde_json::to_value(dep).unwrap()
  595. }
  596. fn deser_programs(
  597. programs: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
  598. ) -> Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>> {
  599. programs
  600. .iter()
  601. .map(|(cluster, programs)| {
  602. let cluster: Cluster = cluster.parse()?;
  603. let programs = programs
  604. .iter()
  605. .map(|(name, program_id)| {
  606. Ok((
  607. name.clone(),
  608. ProgramDeployment::try_from(match &program_id {
  609. serde_json::Value::String(address) => _ProgramDeployment {
  610. address: address.parse()?,
  611. path: None,
  612. idl: None,
  613. },
  614. serde_json::Value::Object(_) => {
  615. serde_json::from_value(program_id.clone())
  616. .map_err(|_| anyhow!("Unable to read toml"))?
  617. }
  618. _ => return Err(anyhow!("Invalid toml type")),
  619. })?,
  620. ))
  621. })
  622. .collect::<Result<BTreeMap<String, ProgramDeployment>>>()?;
  623. Ok((cluster, programs))
  624. })
  625. .collect::<Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>>>()
  626. }
  627. #[derive(Default, Debug, Clone, Serialize, Deserialize)]
  628. pub struct TestValidator {
  629. pub genesis: Option<Vec<GenesisEntry>>,
  630. pub validator: Option<Validator>,
  631. pub startup_wait: i32,
  632. pub shutdown_wait: i32,
  633. pub upgradeable: bool,
  634. }
  635. #[derive(Default, Debug, Clone, Serialize, Deserialize)]
  636. pub struct _TestValidator {
  637. #[serde(skip_serializing_if = "Option::is_none")]
  638. pub genesis: Option<Vec<GenesisEntry>>,
  639. #[serde(skip_serializing_if = "Option::is_none")]
  640. pub validator: Option<_Validator>,
  641. #[serde(skip_serializing_if = "Option::is_none")]
  642. pub startup_wait: Option<i32>,
  643. #[serde(skip_serializing_if = "Option::is_none")]
  644. pub shutdown_wait: Option<i32>,
  645. #[serde(skip_serializing_if = "Option::is_none")]
  646. pub upgradeable: Option<bool>,
  647. }
  648. pub const STARTUP_WAIT: i32 = 5000;
  649. pub const SHUTDOWN_WAIT: i32 = 2000;
  650. impl From<_TestValidator> for TestValidator {
  651. fn from(_test_validator: _TestValidator) -> Self {
  652. Self {
  653. shutdown_wait: _test_validator.shutdown_wait.unwrap_or(SHUTDOWN_WAIT),
  654. startup_wait: _test_validator.startup_wait.unwrap_or(STARTUP_WAIT),
  655. genesis: _test_validator.genesis,
  656. validator: _test_validator.validator.map(Into::into),
  657. upgradeable: _test_validator.upgradeable.unwrap_or(false),
  658. }
  659. }
  660. }
  661. impl From<TestValidator> for _TestValidator {
  662. fn from(test_validator: TestValidator) -> Self {
  663. Self {
  664. shutdown_wait: Some(test_validator.shutdown_wait),
  665. startup_wait: Some(test_validator.startup_wait),
  666. genesis: test_validator.genesis,
  667. validator: test_validator.validator.map(Into::into),
  668. upgradeable: Some(test_validator.upgradeable),
  669. }
  670. }
  671. }
  672. #[derive(Debug, Clone)]
  673. pub struct TestConfig {
  674. pub test_suite_configs: HashMap<PathBuf, TestToml>,
  675. }
  676. impl Deref for TestConfig {
  677. type Target = HashMap<PathBuf, TestToml>;
  678. fn deref(&self) -> &Self::Target {
  679. &self.test_suite_configs
  680. }
  681. }
  682. impl TestConfig {
  683. pub fn discover(root: impl AsRef<Path>, test_paths: Vec<PathBuf>) -> Result<Option<Self>> {
  684. let walker = WalkDir::new(root).into_iter();
  685. let mut test_suite_configs = HashMap::new();
  686. for entry in walker.filter_entry(|e| !is_hidden(e)) {
  687. let entry = entry?;
  688. if entry.file_name() == "Test.toml" {
  689. let entry_path = entry.path();
  690. let test_toml = TestToml::from_path(entry_path)?;
  691. if test_paths.is_empty() || test_paths.iter().any(|p| entry_path.starts_with(p)) {
  692. test_suite_configs.insert(entry.path().into(), test_toml);
  693. }
  694. }
  695. }
  696. Ok(match test_suite_configs.is_empty() {
  697. true => None,
  698. false => Some(Self { test_suite_configs }),
  699. })
  700. }
  701. }
  702. // This file needs to have the same (sub)structure as Anchor.toml
  703. // so it can be parsed as a base test file from an Anchor.toml
  704. #[derive(Debug, Clone, Serialize, Deserialize)]
  705. pub struct _TestToml {
  706. pub extends: Option<Vec<String>>,
  707. pub test: Option<_TestValidator>,
  708. pub scripts: Option<ScriptsConfig>,
  709. }
  710. impl _TestToml {
  711. fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
  712. let s = fs::read_to_string(&path)?;
  713. let parsed_toml: Self = toml::from_str(&s)?;
  714. let mut current_toml = _TestToml {
  715. extends: None,
  716. test: None,
  717. scripts: None,
  718. };
  719. if let Some(bases) = &parsed_toml.extends {
  720. for base in bases {
  721. let mut canonical_base = base.clone();
  722. canonical_base = canonicalize_filepath_from_origin(&canonical_base, &path)?;
  723. current_toml.merge(_TestToml::from_path(&canonical_base)?);
  724. }
  725. }
  726. current_toml.merge(parsed_toml);
  727. if let Some(test) = &mut current_toml.test {
  728. if let Some(genesis_programs) = &mut test.genesis {
  729. for entry in genesis_programs {
  730. entry.program = canonicalize_filepath_from_origin(&entry.program, &path)?;
  731. }
  732. }
  733. if let Some(validator) = &mut test.validator {
  734. if let Some(ledger_dir) = &mut validator.ledger {
  735. *ledger_dir = canonicalize_filepath_from_origin(&ledger_dir, &path)?;
  736. }
  737. if let Some(accounts) = &mut validator.account {
  738. for entry in accounts {
  739. entry.filename = canonicalize_filepath_from_origin(&entry.filename, &path)?;
  740. }
  741. }
  742. }
  743. }
  744. Ok(current_toml)
  745. }
  746. }
  747. /// canonicalizes the `file_path` arg.
  748. /// uses the `path` arg as the current dir
  749. /// from which to turn the relative path
  750. /// into a canonical one
  751. fn canonicalize_filepath_from_origin(
  752. file_path: impl AsRef<Path>,
  753. origin: impl AsRef<Path>,
  754. ) -> Result<String> {
  755. let previous_dir = std::env::current_dir()?;
  756. std::env::set_current_dir(origin.as_ref().parent().unwrap())?;
  757. let result = fs::canonicalize(&file_path)
  758. .with_context(|| {
  759. format!(
  760. "Error reading (possibly relative) path: {}. If relative, this is the path that was used as the current path: {}",
  761. &file_path.as_ref().display(),
  762. &origin.as_ref().display()
  763. )
  764. })?
  765. .display()
  766. .to_string();
  767. std::env::set_current_dir(previous_dir)?;
  768. Ok(result)
  769. }
  770. #[derive(Debug, Clone, Serialize, Deserialize)]
  771. pub struct TestToml {
  772. #[serde(skip_serializing_if = "Option::is_none")]
  773. pub test: Option<TestValidator>,
  774. pub scripts: ScriptsConfig,
  775. }
  776. impl TestToml {
  777. pub fn from_path(p: impl AsRef<Path>) -> Result<Self> {
  778. WithPath::new(_TestToml::from_path(&p)?, p.as_ref().into()).try_into()
  779. }
  780. }
  781. impl Merge for _TestToml {
  782. fn merge(&mut self, other: Self) {
  783. let mut my_scripts = self.scripts.take();
  784. match &mut my_scripts {
  785. None => my_scripts = other.scripts,
  786. Some(my_scripts) => {
  787. if let Some(other_scripts) = other.scripts {
  788. for (name, script) in other_scripts {
  789. my_scripts.insert(name, script);
  790. }
  791. }
  792. }
  793. }
  794. let mut my_test = self.test.take();
  795. match &mut my_test {
  796. Some(my_test) => {
  797. if let Some(other_test) = other.test {
  798. if let Some(startup_wait) = other_test.startup_wait {
  799. my_test.startup_wait = Some(startup_wait);
  800. }
  801. if let Some(other_genesis) = other_test.genesis {
  802. match &mut my_test.genesis {
  803. Some(my_genesis) => {
  804. for other_entry in other_genesis {
  805. match my_genesis
  806. .iter()
  807. .position(|g| *g.address == other_entry.address)
  808. {
  809. None => my_genesis.push(other_entry),
  810. Some(i) => my_genesis[i] = other_entry,
  811. }
  812. }
  813. }
  814. None => my_test.genesis = Some(other_genesis),
  815. }
  816. }
  817. let mut my_validator = my_test.validator.take();
  818. match &mut my_validator {
  819. None => my_validator = other_test.validator,
  820. Some(my_validator) => {
  821. if let Some(other_validator) = other_test.validator {
  822. my_validator.merge(other_validator)
  823. }
  824. }
  825. }
  826. my_test.validator = my_validator;
  827. }
  828. }
  829. None => my_test = other.test,
  830. };
  831. // Instantiating a new Self object here ensures that
  832. // this function will fail to compile if new fields get added
  833. // to Self. This is useful as a reminder if they also require merging
  834. *self = Self {
  835. test: my_test,
  836. scripts: my_scripts,
  837. extends: self.extends.take(),
  838. };
  839. }
  840. }
  841. impl TryFrom<WithPath<_TestToml>> for TestToml {
  842. type Error = Error;
  843. fn try_from(mut value: WithPath<_TestToml>) -> Result<Self, Self::Error> {
  844. Ok(Self {
  845. test: value.test.take().map(Into::into),
  846. scripts: value
  847. .scripts
  848. .take()
  849. .ok_or_else(|| anyhow!("Missing 'scripts' section in Test.toml file."))?,
  850. })
  851. }
  852. }
  853. #[derive(Debug, Clone, Serialize, Deserialize)]
  854. pub struct GenesisEntry {
  855. // Base58 pubkey string.
  856. pub address: String,
  857. // Filepath to the compiled program to embed into the genesis.
  858. pub program: String,
  859. // Whether the genesis program is upgradeable.
  860. pub upgradeable: Option<bool>,
  861. }
  862. #[derive(Debug, Clone, Serialize, Deserialize)]
  863. pub struct CloneEntry {
  864. // Base58 pubkey string.
  865. pub address: String,
  866. }
  867. #[derive(Debug, Clone, Serialize, Deserialize)]
  868. pub struct AccountEntry {
  869. // Base58 pubkey string.
  870. pub address: String,
  871. // Name of JSON file containing the account data.
  872. pub filename: String,
  873. }
  874. #[derive(Debug, Clone, Serialize, Deserialize)]
  875. pub struct AccountDirEntry {
  876. // Directory containing account JSON files
  877. pub directory: String,
  878. }
  879. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  880. pub struct _Validator {
  881. // Load an account from the provided JSON file
  882. #[serde(skip_serializing_if = "Option::is_none")]
  883. pub account: Option<Vec<AccountEntry>>,
  884. // Load all the accounts from the JSON files found in the specified DIRECTORY
  885. #[serde(skip_serializing_if = "Option::is_none")]
  886. pub account_dir: Option<Vec<AccountDirEntry>>,
  887. // IP address to bind the validator ports. [default: 0.0.0.0]
  888. #[serde(skip_serializing_if = "Option::is_none")]
  889. pub bind_address: Option<String>,
  890. // Copy an account from the cluster referenced by the url argument.
  891. #[serde(skip_serializing_if = "Option::is_none")]
  892. pub clone: Option<Vec<CloneEntry>>,
  893. // Range to use for dynamically assigned ports. [default: 1024-65535]
  894. #[serde(skip_serializing_if = "Option::is_none")]
  895. pub dynamic_port_range: Option<String>,
  896. // Enable the faucet on this port [default: 9900].
  897. #[serde(skip_serializing_if = "Option::is_none")]
  898. pub faucet_port: Option<u16>,
  899. // Give the faucet address this much SOL in genesis. [default: 1000000]
  900. #[serde(skip_serializing_if = "Option::is_none")]
  901. pub faucet_sol: Option<String>,
  902. // Geyser plugin config location
  903. #[serde(skip_serializing_if = "Option::is_none")]
  904. pub geyser_plugin_config: Option<String>,
  905. // Gossip DNS name or IP address for the validator to advertise in gossip. [default: 127.0.0.1]
  906. #[serde(skip_serializing_if = "Option::is_none")]
  907. pub gossip_host: Option<String>,
  908. // Gossip port number for the validator
  909. #[serde(skip_serializing_if = "Option::is_none")]
  910. pub gossip_port: Option<u16>,
  911. // URL for Solana's JSON RPC or moniker.
  912. #[serde(skip_serializing_if = "Option::is_none")]
  913. pub url: Option<String>,
  914. // Use DIR as ledger location
  915. #[serde(skip_serializing_if = "Option::is_none")]
  916. pub ledger: Option<String>,
  917. // Keep this amount of shreds in root slots. [default: 10000]
  918. #[serde(skip_serializing_if = "Option::is_none")]
  919. pub limit_ledger_size: Option<String>,
  920. // Enable JSON RPC on this port, and the next port for the RPC websocket. [default: 8899]
  921. #[serde(skip_serializing_if = "Option::is_none")]
  922. pub rpc_port: Option<u16>,
  923. // Override the number of slots in an epoch.
  924. #[serde(skip_serializing_if = "Option::is_none")]
  925. pub slots_per_epoch: Option<String>,
  926. // The number of ticks in a slot
  927. #[serde(skip_serializing_if = "Option::is_none")]
  928. pub ticks_per_slot: Option<u16>,
  929. // Warp the ledger to WARP_SLOT after starting the validator.
  930. #[serde(skip_serializing_if = "Option::is_none")]
  931. pub warp_slot: Option<String>,
  932. }
  933. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  934. pub struct Validator {
  935. #[serde(skip_serializing_if = "Option::is_none")]
  936. pub account: Option<Vec<AccountEntry>>,
  937. #[serde(skip_serializing_if = "Option::is_none")]
  938. pub account_dir: Option<Vec<AccountDirEntry>>,
  939. pub bind_address: String,
  940. #[serde(skip_serializing_if = "Option::is_none")]
  941. pub clone: Option<Vec<CloneEntry>>,
  942. #[serde(skip_serializing_if = "Option::is_none")]
  943. pub dynamic_port_range: Option<String>,
  944. #[serde(skip_serializing_if = "Option::is_none")]
  945. pub faucet_port: Option<u16>,
  946. #[serde(skip_serializing_if = "Option::is_none")]
  947. pub faucet_sol: Option<String>,
  948. #[serde(skip_serializing_if = "Option::is_none")]
  949. pub geyser_plugin_config: Option<String>,
  950. #[serde(skip_serializing_if = "Option::is_none")]
  951. pub gossip_host: Option<String>,
  952. #[serde(skip_serializing_if = "Option::is_none")]
  953. pub gossip_port: Option<u16>,
  954. #[serde(skip_serializing_if = "Option::is_none")]
  955. pub url: Option<String>,
  956. pub ledger: String,
  957. #[serde(skip_serializing_if = "Option::is_none")]
  958. pub limit_ledger_size: Option<String>,
  959. pub rpc_port: u16,
  960. #[serde(skip_serializing_if = "Option::is_none")]
  961. pub slots_per_epoch: Option<String>,
  962. #[serde(skip_serializing_if = "Option::is_none")]
  963. pub ticks_per_slot: Option<u16>,
  964. #[serde(skip_serializing_if = "Option::is_none")]
  965. pub warp_slot: Option<String>,
  966. }
  967. impl From<_Validator> for Validator {
  968. fn from(_validator: _Validator) -> Self {
  969. Self {
  970. account: _validator.account,
  971. account_dir: _validator.account_dir,
  972. bind_address: _validator
  973. .bind_address
  974. .unwrap_or_else(|| DEFAULT_BIND_ADDRESS.to_string()),
  975. clone: _validator.clone,
  976. dynamic_port_range: _validator.dynamic_port_range,
  977. faucet_port: _validator.faucet_port,
  978. faucet_sol: _validator.faucet_sol,
  979. geyser_plugin_config: _validator.geyser_plugin_config,
  980. gossip_host: _validator.gossip_host,
  981. gossip_port: _validator.gossip_port,
  982. url: _validator.url,
  983. ledger: _validator
  984. .ledger
  985. .unwrap_or_else(|| DEFAULT_LEDGER_PATH.to_string()),
  986. limit_ledger_size: _validator.limit_ledger_size,
  987. rpc_port: _validator
  988. .rpc_port
  989. .unwrap_or(solana_sdk::rpc_port::DEFAULT_RPC_PORT),
  990. slots_per_epoch: _validator.slots_per_epoch,
  991. ticks_per_slot: _validator.ticks_per_slot,
  992. warp_slot: _validator.warp_slot,
  993. }
  994. }
  995. }
  996. impl From<Validator> for _Validator {
  997. fn from(validator: Validator) -> Self {
  998. Self {
  999. account: validator.account,
  1000. account_dir: validator.account_dir,
  1001. bind_address: Some(validator.bind_address),
  1002. clone: validator.clone,
  1003. dynamic_port_range: validator.dynamic_port_range,
  1004. faucet_port: validator.faucet_port,
  1005. faucet_sol: validator.faucet_sol,
  1006. geyser_plugin_config: validator.geyser_plugin_config,
  1007. gossip_host: validator.gossip_host,
  1008. gossip_port: validator.gossip_port,
  1009. url: validator.url,
  1010. ledger: Some(validator.ledger),
  1011. limit_ledger_size: validator.limit_ledger_size,
  1012. rpc_port: Some(validator.rpc_port),
  1013. slots_per_epoch: validator.slots_per_epoch,
  1014. ticks_per_slot: validator.ticks_per_slot,
  1015. warp_slot: validator.warp_slot,
  1016. }
  1017. }
  1018. }
  1019. const DEFAULT_LEDGER_PATH: &str = ".anchor/test-ledger";
  1020. const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0";
  1021. impl Merge for _Validator {
  1022. fn merge(&mut self, other: Self) {
  1023. // Instantiating a new Self object here ensures that
  1024. // this function will fail to compile if new fields get added
  1025. // to Self. This is useful as a reminder if they also require merging
  1026. *self = Self {
  1027. account: match self.account.take() {
  1028. None => other.account,
  1029. Some(mut entries) => match other.account {
  1030. None => Some(entries),
  1031. Some(other_entries) => {
  1032. for other_entry in other_entries {
  1033. match entries
  1034. .iter()
  1035. .position(|my_entry| *my_entry.address == other_entry.address)
  1036. {
  1037. None => entries.push(other_entry),
  1038. Some(i) => entries[i] = other_entry,
  1039. };
  1040. }
  1041. Some(entries)
  1042. }
  1043. },
  1044. },
  1045. account_dir: match self.account_dir.take() {
  1046. None => other.account_dir,
  1047. Some(mut entries) => match other.account_dir {
  1048. None => Some(entries),
  1049. Some(other_entries) => {
  1050. for other_entry in other_entries {
  1051. match entries
  1052. .iter()
  1053. .position(|my_entry| *my_entry.directory == other_entry.directory)
  1054. {
  1055. None => entries.push(other_entry),
  1056. Some(i) => entries[i] = other_entry,
  1057. };
  1058. }
  1059. Some(entries)
  1060. }
  1061. },
  1062. },
  1063. bind_address: other.bind_address.or_else(|| self.bind_address.take()),
  1064. clone: match self.clone.take() {
  1065. None => other.clone,
  1066. Some(mut entries) => match other.clone {
  1067. None => Some(entries),
  1068. Some(other_entries) => {
  1069. for other_entry in other_entries {
  1070. match entries
  1071. .iter()
  1072. .position(|my_entry| *my_entry.address == other_entry.address)
  1073. {
  1074. None => entries.push(other_entry),
  1075. Some(i) => entries[i] = other_entry,
  1076. };
  1077. }
  1078. Some(entries)
  1079. }
  1080. },
  1081. },
  1082. dynamic_port_range: other
  1083. .dynamic_port_range
  1084. .or_else(|| self.dynamic_port_range.take()),
  1085. faucet_port: other.faucet_port.or_else(|| self.faucet_port.take()),
  1086. faucet_sol: other.faucet_sol.or_else(|| self.faucet_sol.take()),
  1087. geyser_plugin_config: other
  1088. .geyser_plugin_config
  1089. .or_else(|| self.geyser_plugin_config.take()),
  1090. gossip_host: other.gossip_host.or_else(|| self.gossip_host.take()),
  1091. gossip_port: other.gossip_port.or_else(|| self.gossip_port.take()),
  1092. url: other.url.or_else(|| self.url.take()),
  1093. ledger: other.ledger.or_else(|| self.ledger.take()),
  1094. limit_ledger_size: other
  1095. .limit_ledger_size
  1096. .or_else(|| self.limit_ledger_size.take()),
  1097. rpc_port: other.rpc_port.or_else(|| self.rpc_port.take()),
  1098. slots_per_epoch: other
  1099. .slots_per_epoch
  1100. .or_else(|| self.slots_per_epoch.take()),
  1101. ticks_per_slot: other.ticks_per_slot.or_else(|| self.ticks_per_slot.take()),
  1102. warp_slot: other.warp_slot.or_else(|| self.warp_slot.take()),
  1103. };
  1104. }
  1105. }
  1106. #[derive(Debug, Clone)]
  1107. pub struct Program {
  1108. pub lib_name: String,
  1109. pub solidity: bool,
  1110. // Canonicalized path to the program directory or Solidity source file
  1111. pub path: PathBuf,
  1112. pub idl: Option<Idl>,
  1113. }
  1114. impl Program {
  1115. pub fn pubkey(&self) -> Result<Pubkey> {
  1116. self.keypair().map(|kp| kp.pubkey())
  1117. }
  1118. pub fn keypair(&self) -> Result<Keypair> {
  1119. let file = self.keypair_file()?;
  1120. solana_sdk::signature::read_keypair_file(file.path())
  1121. .map_err(|_| anyhow!("failed to read keypair for program: {}", self.lib_name))
  1122. }
  1123. // Lazily initializes the keypair file with a new key if it doesn't exist.
  1124. pub fn keypair_file(&self) -> Result<WithPath<File>> {
  1125. let deploy_dir_path = "target/deploy/";
  1126. fs::create_dir_all(deploy_dir_path)
  1127. .with_context(|| format!("Error creating directory with path: {deploy_dir_path}"))?;
  1128. let path = std::env::current_dir()
  1129. .expect("Must have current dir")
  1130. .join(format!("target/deploy/{}-keypair.json", self.lib_name));
  1131. if path.exists() {
  1132. return Ok(WithPath::new(
  1133. File::open(&path)
  1134. .with_context(|| format!("Error opening file with path: {}", path.display()))?,
  1135. path,
  1136. ));
  1137. }
  1138. let program_kp = Keypair::new();
  1139. let mut file = File::create(&path)
  1140. .with_context(|| format!("Error creating file with path: {}", path.display()))?;
  1141. file.write_all(format!("{:?}", &program_kp.to_bytes()).as_bytes())?;
  1142. Ok(WithPath::new(file, path))
  1143. }
  1144. pub fn binary_path(&self) -> PathBuf {
  1145. std::env::current_dir()
  1146. .expect("Must have current dir")
  1147. .join(format!("target/deploy/{}.so", self.lib_name))
  1148. }
  1149. }
  1150. #[derive(Debug, Default)]
  1151. pub struct ProgramDeployment {
  1152. pub address: Pubkey,
  1153. pub path: Option<String>,
  1154. pub idl: Option<String>,
  1155. }
  1156. impl TryFrom<_ProgramDeployment> for ProgramDeployment {
  1157. type Error = anyhow::Error;
  1158. fn try_from(pd: _ProgramDeployment) -> Result<Self, Self::Error> {
  1159. Ok(ProgramDeployment {
  1160. address: pd.address.parse()?,
  1161. path: pd.path,
  1162. idl: pd.idl,
  1163. })
  1164. }
  1165. }
  1166. #[derive(Debug, Default, Serialize, Deserialize)]
  1167. pub struct _ProgramDeployment {
  1168. pub address: String,
  1169. pub path: Option<String>,
  1170. pub idl: Option<String>,
  1171. }
  1172. impl From<&ProgramDeployment> for _ProgramDeployment {
  1173. fn from(pd: &ProgramDeployment) -> Self {
  1174. Self {
  1175. address: pd.address.to_string(),
  1176. path: pd.path.clone(),
  1177. idl: pd.idl.clone(),
  1178. }
  1179. }
  1180. }
  1181. pub struct ProgramWorkspace {
  1182. pub name: String,
  1183. pub program_id: Pubkey,
  1184. pub idl: Idl,
  1185. }
  1186. #[derive(Debug, Serialize, Deserialize)]
  1187. pub struct AnchorPackage {
  1188. pub name: String,
  1189. pub address: String,
  1190. pub idl: Option<String>,
  1191. }
  1192. impl AnchorPackage {
  1193. pub fn from(name: String, cfg: &WithPath<Config>) -> Result<Self> {
  1194. let cluster = &cfg.provider.cluster;
  1195. if cluster != &Cluster::Mainnet {
  1196. return Err(anyhow!("Publishing requires the mainnet cluster"));
  1197. }
  1198. let program_details = cfg
  1199. .programs
  1200. .get(cluster)
  1201. .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?
  1202. .get(&name)
  1203. .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?;
  1204. let idl = program_details.idl.clone();
  1205. let address = program_details.address.to_string();
  1206. Ok(Self { name, address, idl })
  1207. }
  1208. }
  1209. #[macro_export]
  1210. macro_rules! home_path {
  1211. ($my_struct:ident, $path:literal) => {
  1212. #[derive(Clone, Debug)]
  1213. pub struct $my_struct(String);
  1214. impl Default for $my_struct {
  1215. fn default() -> Self {
  1216. $my_struct(home_dir().unwrap().join($path).display().to_string())
  1217. }
  1218. }
  1219. impl $my_struct {
  1220. fn stringify_with_tilde(&self) -> String {
  1221. self.0
  1222. .replacen(home_dir().unwrap().to_str().unwrap(), "~", 1)
  1223. }
  1224. }
  1225. impl FromStr for $my_struct {
  1226. type Err = anyhow::Error;
  1227. fn from_str(s: &str) -> Result<Self, Self::Err> {
  1228. Ok(Self(s.to_owned()))
  1229. }
  1230. }
  1231. impl ToString for $my_struct {
  1232. fn to_string(&self) -> String {
  1233. self.0.clone()
  1234. }
  1235. }
  1236. };
  1237. }
  1238. home_path!(WalletPath, ".config/solana/id.json");
  1239. #[cfg(test)]
  1240. mod tests {
  1241. use super::*;
  1242. const BASE_CONFIG: &str = "
  1243. [provider]
  1244. cluster = \"localnet\"
  1245. wallet = \"id.json\"
  1246. ";
  1247. const CUSTOM_CONFIG: &str = "
  1248. [provider]
  1249. cluster = { http = \"http://my-url.com\", ws = \"ws://my-url.com\" }
  1250. wallet = \"id.json\"
  1251. ";
  1252. #[test]
  1253. fn parse_custom_cluster() {
  1254. let config = Config::from_str(CUSTOM_CONFIG).unwrap();
  1255. assert!(!config.features.skip_lint);
  1256. }
  1257. #[test]
  1258. fn parse_skip_lint_no_section() {
  1259. let config = Config::from_str(BASE_CONFIG).unwrap();
  1260. assert!(!config.features.skip_lint);
  1261. }
  1262. #[test]
  1263. fn parse_skip_lint_no_value() {
  1264. let string = BASE_CONFIG.to_owned() + "[features]";
  1265. let config = Config::from_str(&string).unwrap();
  1266. assert!(!config.features.skip_lint);
  1267. }
  1268. #[test]
  1269. fn parse_skip_lint_true() {
  1270. let string = BASE_CONFIG.to_owned() + "[features]\nskip-lint = true";
  1271. let config = Config::from_str(&string).unwrap();
  1272. assert!(config.features.skip_lint);
  1273. }
  1274. #[test]
  1275. fn parse_skip_lint_false() {
  1276. let string = BASE_CONFIG.to_owned() + "[features]\nskip-lint = false";
  1277. let config = Config::from_str(&string).unwrap();
  1278. assert!(!config.features.skip_lint);
  1279. }
  1280. }