config.rs 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109
  1. use crate::is_hidden;
  2. use anchor_client::Cluster;
  3. use anchor_syn::idl::Idl;
  4. use anyhow::{anyhow, Context, Error, Result};
  5. use clap::{ArgEnum, Parser};
  6. use heck::SnakeCase;
  7. use serde::{Deserialize, Serialize};
  8. use solana_cli_config::{Config as SolanaConfig, CONFIG_FILE};
  9. use solana_sdk::pubkey::Pubkey;
  10. use solana_sdk::signature::{Keypair, Signer};
  11. use std::collections::{BTreeMap, HashMap};
  12. use std::convert::TryFrom;
  13. use std::fs::{self, File};
  14. use std::io;
  15. use std::io::prelude::*;
  16. use std::ops::Deref;
  17. use std::path::Path;
  18. use std::path::PathBuf;
  19. use std::str::FromStr;
  20. use walkdir::WalkDir;
  21. pub trait Merge: Sized {
  22. fn merge(&mut self, _other: Self) {}
  23. }
  24. #[derive(Default, Debug, Parser)]
  25. pub struct ConfigOverride {
  26. /// Cluster override.
  27. #[clap(global = true, long = "provider.cluster")]
  28. pub cluster: Option<Cluster>,
  29. /// Wallet override.
  30. #[clap(global = true, long = "provider.wallet")]
  31. pub wallet: Option<WalletPath>,
  32. }
  33. #[derive(Debug)]
  34. pub struct WithPath<T> {
  35. inner: T,
  36. path: PathBuf,
  37. }
  38. impl<T> WithPath<T> {
  39. pub fn new(inner: T, path: PathBuf) -> Self {
  40. Self { inner, path }
  41. }
  42. pub fn path(&self) -> &PathBuf {
  43. &self.path
  44. }
  45. pub fn into_inner(self) -> T {
  46. self.inner
  47. }
  48. }
  49. impl<T> std::convert::AsRef<T> for WithPath<T> {
  50. fn as_ref(&self) -> &T {
  51. &self.inner
  52. }
  53. }
  54. #[derive(Debug, Clone, PartialEq)]
  55. pub struct Manifest(cargo_toml::Manifest);
  56. impl Manifest {
  57. pub fn from_path(p: impl AsRef<Path>) -> Result<Self> {
  58. cargo_toml::Manifest::from_path(&p)
  59. .map(Manifest)
  60. .map_err(anyhow::Error::from)
  61. .with_context(|| format!("Error reading manifest from path: {}", p.as_ref().display()))
  62. }
  63. pub fn lib_name(&self) -> Result<String> {
  64. if self.lib.is_some() && self.lib.as_ref().unwrap().name.is_some() {
  65. Ok(self
  66. .lib
  67. .as_ref()
  68. .unwrap()
  69. .name
  70. .as_ref()
  71. .unwrap()
  72. .to_string()
  73. .to_snake_case())
  74. } else {
  75. Ok(self
  76. .package
  77. .as_ref()
  78. .ok_or_else(|| anyhow!("package section not provided"))?
  79. .name
  80. .to_string()
  81. .to_snake_case())
  82. }
  83. }
  84. pub fn version(&self) -> String {
  85. match &self.package {
  86. Some(package) => package.version.to_string(),
  87. _ => "0.0.0".to_string(),
  88. }
  89. }
  90. // Climbs each parent directory from the current dir until we find a Cargo.toml
  91. pub fn discover() -> Result<Option<WithPath<Manifest>>> {
  92. Manifest::discover_from_path(std::env::current_dir()?)
  93. }
  94. // Climbs each parent directory from a given starting directory until we find a Cargo.toml.
  95. pub fn discover_from_path(start_from: PathBuf) -> Result<Option<WithPath<Manifest>>> {
  96. let mut cwd_opt = Some(start_from.as_path());
  97. while let Some(cwd) = cwd_opt {
  98. for f in fs::read_dir(cwd).with_context(|| {
  99. format!("Error reading the directory with path: {}", cwd.display())
  100. })? {
  101. let p = f
  102. .with_context(|| {
  103. format!("Error reading the directory with path: {}", cwd.display())
  104. })?
  105. .path();
  106. if let Some(filename) = p.file_name() {
  107. if filename.to_str() == Some("Cargo.toml") {
  108. let m = WithPath::new(Manifest::from_path(&p)?, p);
  109. return Ok(Some(m));
  110. }
  111. }
  112. }
  113. // Not found. Go up a directory level.
  114. cwd_opt = cwd.parent();
  115. }
  116. Ok(None)
  117. }
  118. }
  119. impl Deref for Manifest {
  120. type Target = cargo_toml::Manifest;
  121. fn deref(&self) -> &Self::Target {
  122. &self.0
  123. }
  124. }
  125. impl WithPath<Config> {
  126. pub fn get_program_list(&self) -> Result<Vec<PathBuf>> {
  127. // Canonicalize the workspace filepaths to compare with relative paths.
  128. let (members, exclude) = self.canonicalize_workspace()?;
  129. // Get all candidate programs.
  130. //
  131. // If [workspace.members] exists, then use that.
  132. // Otherwise, default to `programs/*`.
  133. let program_paths: Vec<PathBuf> = {
  134. if members.is_empty() {
  135. let path = self.path().parent().unwrap().join("programs");
  136. fs::read_dir(path)?
  137. .filter(|entry| entry.as_ref().map(|e| e.path().is_dir()).unwrap_or(false))
  138. .map(|dir| dir.map(|d| d.path().canonicalize().unwrap()))
  139. .collect::<Vec<Result<PathBuf, std::io::Error>>>()
  140. .into_iter()
  141. .collect::<Result<Vec<PathBuf>, std::io::Error>>()?
  142. } else {
  143. members
  144. }
  145. };
  146. // Filter out everything part of the exclude array.
  147. Ok(program_paths
  148. .into_iter()
  149. .filter(|m| !exclude.contains(m))
  150. .collect())
  151. }
  152. // TODO: this should read idl dir instead of parsing source.
  153. pub fn read_all_programs(&self) -> Result<Vec<Program>> {
  154. let mut r = vec![];
  155. for path in self.get_program_list()? {
  156. let cargo = Manifest::from_path(&path.join("Cargo.toml"))?;
  157. let lib_name = cargo.lib_name()?;
  158. let version = cargo.version();
  159. let idl = anchor_syn::idl::file::parse(
  160. path.join("src/lib.rs"),
  161. version,
  162. self.features.seeds,
  163. false,
  164. )?;
  165. r.push(Program {
  166. lib_name,
  167. path,
  168. idl,
  169. });
  170. }
  171. Ok(r)
  172. }
  173. pub fn canonicalize_workspace(&self) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
  174. let members = self
  175. .workspace
  176. .members
  177. .iter()
  178. .map(|m| {
  179. self.path()
  180. .parent()
  181. .unwrap()
  182. .join(m)
  183. .canonicalize()
  184. .unwrap_or_else(|_| {
  185. panic!("Error reading workspace.members. File {:?} does not exist at path {:?}.", m, self.path)
  186. })
  187. })
  188. .collect();
  189. let exclude = self
  190. .workspace
  191. .exclude
  192. .iter()
  193. .map(|m| {
  194. self.path()
  195. .parent()
  196. .unwrap()
  197. .join(m)
  198. .canonicalize()
  199. .unwrap_or_else(|_| {
  200. panic!("Error reading workspace.exclude. File {:?} does not exist at path {:?}.", m, self.path)
  201. })
  202. })
  203. .collect();
  204. Ok((members, exclude))
  205. }
  206. pub fn get_program(&self, name: &str) -> Result<Option<WithPath<Program>>> {
  207. for program in self.read_all_programs()? {
  208. let cargo_toml = program.path.join("Cargo.toml");
  209. if !cargo_toml.exists() {
  210. return Err(anyhow!(
  211. "Did not find Cargo.toml at the path: {}",
  212. program.path.display()
  213. ));
  214. }
  215. let p_lib_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
  216. if name == p_lib_name {
  217. let path = self
  218. .path()
  219. .parent()
  220. .unwrap()
  221. .canonicalize()?
  222. .join(&program.path);
  223. return Ok(Some(WithPath::new(program, path)));
  224. }
  225. }
  226. Ok(None)
  227. }
  228. }
  229. impl<T> std::ops::Deref for WithPath<T> {
  230. type Target = T;
  231. fn deref(&self) -> &Self::Target {
  232. &self.inner
  233. }
  234. }
  235. impl<T> std::ops::DerefMut for WithPath<T> {
  236. fn deref_mut(&mut self) -> &mut Self::Target {
  237. &mut self.inner
  238. }
  239. }
  240. #[derive(Debug, Default)]
  241. pub struct Config {
  242. pub anchor_version: Option<String>,
  243. pub solana_version: Option<String>,
  244. pub features: FeaturesConfig,
  245. pub registry: RegistryConfig,
  246. pub provider: ProviderConfig,
  247. pub programs: ProgramsConfig,
  248. pub scripts: ScriptsConfig,
  249. pub workspace: WorkspaceConfig,
  250. // Separate entry next to test_config because
  251. // "anchor localnet" only has access to the Anchor.toml,
  252. // not the Test.toml files
  253. pub test_validator: Option<TestValidator>,
  254. pub test_config: Option<TestConfig>,
  255. }
  256. #[derive(Default, Clone, Debug, Serialize, Deserialize)]
  257. pub struct FeaturesConfig {
  258. #[serde(default)]
  259. pub seeds: bool,
  260. }
  261. #[derive(Clone, Debug, Serialize, Deserialize)]
  262. pub struct RegistryConfig {
  263. pub url: String,
  264. }
  265. impl Default for RegistryConfig {
  266. fn default() -> Self {
  267. Self {
  268. url: "https://anchor.projectserum.com".to_string(),
  269. }
  270. }
  271. }
  272. #[derive(Debug, Default)]
  273. pub struct ProviderConfig {
  274. pub cluster: Cluster,
  275. pub wallet: WalletPath,
  276. }
  277. pub type ScriptsConfig = BTreeMap<String, String>;
  278. pub type ProgramsConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
  279. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  280. pub struct WorkspaceConfig {
  281. #[serde(default, skip_serializing_if = "Vec::is_empty")]
  282. pub members: Vec<String>,
  283. #[serde(default, skip_serializing_if = "Vec::is_empty")]
  284. pub exclude: Vec<String>,
  285. #[serde(default, skip_serializing_if = "String::is_empty")]
  286. pub types: String,
  287. }
  288. #[derive(ArgEnum, Parser, Clone, PartialEq, Debug)]
  289. pub enum BootstrapMode {
  290. None,
  291. Debian,
  292. }
  293. #[derive(Debug, Clone)]
  294. pub struct BuildConfig {
  295. pub verifiable: bool,
  296. pub solana_version: Option<String>,
  297. pub docker_image: String,
  298. pub bootstrap: BootstrapMode,
  299. }
  300. impl Config {
  301. fn with_test_config(mut self, p: impl AsRef<Path>) -> Result<Self> {
  302. self.test_config = TestConfig::discover(p)?;
  303. Ok(self)
  304. }
  305. pub fn docker(&self) -> String {
  306. let ver = self
  307. .anchor_version
  308. .clone()
  309. .unwrap_or_else(|| crate::DOCKER_BUILDER_VERSION.to_string());
  310. format!("projectserum/build:v{}", ver)
  311. }
  312. pub fn discover(cfg_override: &ConfigOverride) -> Result<Option<WithPath<Config>>> {
  313. Config::_discover().map(|opt| {
  314. opt.map(|mut cfg| {
  315. if let Some(cluster) = cfg_override.cluster.clone() {
  316. cfg.provider.cluster = cluster;
  317. }
  318. if let Some(wallet) = cfg_override.wallet.clone() {
  319. cfg.provider.wallet = wallet;
  320. }
  321. cfg
  322. })
  323. })
  324. }
  325. // Climbs each parent directory until we find an Anchor.toml.
  326. fn _discover() -> Result<Option<WithPath<Config>>> {
  327. let _cwd = std::env::current_dir()?;
  328. let mut cwd_opt = Some(_cwd.as_path());
  329. while let Some(cwd) = cwd_opt {
  330. for f in fs::read_dir(cwd).with_context(|| {
  331. format!("Error reading the directory with path: {}", cwd.display())
  332. })? {
  333. let p = f
  334. .with_context(|| {
  335. format!("Error reading the directory with path: {}", cwd.display())
  336. })?
  337. .path();
  338. if let Some(filename) = p.file_name() {
  339. if filename.to_str() == Some("Anchor.toml") {
  340. let cfg = Config::from_path(&p)?;
  341. return Ok(Some(WithPath::new(cfg, p)));
  342. }
  343. }
  344. }
  345. cwd_opt = cwd.parent();
  346. }
  347. Ok(None)
  348. }
  349. fn from_path(p: impl AsRef<Path>) -> Result<Self> {
  350. fs::read_to_string(&p)
  351. .with_context(|| format!("Error reading the file with path: {}", p.as_ref().display()))?
  352. .parse::<Self>()?
  353. .with_test_config(p.as_ref().parent().unwrap())
  354. }
  355. pub fn wallet_kp(&self) -> Result<Keypair> {
  356. solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string())
  357. .map_err(|_| anyhow!("Unable to read keypair file"))
  358. }
  359. }
  360. #[derive(Debug, Serialize, Deserialize)]
  361. struct _Config {
  362. anchor_version: Option<String>,
  363. solana_version: Option<String>,
  364. features: Option<FeaturesConfig>,
  365. programs: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
  366. registry: Option<RegistryConfig>,
  367. provider: Provider,
  368. workspace: Option<WorkspaceConfig>,
  369. scripts: Option<ScriptsConfig>,
  370. test: Option<_TestValidator>,
  371. }
  372. #[derive(Debug, Serialize, Deserialize)]
  373. struct Provider {
  374. cluster: String,
  375. wallet: String,
  376. }
  377. impl ToString for Config {
  378. fn to_string(&self) -> String {
  379. let programs = {
  380. let c = ser_programs(&self.programs);
  381. if c.is_empty() {
  382. None
  383. } else {
  384. Some(c)
  385. }
  386. };
  387. let cfg = _Config {
  388. anchor_version: self.anchor_version.clone(),
  389. solana_version: self.solana_version.clone(),
  390. features: Some(self.features.clone()),
  391. registry: Some(self.registry.clone()),
  392. provider: Provider {
  393. cluster: format!("{}", self.provider.cluster),
  394. wallet: self.provider.wallet.to_string(),
  395. },
  396. test: self.test_validator.clone().map(Into::into),
  397. scripts: match self.scripts.is_empty() {
  398. true => None,
  399. false => Some(self.scripts.clone()),
  400. },
  401. programs,
  402. workspace: (!self.workspace.members.is_empty() || !self.workspace.exclude.is_empty())
  403. .then(|| self.workspace.clone()),
  404. };
  405. toml::to_string(&cfg).expect("Must be well formed")
  406. }
  407. }
  408. impl FromStr for Config {
  409. type Err = Error;
  410. fn from_str(s: &str) -> Result<Self, Self::Err> {
  411. let cfg: _Config = toml::from_str(s)
  412. .map_err(|e| anyhow::format_err!("Unable to deserialize config: {}", e.to_string()))?;
  413. Ok(Config {
  414. anchor_version: cfg.anchor_version,
  415. solana_version: cfg.solana_version,
  416. features: cfg.features.unwrap_or_default(),
  417. registry: cfg.registry.unwrap_or_default(),
  418. provider: ProviderConfig {
  419. cluster: cfg.provider.cluster.parse()?,
  420. wallet: shellexpand::tilde(&cfg.provider.wallet).parse()?,
  421. },
  422. scripts: cfg.scripts.unwrap_or_default(),
  423. test_validator: cfg.test.map(Into::into),
  424. test_config: None,
  425. programs: cfg.programs.map_or(Ok(BTreeMap::new()), deser_programs)?,
  426. workspace: cfg.workspace.unwrap_or_default(),
  427. })
  428. }
  429. }
  430. pub fn get_solana_cfg_url() -> Result<String, io::Error> {
  431. let config_file = CONFIG_FILE.as_ref().ok_or_else(|| {
  432. io::Error::new(
  433. io::ErrorKind::NotFound,
  434. "Default Solana config was not found",
  435. )
  436. })?;
  437. SolanaConfig::load(config_file).map(|config| config.json_rpc_url)
  438. }
  439. fn ser_programs(
  440. programs: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
  441. ) -> BTreeMap<String, BTreeMap<String, serde_json::Value>> {
  442. programs
  443. .iter()
  444. .map(|(cluster, programs)| {
  445. let cluster = cluster.to_string();
  446. let programs = programs
  447. .iter()
  448. .map(|(name, deployment)| {
  449. (
  450. name.clone(),
  451. to_value(&_ProgramDeployment::from(deployment)),
  452. )
  453. })
  454. .collect::<BTreeMap<String, serde_json::Value>>();
  455. (cluster, programs)
  456. })
  457. .collect::<BTreeMap<String, BTreeMap<String, serde_json::Value>>>()
  458. }
  459. fn to_value(dep: &_ProgramDeployment) -> serde_json::Value {
  460. if dep.path.is_none() && dep.idl.is_none() {
  461. return serde_json::Value::String(dep.address.to_string());
  462. }
  463. serde_json::to_value(dep).unwrap()
  464. }
  465. fn deser_programs(
  466. programs: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
  467. ) -> Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>> {
  468. programs
  469. .iter()
  470. .map(|(cluster, programs)| {
  471. let cluster: Cluster = cluster.parse()?;
  472. let programs = programs
  473. .iter()
  474. .map(|(name, program_id)| {
  475. Ok((
  476. name.clone(),
  477. ProgramDeployment::try_from(match &program_id {
  478. serde_json::Value::String(address) => _ProgramDeployment {
  479. address: address.parse()?,
  480. path: None,
  481. idl: None,
  482. },
  483. serde_json::Value::Object(_) => {
  484. serde_json::from_value(program_id.clone())
  485. .map_err(|_| anyhow!("Unable to read toml"))?
  486. }
  487. _ => return Err(anyhow!("Invalid toml type")),
  488. })?,
  489. ))
  490. })
  491. .collect::<Result<BTreeMap<String, ProgramDeployment>>>()?;
  492. Ok((cluster, programs))
  493. })
  494. .collect::<Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>>>()
  495. }
  496. #[derive(Default, Debug, Clone, Serialize, Deserialize)]
  497. pub struct TestValidator {
  498. pub genesis: Option<Vec<GenesisEntry>>,
  499. pub validator: Option<Validator>,
  500. pub startup_wait: i32,
  501. pub shutdown_wait: i32,
  502. }
  503. #[derive(Default, Debug, Clone, Serialize, Deserialize)]
  504. pub struct _TestValidator {
  505. #[serde(skip_serializing_if = "Option::is_none")]
  506. pub genesis: Option<Vec<GenesisEntry>>,
  507. #[serde(skip_serializing_if = "Option::is_none")]
  508. pub validator: Option<_Validator>,
  509. #[serde(skip_serializing_if = "Option::is_none")]
  510. pub startup_wait: Option<i32>,
  511. #[serde(skip_serializing_if = "Option::is_none")]
  512. pub shutdown_wait: Option<i32>,
  513. }
  514. pub const STARTUP_WAIT: i32 = 5000;
  515. pub const SHUTDOWN_WAIT: i32 = 2000;
  516. impl From<_TestValidator> for TestValidator {
  517. fn from(_test_validator: _TestValidator) -> Self {
  518. Self {
  519. shutdown_wait: _test_validator.shutdown_wait.unwrap_or(SHUTDOWN_WAIT),
  520. startup_wait: _test_validator.startup_wait.unwrap_or(STARTUP_WAIT),
  521. genesis: _test_validator.genesis,
  522. validator: _test_validator.validator.map(Into::into),
  523. }
  524. }
  525. }
  526. impl From<TestValidator> for _TestValidator {
  527. fn from(test_validator: TestValidator) -> Self {
  528. Self {
  529. shutdown_wait: Some(test_validator.shutdown_wait),
  530. startup_wait: Some(test_validator.startup_wait),
  531. genesis: test_validator.genesis,
  532. validator: test_validator.validator.map(Into::into),
  533. }
  534. }
  535. }
  536. #[derive(Debug, Clone)]
  537. pub struct TestConfig {
  538. pub test_suite_configs: HashMap<PathBuf, TestToml>,
  539. }
  540. impl Deref for TestConfig {
  541. type Target = HashMap<PathBuf, TestToml>;
  542. fn deref(&self) -> &Self::Target {
  543. &self.test_suite_configs
  544. }
  545. }
  546. impl TestConfig {
  547. pub fn discover(root: impl AsRef<Path>) -> Result<Option<Self>> {
  548. let walker = WalkDir::new(root).into_iter();
  549. let mut test_suite_configs = HashMap::new();
  550. for entry in walker.filter_entry(|e| !is_hidden(e)) {
  551. let entry = entry?;
  552. if entry.file_name() == "Test.toml" {
  553. let test_toml = TestToml::from_path(entry.path())?;
  554. test_suite_configs.insert(entry.path().into(), test_toml);
  555. }
  556. }
  557. Ok(match test_suite_configs.is_empty() {
  558. true => None,
  559. false => Some(Self { test_suite_configs }),
  560. })
  561. }
  562. }
  563. // This file needs to have the same (sub)structure as Anchor.toml
  564. // so it can be parsed as a base test file from an Anchor.toml
  565. #[derive(Debug, Clone, Serialize, Deserialize)]
  566. pub struct _TestToml {
  567. pub extends: Option<Vec<String>>,
  568. pub test: Option<_TestValidator>,
  569. pub scripts: Option<ScriptsConfig>,
  570. }
  571. impl _TestToml {
  572. fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
  573. let s = fs::read_to_string(&path)?;
  574. let parsed_toml: Self = toml::from_str(&s)?;
  575. let mut current_toml = _TestToml {
  576. extends: None,
  577. test: None,
  578. scripts: None,
  579. };
  580. if let Some(bases) = &parsed_toml.extends {
  581. for base in bases {
  582. let mut canonical_base = base.clone();
  583. canonical_base = canonicalize_filepath_from_origin(&canonical_base, &path)?;
  584. current_toml.merge(_TestToml::from_path(&canonical_base)?);
  585. }
  586. }
  587. current_toml.merge(parsed_toml);
  588. if let Some(test) = &mut current_toml.test {
  589. if let Some(genesis_programs) = &mut test.genesis {
  590. for entry in genesis_programs {
  591. entry.program = canonicalize_filepath_from_origin(&entry.program, &path)?;
  592. }
  593. }
  594. if let Some(validator) = &mut test.validator {
  595. if let Some(ledger_dir) = &mut validator.ledger {
  596. *ledger_dir = canonicalize_filepath_from_origin(&ledger_dir, &path)?;
  597. }
  598. if let Some(accounts) = &mut validator.account {
  599. for entry in accounts {
  600. entry.filename = canonicalize_filepath_from_origin(&entry.filename, &path)?;
  601. }
  602. }
  603. }
  604. }
  605. Ok(current_toml)
  606. }
  607. }
  608. /// canonicalizes the `file_path` arg.
  609. /// uses the `path` arg as the current dir
  610. /// from which to turn the relative path
  611. /// into a canonical one
  612. fn canonicalize_filepath_from_origin(
  613. file_path: impl AsRef<Path>,
  614. path: impl AsRef<Path>,
  615. ) -> Result<String> {
  616. let previous_dir = std::env::current_dir()?;
  617. std::env::set_current_dir(path.as_ref().parent().unwrap())?;
  618. let result = fs::canonicalize(file_path)?.display().to_string();
  619. std::env::set_current_dir(previous_dir)?;
  620. Ok(result)
  621. }
  622. #[derive(Debug, Clone, Serialize, Deserialize)]
  623. pub struct TestToml {
  624. #[serde(skip_serializing_if = "Option::is_none")]
  625. pub test: Option<TestValidator>,
  626. pub scripts: ScriptsConfig,
  627. }
  628. impl TestToml {
  629. pub fn from_path(p: impl AsRef<Path>) -> Result<Self> {
  630. WithPath::new(_TestToml::from_path(&p)?, p.as_ref().into()).try_into()
  631. }
  632. }
  633. impl Merge for _TestToml {
  634. fn merge(&mut self, other: Self) {
  635. let mut my_scripts = self.scripts.take();
  636. match &mut my_scripts {
  637. None => my_scripts = other.scripts,
  638. Some(my_scripts) => {
  639. if let Some(other_scripts) = other.scripts {
  640. for (name, script) in other_scripts {
  641. my_scripts.insert(name, script);
  642. }
  643. }
  644. }
  645. }
  646. let mut my_test = self.test.take();
  647. match &mut my_test {
  648. Some(my_test) => {
  649. if let Some(other_test) = other.test {
  650. if let Some(startup_wait) = other_test.startup_wait {
  651. my_test.startup_wait = Some(startup_wait);
  652. }
  653. if let Some(other_genesis) = other_test.genesis {
  654. match &mut my_test.genesis {
  655. Some(my_genesis) => {
  656. for other_entry in other_genesis {
  657. match my_genesis
  658. .iter()
  659. .position(|g| *g.address == other_entry.address)
  660. {
  661. None => my_genesis.push(other_entry),
  662. Some(i) => my_genesis[i] = other_entry,
  663. }
  664. }
  665. }
  666. None => my_test.genesis = Some(other_genesis),
  667. }
  668. }
  669. let mut my_validator = my_test.validator.take();
  670. match &mut my_validator {
  671. None => my_validator = other_test.validator,
  672. Some(my_validator) => {
  673. if let Some(other_validator) = other_test.validator {
  674. my_validator.merge(other_validator)
  675. }
  676. }
  677. }
  678. my_test.validator = my_validator;
  679. }
  680. }
  681. None => my_test = other.test,
  682. };
  683. // Instantiating a new Self object here ensures that
  684. // this function will fail to compile if new fields get added
  685. // to Self. This is useful as a reminder if they also require merging
  686. *self = Self {
  687. test: my_test,
  688. scripts: my_scripts,
  689. extends: self.extends.take(),
  690. };
  691. }
  692. }
  693. impl TryFrom<WithPath<_TestToml>> for TestToml {
  694. type Error = Error;
  695. fn try_from(mut value: WithPath<_TestToml>) -> Result<Self, Self::Error> {
  696. Ok(Self {
  697. test: value.test.take().map(Into::into),
  698. scripts: value
  699. .scripts
  700. .take()
  701. .ok_or_else(|| anyhow!("Missing 'scripts' section in Test.toml file."))?,
  702. })
  703. }
  704. }
  705. #[derive(Debug, Clone, Serialize, Deserialize)]
  706. pub struct GenesisEntry {
  707. // Base58 pubkey string.
  708. pub address: String,
  709. // Filepath to the compiled program to embed into the genesis.
  710. pub program: String,
  711. }
  712. #[derive(Debug, Clone, Serialize, Deserialize)]
  713. pub struct CloneEntry {
  714. // Base58 pubkey string.
  715. pub address: String,
  716. }
  717. #[derive(Debug, Clone, Serialize, Deserialize)]
  718. pub struct AccountEntry {
  719. // Base58 pubkey string.
  720. pub address: String,
  721. // Name of JSON file containing the account data.
  722. pub filename: String,
  723. }
  724. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  725. pub struct _Validator {
  726. // Load an account from the provided JSON file
  727. #[serde(skip_serializing_if = "Option::is_none")]
  728. pub account: Option<Vec<AccountEntry>>,
  729. // IP address to bind the validator ports. [default: 0.0.0.0]
  730. #[serde(skip_serializing_if = "Option::is_none")]
  731. pub bind_address: Option<String>,
  732. // Copy an account from the cluster referenced by the url argument.
  733. #[serde(skip_serializing_if = "Option::is_none")]
  734. pub clone: Option<Vec<CloneEntry>>,
  735. // Range to use for dynamically assigned ports. [default: 1024-65535]
  736. #[serde(skip_serializing_if = "Option::is_none")]
  737. pub dynamic_port_range: Option<String>,
  738. // Enable the faucet on this port [default: 9900].
  739. #[serde(skip_serializing_if = "Option::is_none")]
  740. pub faucet_port: Option<u16>,
  741. // Give the faucet address this much SOL in genesis. [default: 1000000]
  742. #[serde(skip_serializing_if = "Option::is_none")]
  743. pub faucet_sol: Option<String>,
  744. // Gossip DNS name or IP address for the validator to advertise in gossip. [default: 127.0.0.1]
  745. #[serde(skip_serializing_if = "Option::is_none")]
  746. pub gossip_host: Option<String>,
  747. // Gossip port number for the validator
  748. #[serde(skip_serializing_if = "Option::is_none")]
  749. pub gossip_port: Option<u16>,
  750. // URL for Solana's JSON RPC or moniker.
  751. #[serde(skip_serializing_if = "Option::is_none")]
  752. pub url: Option<String>,
  753. // Use DIR as ledger location
  754. #[serde(skip_serializing_if = "Option::is_none")]
  755. pub ledger: Option<String>,
  756. // Keep this amount of shreds in root slots. [default: 10000]
  757. #[serde(skip_serializing_if = "Option::is_none")]
  758. pub limit_ledger_size: Option<String>,
  759. // Enable JSON RPC on this port, and the next port for the RPC websocket. [default: 8899]
  760. #[serde(skip_serializing_if = "Option::is_none")]
  761. pub rpc_port: Option<u16>,
  762. // Override the number of slots in an epoch.
  763. #[serde(skip_serializing_if = "Option::is_none")]
  764. pub slots_per_epoch: Option<String>,
  765. // Warp the ledger to WARP_SLOT after starting the validator.
  766. #[serde(skip_serializing_if = "Option::is_none")]
  767. pub warp_slot: Option<String>,
  768. }
  769. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  770. pub struct Validator {
  771. #[serde(skip_serializing_if = "Option::is_none")]
  772. pub account: Option<Vec<AccountEntry>>,
  773. pub bind_address: String,
  774. #[serde(skip_serializing_if = "Option::is_none")]
  775. pub clone: Option<Vec<CloneEntry>>,
  776. #[serde(skip_serializing_if = "Option::is_none")]
  777. pub dynamic_port_range: Option<String>,
  778. #[serde(skip_serializing_if = "Option::is_none")]
  779. pub faucet_port: Option<u16>,
  780. #[serde(skip_serializing_if = "Option::is_none")]
  781. pub faucet_sol: Option<String>,
  782. #[serde(skip_serializing_if = "Option::is_none")]
  783. pub gossip_host: Option<String>,
  784. #[serde(skip_serializing_if = "Option::is_none")]
  785. pub gossip_port: Option<u16>,
  786. #[serde(skip_serializing_if = "Option::is_none")]
  787. pub url: Option<String>,
  788. pub ledger: String,
  789. #[serde(skip_serializing_if = "Option::is_none")]
  790. pub limit_ledger_size: Option<String>,
  791. pub rpc_port: u16,
  792. #[serde(skip_serializing_if = "Option::is_none")]
  793. pub slots_per_epoch: Option<String>,
  794. #[serde(skip_serializing_if = "Option::is_none")]
  795. pub warp_slot: Option<String>,
  796. }
  797. impl From<_Validator> for Validator {
  798. fn from(_validator: _Validator) -> Self {
  799. Self {
  800. account: _validator.account,
  801. bind_address: _validator
  802. .bind_address
  803. .unwrap_or_else(|| DEFAULT_BIND_ADDRESS.to_string()),
  804. clone: _validator.clone,
  805. dynamic_port_range: _validator.dynamic_port_range,
  806. faucet_port: _validator.faucet_port,
  807. faucet_sol: _validator.faucet_sol,
  808. gossip_host: _validator.gossip_host,
  809. gossip_port: _validator.gossip_port,
  810. url: _validator.url,
  811. ledger: _validator
  812. .ledger
  813. .unwrap_or_else(|| DEFAULT_LEDGER_PATH.to_string()),
  814. limit_ledger_size: _validator.limit_ledger_size,
  815. rpc_port: _validator
  816. .rpc_port
  817. .unwrap_or(solana_sdk::rpc_port::DEFAULT_RPC_PORT),
  818. slots_per_epoch: _validator.slots_per_epoch,
  819. warp_slot: _validator.warp_slot,
  820. }
  821. }
  822. }
  823. impl From<Validator> for _Validator {
  824. fn from(validator: Validator) -> Self {
  825. Self {
  826. account: validator.account,
  827. bind_address: Some(validator.bind_address),
  828. clone: validator.clone,
  829. dynamic_port_range: validator.dynamic_port_range,
  830. faucet_port: validator.faucet_port,
  831. faucet_sol: validator.faucet_sol,
  832. gossip_host: validator.gossip_host,
  833. gossip_port: validator.gossip_port,
  834. url: validator.url,
  835. ledger: Some(validator.ledger),
  836. limit_ledger_size: validator.limit_ledger_size,
  837. rpc_port: Some(validator.rpc_port),
  838. slots_per_epoch: validator.slots_per_epoch,
  839. warp_slot: validator.warp_slot,
  840. }
  841. }
  842. }
  843. const DEFAULT_LEDGER_PATH: &str = ".anchor/test-ledger";
  844. const DEFAULT_BIND_ADDRESS: &str = "0.0.0.0";
  845. impl Merge for _Validator {
  846. fn merge(&mut self, other: Self) {
  847. // Instantiating a new Self object here ensures that
  848. // this function will fail to compile if new fields get added
  849. // to Self. This is useful as a reminder if they also require merging
  850. *self = Self {
  851. account: match self.account.take() {
  852. None => other.account,
  853. Some(mut entries) => match other.account {
  854. None => Some(entries),
  855. Some(other_entries) => {
  856. for other_entry in other_entries {
  857. match entries
  858. .iter()
  859. .position(|my_entry| *my_entry.address == other_entry.address)
  860. {
  861. None => entries.push(other_entry),
  862. Some(i) => entries[i] = other_entry,
  863. };
  864. }
  865. Some(entries)
  866. }
  867. },
  868. },
  869. bind_address: other.bind_address.or_else(|| self.bind_address.take()),
  870. clone: match self.clone.take() {
  871. None => other.clone,
  872. Some(mut entries) => match other.clone {
  873. None => Some(entries),
  874. Some(other_entries) => {
  875. for other_entry in other_entries {
  876. match entries
  877. .iter()
  878. .position(|my_entry| *my_entry.address == other_entry.address)
  879. {
  880. None => entries.push(other_entry),
  881. Some(i) => entries[i] = other_entry,
  882. };
  883. }
  884. Some(entries)
  885. }
  886. },
  887. },
  888. dynamic_port_range: other
  889. .dynamic_port_range
  890. .or_else(|| self.dynamic_port_range.take()),
  891. faucet_port: other.faucet_port.or_else(|| self.faucet_port.take()),
  892. faucet_sol: other.faucet_sol.or_else(|| self.faucet_sol.take()),
  893. gossip_host: other.gossip_host.or_else(|| self.gossip_host.take()),
  894. gossip_port: other.gossip_port.or_else(|| self.gossip_port.take()),
  895. url: other.url.or_else(|| self.url.take()),
  896. ledger: other.ledger.or_else(|| self.ledger.take()),
  897. limit_ledger_size: other
  898. .limit_ledger_size
  899. .or_else(|| self.limit_ledger_size.take()),
  900. rpc_port: other.rpc_port.or_else(|| self.rpc_port.take()),
  901. slots_per_epoch: other
  902. .slots_per_epoch
  903. .or_else(|| self.slots_per_epoch.take()),
  904. warp_slot: other.warp_slot.or_else(|| self.warp_slot.take()),
  905. };
  906. }
  907. }
  908. #[derive(Debug, Clone)]
  909. pub struct Program {
  910. pub lib_name: String,
  911. // Canonicalized path to the program directory.
  912. pub path: PathBuf,
  913. pub idl: Option<Idl>,
  914. }
  915. impl Program {
  916. pub fn pubkey(&self) -> Result<Pubkey> {
  917. self.keypair().map(|kp| kp.pubkey())
  918. }
  919. pub fn keypair(&self) -> Result<Keypair> {
  920. let file = self.keypair_file()?;
  921. solana_sdk::signature::read_keypair_file(file.path())
  922. .map_err(|_| anyhow!("failed to read keypair for program: {}", self.lib_name))
  923. }
  924. // Lazily initializes the keypair file with a new key if it doesn't exist.
  925. pub fn keypair_file(&self) -> Result<WithPath<File>> {
  926. let deploy_dir_path = "target/deploy/";
  927. fs::create_dir_all(deploy_dir_path)
  928. .with_context(|| format!("Error creating directory with path: {}", deploy_dir_path))?;
  929. let path = std::env::current_dir()
  930. .expect("Must have current dir")
  931. .join(format!("target/deploy/{}-keypair.json", self.lib_name));
  932. if path.exists() {
  933. return Ok(WithPath::new(
  934. File::open(&path)
  935. .with_context(|| format!("Error opening file with path: {}", path.display()))?,
  936. path,
  937. ));
  938. }
  939. let program_kp = Keypair::generate(&mut rand::rngs::OsRng);
  940. let mut file = File::create(&path)
  941. .with_context(|| format!("Error creating file with path: {}", path.display()))?;
  942. file.write_all(format!("{:?}", &program_kp.to_bytes()).as_bytes())?;
  943. Ok(WithPath::new(file, path))
  944. }
  945. pub fn binary_path(&self) -> PathBuf {
  946. std::env::current_dir()
  947. .expect("Must have current dir")
  948. .join(format!("target/deploy/{}.so", self.lib_name))
  949. }
  950. }
  951. #[derive(Debug, Default)]
  952. pub struct ProgramDeployment {
  953. pub address: Pubkey,
  954. pub path: Option<String>,
  955. pub idl: Option<String>,
  956. }
  957. impl TryFrom<_ProgramDeployment> for ProgramDeployment {
  958. type Error = anyhow::Error;
  959. fn try_from(pd: _ProgramDeployment) -> Result<Self, Self::Error> {
  960. Ok(ProgramDeployment {
  961. address: pd.address.parse()?,
  962. path: pd.path,
  963. idl: pd.idl,
  964. })
  965. }
  966. }
  967. #[derive(Debug, Default, Serialize, Deserialize)]
  968. pub struct _ProgramDeployment {
  969. pub address: String,
  970. pub path: Option<String>,
  971. pub idl: Option<String>,
  972. }
  973. impl From<&ProgramDeployment> for _ProgramDeployment {
  974. fn from(pd: &ProgramDeployment) -> Self {
  975. Self {
  976. address: pd.address.to_string(),
  977. path: pd.path.clone(),
  978. idl: pd.idl.clone(),
  979. }
  980. }
  981. }
  982. pub struct ProgramWorkspace {
  983. pub name: String,
  984. pub program_id: Pubkey,
  985. pub idl: Idl,
  986. }
  987. #[derive(Debug, Serialize, Deserialize)]
  988. pub struct AnchorPackage {
  989. pub name: String,
  990. pub address: String,
  991. pub idl: Option<String>,
  992. }
  993. impl AnchorPackage {
  994. pub fn from(name: String, cfg: &WithPath<Config>) -> Result<Self> {
  995. let cluster = &cfg.provider.cluster;
  996. if cluster != &Cluster::Mainnet {
  997. return Err(anyhow!("Publishing requires the mainnet cluster"));
  998. }
  999. let program_details = cfg
  1000. .programs
  1001. .get(cluster)
  1002. .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?
  1003. .get(&name)
  1004. .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?;
  1005. let idl = program_details.idl.clone();
  1006. let address = program_details.address.to_string();
  1007. Ok(Self { name, address, idl })
  1008. }
  1009. }
  1010. crate::home_path!(WalletPath, ".config/solana/id.json");