config.rs 49 KB

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