config.rs 9.9 KB

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