config.rs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. use crate::ConfigOverride;
  2. use anchor_client::Cluster;
  3. use anchor_syn::idl::Idl;
  4. use anyhow::{anyhow, Error, Result};
  5. use serde::{Deserialize, Serialize};
  6. use solana_sdk::pubkey::Pubkey;
  7. use solana_sdk::signature::Keypair;
  8. use std::collections::BTreeMap;
  9. use std::fs::{self, File};
  10. use std::io::prelude::*;
  11. use std::path::Path;
  12. use std::path::PathBuf;
  13. use std::str::FromStr;
  14. #[derive(Debug, Default)]
  15. pub struct Config {
  16. pub provider: ProviderConfig,
  17. pub clusters: ClustersConfig,
  18. pub test: Option<Test>,
  19. }
  20. #[derive(Debug, Default)]
  21. pub struct ProviderConfig {
  22. pub cluster: Cluster,
  23. pub wallet: WalletPath,
  24. }
  25. pub type ClustersConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
  26. impl Config {
  27. pub fn discover(
  28. cfg_override: &ConfigOverride,
  29. ) -> Result<Option<(Self, PathBuf, Option<PathBuf>)>> {
  30. Config::_discover().map(|opt| {
  31. opt.map(|(mut cfg, cfg_path, cargo_toml)| {
  32. if let Some(cluster) = cfg_override.cluster.clone() {
  33. cfg.provider.cluster = cluster;
  34. }
  35. if let Some(wallet) = cfg_override.wallet.clone() {
  36. cfg.provider.wallet = wallet;
  37. }
  38. (cfg, cfg_path, cargo_toml)
  39. })
  40. })
  41. }
  42. // Searches all parent directories for an Anchor.toml file.
  43. fn _discover() -> Result<Option<(Self, PathBuf, Option<PathBuf>)>> {
  44. // Set to true if we ever see a Cargo.toml file when traversing the
  45. // parent directories.
  46. let mut cargo_toml = None;
  47. let _cwd = std::env::current_dir()?;
  48. let mut cwd_opt = Some(_cwd.as_path());
  49. while let Some(cwd) = cwd_opt {
  50. let files = fs::read_dir(cwd)?;
  51. // Cargo.toml file for this directory level.
  52. let mut cargo_toml_level = None;
  53. let mut anchor_toml = None;
  54. for f in files {
  55. let p = f?.path();
  56. if let Some(filename) = p.file_name() {
  57. if filename.to_str() == Some("Cargo.toml") {
  58. cargo_toml_level = Some(p);
  59. } else if filename.to_str() == Some("Anchor.toml") {
  60. let mut cfg_file = File::open(&p)?;
  61. let mut cfg_contents = String::new();
  62. cfg_file.read_to_string(&mut cfg_contents)?;
  63. let cfg = cfg_contents.parse()?;
  64. anchor_toml = Some((cfg, p));
  65. }
  66. }
  67. }
  68. if let Some((cfg, parent)) = anchor_toml {
  69. return Ok(Some((cfg, parent, cargo_toml)));
  70. }
  71. if cargo_toml.is_none() {
  72. cargo_toml = cargo_toml_level;
  73. }
  74. cwd_opt = cwd.parent();
  75. }
  76. Ok(None)
  77. }
  78. pub fn wallet_kp(&self) -> Result<Keypair> {
  79. solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string())
  80. .map_err(|_| anyhow!("Unable to read keypair file"))
  81. }
  82. }
  83. // Pubkey serializes as a byte array so use this type a hack to serialize
  84. // into base 58 strings.
  85. #[derive(Debug, Serialize, Deserialize)]
  86. struct _Config {
  87. provider: Provider,
  88. test: Option<Test>,
  89. clusters: Option<BTreeMap<String, BTreeMap<String, String>>>,
  90. }
  91. #[derive(Debug, Serialize, Deserialize)]
  92. struct Provider {
  93. cluster: String,
  94. wallet: String,
  95. }
  96. impl ToString for Config {
  97. fn to_string(&self) -> String {
  98. let clusters = {
  99. let c = ser_clusters(&self.clusters);
  100. if c.len() == 0 {
  101. None
  102. } else {
  103. Some(c)
  104. }
  105. };
  106. let cfg = _Config {
  107. provider: Provider {
  108. cluster: format!("{}", self.provider.cluster),
  109. wallet: self.provider.wallet.to_string(),
  110. },
  111. test: self.test.clone(),
  112. clusters,
  113. };
  114. toml::to_string(&cfg).expect("Must be well formed")
  115. }
  116. }
  117. impl FromStr for Config {
  118. type Err = Error;
  119. fn from_str(s: &str) -> Result<Self, Self::Err> {
  120. let cfg: _Config = toml::from_str(s)
  121. .map_err(|e| anyhow::format_err!("Unable to deserialize config: {}", e.to_string()))?;
  122. Ok(Config {
  123. provider: ProviderConfig {
  124. cluster: cfg.provider.cluster.parse()?,
  125. wallet: shellexpand::tilde(&cfg.provider.wallet).parse()?,
  126. },
  127. test: cfg.test,
  128. clusters: cfg
  129. .clusters
  130. .map_or(Ok(BTreeMap::new()), |c| deser_clusters(c))?,
  131. })
  132. }
  133. }
  134. fn ser_clusters(
  135. clusters: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
  136. ) -> BTreeMap<String, BTreeMap<String, String>> {
  137. clusters
  138. .iter()
  139. .map(|(cluster, programs)| {
  140. let cluster = cluster.to_string();
  141. let programs = programs
  142. .iter()
  143. .map(|(name, deployment)| (name.clone(), deployment.program_id.to_string()))
  144. .collect::<BTreeMap<String, String>>();
  145. (cluster, programs)
  146. })
  147. .collect::<BTreeMap<String, BTreeMap<String, String>>>()
  148. }
  149. fn deser_clusters(
  150. clusters: BTreeMap<String, BTreeMap<String, String>>,
  151. ) -> Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>> {
  152. clusters
  153. .iter()
  154. .map(|(cluster, programs)| {
  155. let cluster: Cluster = cluster.parse()?;
  156. let programs = programs
  157. .iter()
  158. .map(|(name, program_id)| {
  159. Ok((
  160. name.clone(),
  161. ProgramDeployment {
  162. name: name.clone(),
  163. program_id: program_id.parse()?,
  164. },
  165. ))
  166. })
  167. .collect::<Result<BTreeMap<String, ProgramDeployment>>>()?;
  168. Ok((cluster, programs))
  169. })
  170. .collect::<Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>>>()
  171. }
  172. #[derive(Debug, Clone, Serialize, Deserialize)]
  173. pub struct Test {
  174. pub genesis: Vec<GenesisEntry>,
  175. }
  176. #[derive(Debug, Clone, Serialize, Deserialize)]
  177. pub struct GenesisEntry {
  178. // Base58 pubkey string.
  179. pub address: String,
  180. // Filepath to the compiled program to embed into the genesis.
  181. pub program: String,
  182. }
  183. // TODO: this should read idl dir instead of parsing source.
  184. pub fn read_all_programs() -> Result<Vec<Program>> {
  185. let files = fs::read_dir("programs")?;
  186. let mut r = vec![];
  187. for f in files {
  188. let path = f?.path();
  189. let idl = anchor_syn::parser::file::parse(path.join("src/lib.rs"))?;
  190. let lib_name = extract_lib_name(&path.join("Cargo.toml"))?;
  191. r.push(Program {
  192. lib_name,
  193. path,
  194. idl,
  195. });
  196. }
  197. Ok(r)
  198. }
  199. pub fn extract_lib_name(path: impl AsRef<Path>) -> Result<String> {
  200. let mut toml = File::open(path)?;
  201. let mut contents = String::new();
  202. toml.read_to_string(&mut contents)?;
  203. let cargo_toml: toml::Value = contents.parse()?;
  204. match cargo_toml {
  205. toml::Value::Table(t) => match t.get("lib") {
  206. None => Err(anyhow!("lib not found in Cargo.toml")),
  207. Some(lib) => match lib
  208. .get("name")
  209. .ok_or_else(|| anyhow!("lib name not found in Cargo.toml"))?
  210. {
  211. toml::Value::String(n) => Ok(n.to_string()),
  212. _ => Err(anyhow!("lib name must be a string")),
  213. },
  214. },
  215. _ => Err(anyhow!("Invalid Cargo.toml")),
  216. }
  217. }
  218. #[derive(Debug, Clone)]
  219. pub struct Program {
  220. pub lib_name: String,
  221. pub path: PathBuf,
  222. pub idl: Idl,
  223. }
  224. impl Program {
  225. pub fn anchor_keypair_path(&self) -> PathBuf {
  226. std::env::current_dir()
  227. .expect("Must have current dir")
  228. .join(format!(
  229. "target/deploy/anchor-{}-keypair.json",
  230. self.lib_name
  231. ))
  232. }
  233. pub fn binary_path(&self) -> PathBuf {
  234. std::env::current_dir()
  235. .expect("Must have current dir")
  236. .join(format!("target/deploy/{}.so", self.lib_name))
  237. }
  238. }
  239. #[derive(Debug, Default)]
  240. pub struct ProgramDeployment {
  241. pub name: String,
  242. pub program_id: Pubkey,
  243. }
  244. pub struct ProgramWorkspace {
  245. pub name: String,
  246. pub program_id: Pubkey,
  247. pub idl: Idl,
  248. }
  249. serum_common::home_path!(WalletPath, ".config/solana/id.json");