config.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. use anchor_client::Cluster;
  2. use anchor_syn::idl::Idl;
  3. use anyhow::{anyhow, Context, Error, Result};
  4. use clap::{ArgEnum, Parser};
  5. use heck::SnakeCase;
  6. use serde::{Deserialize, Serialize};
  7. use solana_cli_config::{Config as SolanaConfig, CONFIG_FILE};
  8. use solana_sdk::pubkey::Pubkey;
  9. use solana_sdk::signature::{Keypair, Signer};
  10. use std::collections::BTreeMap;
  11. use std::convert::TryFrom;
  12. use std::fs::{self, File};
  13. use std::io;
  14. use std::io::prelude::*;
  15. use std::ops::Deref;
  16. use std::path::Path;
  17. use std::path::PathBuf;
  18. use std::str::FromStr;
  19. #[derive(Default, Debug, Parser)]
  20. pub struct ConfigOverride {
  21. /// Cluster override.
  22. #[clap(global = true, long = "provider.cluster")]
  23. pub cluster: Option<Cluster>,
  24. /// Wallet override.
  25. #[clap(global = true, long = "provider.wallet")]
  26. pub wallet: Option<WalletPath>,
  27. }
  28. pub struct WithPath<T> {
  29. inner: T,
  30. path: PathBuf,
  31. }
  32. impl<T> WithPath<T> {
  33. pub fn new(inner: T, path: PathBuf) -> Self {
  34. Self { inner, path }
  35. }
  36. pub fn path(&self) -> &PathBuf {
  37. &self.path
  38. }
  39. pub fn into_inner(self) -> T {
  40. self.inner
  41. }
  42. }
  43. impl<T> std::convert::AsRef<T> for WithPath<T> {
  44. fn as_ref(&self) -> &T {
  45. &self.inner
  46. }
  47. }
  48. #[derive(Debug, Clone, PartialEq)]
  49. pub struct Manifest(cargo_toml::Manifest);
  50. impl Manifest {
  51. pub fn from_path(p: impl AsRef<Path>) -> Result<Self> {
  52. cargo_toml::Manifest::from_path(&p)
  53. .map(Manifest)
  54. .map_err(anyhow::Error::from)
  55. .with_context(|| format!("Error reading manifest from path: {}", p.as_ref().display()))
  56. }
  57. pub fn lib_name(&self) -> Result<String> {
  58. if self.lib.is_some() && self.lib.as_ref().unwrap().name.is_some() {
  59. Ok(self
  60. .lib
  61. .as_ref()
  62. .unwrap()
  63. .name
  64. .as_ref()
  65. .unwrap()
  66. .to_string()
  67. .to_snake_case())
  68. } else {
  69. Ok(self
  70. .package
  71. .as_ref()
  72. .ok_or_else(|| anyhow!("package section not provided"))?
  73. .name
  74. .to_string()
  75. .to_snake_case())
  76. }
  77. }
  78. pub fn version(&self) -> String {
  79. match &self.package {
  80. Some(package) => package.version.to_string(),
  81. _ => "0.0.0".to_string(),
  82. }
  83. }
  84. // Climbs each parent directory from the current dir until we find a Cargo.toml
  85. pub fn discover() -> Result<Option<WithPath<Manifest>>> {
  86. Manifest::discover_from_path(std::env::current_dir()?)
  87. }
  88. // Climbs each parent directory from a given starting directory until we find a Cargo.toml.
  89. pub fn discover_from_path(start_from: PathBuf) -> Result<Option<WithPath<Manifest>>> {
  90. let mut cwd_opt = Some(start_from.as_path());
  91. while let Some(cwd) = cwd_opt {
  92. for f in fs::read_dir(cwd).with_context(|| {
  93. format!("Error reading the directory with path: {}", cwd.display())
  94. })? {
  95. let p = f
  96. .with_context(|| {
  97. format!("Error reading the directory with path: {}", cwd.display())
  98. })?
  99. .path();
  100. if let Some(filename) = p.file_name() {
  101. if filename.to_str() == Some("Cargo.toml") {
  102. let m = WithPath::new(Manifest::from_path(&p)?, p);
  103. return Ok(Some(m));
  104. }
  105. }
  106. }
  107. // Not found. Go up a directory level.
  108. cwd_opt = cwd.parent();
  109. }
  110. Ok(None)
  111. }
  112. }
  113. impl Deref for Manifest {
  114. type Target = cargo_toml::Manifest;
  115. fn deref(&self) -> &Self::Target {
  116. &self.0
  117. }
  118. }
  119. impl WithPath<Config> {
  120. pub fn get_program_list(&self) -> Result<Vec<PathBuf>> {
  121. // Canonicalize the workspace filepaths to compare with relative paths.
  122. let (members, exclude) = self.canonicalize_workspace()?;
  123. // Get all candidate programs.
  124. //
  125. // If [workspace.members] exists, then use that.
  126. // Otherwise, default to `programs/*`.
  127. let program_paths: Vec<PathBuf> = {
  128. if members.is_empty() {
  129. let path = self.path().parent().unwrap().join("programs");
  130. fs::read_dir(path)?
  131. .filter(|entry| entry.as_ref().map(|e| e.path().is_dir()).unwrap_or(false))
  132. .map(|dir| dir.map(|d| d.path().canonicalize().unwrap()))
  133. .collect::<Vec<Result<PathBuf, std::io::Error>>>()
  134. .into_iter()
  135. .collect::<Result<Vec<PathBuf>, std::io::Error>>()?
  136. } else {
  137. members
  138. }
  139. };
  140. // Filter out everything part of the exclude array.
  141. Ok(program_paths
  142. .into_iter()
  143. .filter(|m| !exclude.contains(m))
  144. .collect())
  145. }
  146. // TODO: this should read idl dir instead of parsing source.
  147. pub fn read_all_programs(&self) -> Result<Vec<Program>> {
  148. let mut r = vec![];
  149. for path in self.get_program_list()? {
  150. let cargo = Manifest::from_path(&path.join("Cargo.toml"))?;
  151. let lib_name = cargo.lib_name()?;
  152. let version = cargo.version();
  153. let idl = anchor_syn::idl::file::parse(
  154. path.join("src/lib.rs"),
  155. version,
  156. self.features.seeds,
  157. false,
  158. )?;
  159. r.push(Program {
  160. lib_name,
  161. path,
  162. idl,
  163. });
  164. }
  165. Ok(r)
  166. }
  167. pub fn canonicalize_workspace(&self) -> Result<(Vec<PathBuf>, Vec<PathBuf>)> {
  168. let members = self
  169. .workspace
  170. .members
  171. .iter()
  172. .map(|m| {
  173. self.path()
  174. .parent()
  175. .unwrap()
  176. .join(m)
  177. .canonicalize()
  178. .unwrap()
  179. })
  180. .collect();
  181. let exclude = self
  182. .workspace
  183. .exclude
  184. .iter()
  185. .map(|m| {
  186. self.path()
  187. .parent()
  188. .unwrap()
  189. .join(m)
  190. .canonicalize()
  191. .unwrap()
  192. })
  193. .collect();
  194. Ok((members, exclude))
  195. }
  196. pub fn get_program(&self, name: &str) -> Result<Option<WithPath<Program>>> {
  197. for program in self.read_all_programs()? {
  198. let cargo_toml = program.path.join("Cargo.toml");
  199. if !cargo_toml.exists() {
  200. return Err(anyhow!(
  201. "Did not find Cargo.toml at the path: {}",
  202. program.path.display()
  203. ));
  204. }
  205. let p_lib_name = Manifest::from_path(&cargo_toml)?.lib_name()?;
  206. if name == p_lib_name {
  207. let path = self
  208. .path()
  209. .parent()
  210. .unwrap()
  211. .canonicalize()?
  212. .join(&program.path);
  213. return Ok(Some(WithPath::new(program, path)));
  214. }
  215. }
  216. Ok(None)
  217. }
  218. }
  219. impl<T> std::ops::Deref for WithPath<T> {
  220. type Target = T;
  221. fn deref(&self) -> &Self::Target {
  222. &self.inner
  223. }
  224. }
  225. impl<T> std::ops::DerefMut for WithPath<T> {
  226. fn deref_mut(&mut self) -> &mut Self::Target {
  227. &mut self.inner
  228. }
  229. }
  230. #[derive(Debug, Default)]
  231. pub struct Config {
  232. pub anchor_version: Option<String>,
  233. pub solana_version: Option<String>,
  234. pub features: FeaturesConfig,
  235. pub registry: RegistryConfig,
  236. pub provider: ProviderConfig,
  237. pub programs: ProgramsConfig,
  238. pub scripts: ScriptsConfig,
  239. pub workspace: WorkspaceConfig,
  240. pub test: Option<Test>,
  241. }
  242. #[derive(Default, Clone, Debug, Serialize, Deserialize)]
  243. pub struct FeaturesConfig {
  244. #[serde(default)]
  245. pub seeds: bool,
  246. }
  247. #[derive(Clone, Debug, Serialize, Deserialize)]
  248. pub struct RegistryConfig {
  249. pub url: String,
  250. }
  251. impl Default for RegistryConfig {
  252. fn default() -> Self {
  253. Self {
  254. url: "https://anchor.projectserum.com".to_string(),
  255. }
  256. }
  257. }
  258. #[derive(Debug, Default)]
  259. pub struct ProviderConfig {
  260. pub cluster: Cluster,
  261. pub wallet: WalletPath,
  262. }
  263. pub type ScriptsConfig = BTreeMap<String, String>;
  264. pub type ProgramsConfig = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;
  265. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  266. pub struct WorkspaceConfig {
  267. #[serde(default, skip_serializing_if = "Vec::is_empty")]
  268. pub members: Vec<String>,
  269. #[serde(default, skip_serializing_if = "Vec::is_empty")]
  270. pub exclude: Vec<String>,
  271. #[serde(default, skip_serializing_if = "String::is_empty")]
  272. pub types: String,
  273. }
  274. #[derive(ArgEnum, Parser, Clone, PartialEq, Debug)]
  275. pub enum BootstrapMode {
  276. None,
  277. Debian,
  278. }
  279. #[derive(Debug, Clone)]
  280. pub struct BuildConfig {
  281. pub verifiable: bool,
  282. pub solana_version: Option<String>,
  283. pub docker_image: String,
  284. pub bootstrap: BootstrapMode,
  285. }
  286. impl Config {
  287. pub fn docker(&self) -> String {
  288. let ver = self
  289. .anchor_version
  290. .clone()
  291. .unwrap_or_else(|| crate::DOCKER_BUILDER_VERSION.to_string());
  292. format!("projectserum/build:v{}", ver)
  293. }
  294. pub fn discover(cfg_override: &ConfigOverride) -> Result<Option<WithPath<Config>>> {
  295. Config::_discover().map(|opt| {
  296. opt.map(|mut cfg| {
  297. if let Some(cluster) = cfg_override.cluster.clone() {
  298. cfg.provider.cluster = cluster;
  299. }
  300. if let Some(wallet) = cfg_override.wallet.clone() {
  301. cfg.provider.wallet = wallet;
  302. }
  303. cfg
  304. })
  305. })
  306. }
  307. // Climbs each parent directory until we find an Anchor.toml.
  308. fn _discover() -> Result<Option<WithPath<Config>>> {
  309. let _cwd = std::env::current_dir()?;
  310. let mut cwd_opt = Some(_cwd.as_path());
  311. while let Some(cwd) = cwd_opt {
  312. for f in fs::read_dir(cwd).with_context(|| {
  313. format!("Error reading the directory with path: {}", cwd.display())
  314. })? {
  315. let p = f
  316. .with_context(|| {
  317. format!("Error reading the directory with path: {}", cwd.display())
  318. })?
  319. .path();
  320. if let Some(filename) = p.file_name() {
  321. if filename.to_str() == Some("Anchor.toml") {
  322. let cfg = Config::from_path(&p)?;
  323. return Ok(Some(WithPath::new(cfg, p)));
  324. }
  325. }
  326. }
  327. cwd_opt = cwd.parent();
  328. }
  329. Ok(None)
  330. }
  331. fn from_path(p: impl AsRef<Path>) -> Result<Self> {
  332. fs::read_to_string(&p)
  333. .with_context(|| format!("Error reading the file with path: {}", p.as_ref().display()))?
  334. .parse()
  335. }
  336. pub fn wallet_kp(&self) -> Result<Keypair> {
  337. solana_sdk::signature::read_keypair_file(&self.provider.wallet.to_string())
  338. .map_err(|_| anyhow!("Unable to read keypair file"))
  339. }
  340. }
  341. #[derive(Debug, Serialize, Deserialize)]
  342. struct _Config {
  343. anchor_version: Option<String>,
  344. solana_version: Option<String>,
  345. features: Option<FeaturesConfig>,
  346. programs: Option<BTreeMap<String, BTreeMap<String, serde_json::Value>>>,
  347. registry: Option<RegistryConfig>,
  348. provider: Provider,
  349. workspace: Option<WorkspaceConfig>,
  350. scripts: Option<ScriptsConfig>,
  351. test: Option<Test>,
  352. }
  353. #[derive(Debug, Serialize, Deserialize)]
  354. struct Provider {
  355. cluster: String,
  356. wallet: String,
  357. }
  358. impl ToString for Config {
  359. fn to_string(&self) -> String {
  360. let programs = {
  361. let c = ser_programs(&self.programs);
  362. if c.is_empty() {
  363. None
  364. } else {
  365. Some(c)
  366. }
  367. };
  368. let cfg = _Config {
  369. anchor_version: self.anchor_version.clone(),
  370. solana_version: self.solana_version.clone(),
  371. features: Some(self.features.clone()),
  372. registry: Some(self.registry.clone()),
  373. provider: Provider {
  374. cluster: format!("{}", self.provider.cluster),
  375. wallet: self.provider.wallet.to_string(),
  376. },
  377. test: self.test.clone(),
  378. scripts: match self.scripts.is_empty() {
  379. true => None,
  380. false => Some(self.scripts.clone()),
  381. },
  382. programs,
  383. workspace: (!self.workspace.members.is_empty() || !self.workspace.exclude.is_empty())
  384. .then(|| self.workspace.clone()),
  385. };
  386. toml::to_string(&cfg).expect("Must be well formed")
  387. }
  388. }
  389. impl FromStr for Config {
  390. type Err = Error;
  391. fn from_str(s: &str) -> Result<Self, Self::Err> {
  392. let cfg: _Config = toml::from_str(s)
  393. .map_err(|e| anyhow::format_err!("Unable to deserialize config: {}", e.to_string()))?;
  394. Ok(Config {
  395. anchor_version: cfg.anchor_version,
  396. solana_version: cfg.solana_version,
  397. features: cfg.features.unwrap_or_default(),
  398. registry: cfg.registry.unwrap_or_default(),
  399. provider: ProviderConfig {
  400. cluster: cfg.provider.cluster.parse()?,
  401. wallet: shellexpand::tilde(&cfg.provider.wallet).parse()?,
  402. },
  403. scripts: cfg.scripts.unwrap_or_default(),
  404. test: cfg.test,
  405. programs: cfg.programs.map_or(Ok(BTreeMap::new()), deser_programs)?,
  406. workspace: cfg.workspace.unwrap_or_default(),
  407. })
  408. }
  409. }
  410. pub fn get_solana_cfg_url() -> Result<String, io::Error> {
  411. let config_file = CONFIG_FILE.as_ref().ok_or_else(|| {
  412. io::Error::new(
  413. io::ErrorKind::NotFound,
  414. "Default Solana config was not found",
  415. )
  416. })?;
  417. SolanaConfig::load(config_file).map(|config| config.json_rpc_url)
  418. }
  419. fn ser_programs(
  420. programs: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
  421. ) -> BTreeMap<String, BTreeMap<String, serde_json::Value>> {
  422. programs
  423. .iter()
  424. .map(|(cluster, programs)| {
  425. let cluster = cluster.to_string();
  426. let programs = programs
  427. .iter()
  428. .map(|(name, deployment)| {
  429. (
  430. name.clone(),
  431. to_value(&_ProgramDeployment::from(deployment)),
  432. )
  433. })
  434. .collect::<BTreeMap<String, serde_json::Value>>();
  435. (cluster, programs)
  436. })
  437. .collect::<BTreeMap<String, BTreeMap<String, serde_json::Value>>>()
  438. }
  439. fn to_value(dep: &_ProgramDeployment) -> serde_json::Value {
  440. if dep.path.is_none() && dep.idl.is_none() {
  441. return serde_json::Value::String(dep.address.to_string());
  442. }
  443. serde_json::to_value(dep).unwrap()
  444. }
  445. fn deser_programs(
  446. programs: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
  447. ) -> Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>> {
  448. programs
  449. .iter()
  450. .map(|(cluster, programs)| {
  451. let cluster: Cluster = cluster.parse()?;
  452. let programs = programs
  453. .iter()
  454. .map(|(name, program_id)| {
  455. Ok((
  456. name.clone(),
  457. ProgramDeployment::try_from(match &program_id {
  458. serde_json::Value::String(address) => _ProgramDeployment {
  459. address: address.parse()?,
  460. path: None,
  461. idl: None,
  462. },
  463. serde_json::Value::Object(_) => {
  464. serde_json::from_value(program_id.clone())
  465. .map_err(|_| anyhow!("Unable to read toml"))?
  466. }
  467. _ => return Err(anyhow!("Invalid toml type")),
  468. })?,
  469. ))
  470. })
  471. .collect::<Result<BTreeMap<String, ProgramDeployment>>>()?;
  472. Ok((cluster, programs))
  473. })
  474. .collect::<Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>>>()
  475. }
  476. #[derive(Debug, Clone, Serialize, Deserialize)]
  477. pub struct Test {
  478. pub genesis: Option<Vec<GenesisEntry>>,
  479. pub validator: Option<Validator>,
  480. pub startup_wait: Option<i32>,
  481. }
  482. #[derive(Debug, Clone, Serialize, Deserialize)]
  483. pub struct GenesisEntry {
  484. // Base58 pubkey string.
  485. pub address: String,
  486. // Filepath to the compiled program to embed into the genesis.
  487. pub program: String,
  488. }
  489. #[derive(Debug, Clone, Serialize, Deserialize)]
  490. pub struct CloneEntry {
  491. // Base58 pubkey string.
  492. pub address: String,
  493. }
  494. #[derive(Debug, Clone, Serialize, Deserialize)]
  495. pub struct AccountEntry {
  496. // Base58 pubkey string.
  497. pub address: String,
  498. // Name of JSON file containing the account data.
  499. pub filename: String,
  500. }
  501. #[derive(Debug, Default, Clone, Serialize, Deserialize)]
  502. pub struct Validator {
  503. // Load an account from the provided JSON file
  504. #[serde(skip_serializing_if = "Option::is_none")]
  505. pub account: Option<Vec<AccountEntry>>,
  506. // IP address to bind the validator ports. [default: 0.0.0.0]
  507. #[serde(default = "default_bind_address")]
  508. pub bind_address: String,
  509. // Copy an account from the cluster referenced by the url argument.
  510. #[serde(skip_serializing_if = "Option::is_none")]
  511. pub clone: Option<Vec<CloneEntry>>,
  512. // Range to use for dynamically assigned ports. [default: 1024-65535]
  513. #[serde(skip_serializing_if = "Option::is_none")]
  514. pub dynamic_port_range: Option<String>,
  515. // Enable the faucet on this port [deafult: 9900].
  516. #[serde(skip_serializing_if = "Option::is_none")]
  517. pub faucet_port: Option<u16>,
  518. // Give the faucet address this much SOL in genesis. [default: 1000000]
  519. #[serde(skip_serializing_if = "Option::is_none")]
  520. pub faucet_sol: Option<String>,
  521. // Gossip DNS name or IP address for the validator to advertise in gossip. [default: 127.0.0.1]
  522. #[serde(skip_serializing_if = "Option::is_none")]
  523. pub gossip_host: Option<String>,
  524. // Gossip port number for the validator
  525. #[serde(skip_serializing_if = "Option::is_none")]
  526. pub gossip_port: Option<u16>,
  527. // URL for Solana's JSON RPC or moniker.
  528. #[serde(skip_serializing_if = "Option::is_none")]
  529. pub url: Option<String>,
  530. // Use DIR as ledger location
  531. #[serde(default = "default_ledger_path")]
  532. pub ledger: String,
  533. // Keep this amount of shreds in root slots. [default: 10000]
  534. #[serde(skip_serializing_if = "Option::is_none")]
  535. pub limit_ledger_size: Option<String>,
  536. // Enable JSON RPC on this port, and the next port for the RPC websocket. [default: 8899]
  537. #[serde(default = "default_rpc_port")]
  538. pub rpc_port: u16,
  539. // Override the number of slots in an epoch.
  540. #[serde(skip_serializing_if = "Option::is_none")]
  541. pub slots_per_epoch: Option<String>,
  542. // Warp the ledger to WARP_SLOT after starting the validator.
  543. #[serde(skip_serializing_if = "Option::is_none")]
  544. pub warp_slot: Option<String>,
  545. }
  546. fn default_ledger_path() -> String {
  547. ".anchor/test-ledger".to_string()
  548. }
  549. fn default_bind_address() -> String {
  550. "0.0.0.0".to_string()
  551. }
  552. fn default_rpc_port() -> u16 {
  553. 8899
  554. }
  555. #[derive(Debug, Clone)]
  556. pub struct Program {
  557. pub lib_name: String,
  558. // Canonicalized path to the program directory.
  559. pub path: PathBuf,
  560. pub idl: Option<Idl>,
  561. }
  562. impl Program {
  563. pub fn pubkey(&self) -> Result<Pubkey> {
  564. self.keypair().map(|kp| kp.pubkey())
  565. }
  566. pub fn keypair(&self) -> Result<Keypair> {
  567. let file = self.keypair_file()?;
  568. solana_sdk::signature::read_keypair_file(file.path())
  569. .map_err(|_| anyhow!("failed to read keypair for program: {}", self.lib_name))
  570. }
  571. // Lazily initializes the keypair file with a new key if it doesn't exist.
  572. pub fn keypair_file(&self) -> Result<WithPath<File>> {
  573. let deploy_dir_path = "target/deploy/";
  574. fs::create_dir_all(deploy_dir_path)
  575. .with_context(|| format!("Error creating directory with path: {}", deploy_dir_path))?;
  576. let path = std::env::current_dir()
  577. .expect("Must have current dir")
  578. .join(format!("target/deploy/{}-keypair.json", self.lib_name));
  579. if path.exists() {
  580. return Ok(WithPath::new(
  581. File::open(&path)
  582. .with_context(|| format!("Error opening file with path: {}", path.display()))?,
  583. path,
  584. ));
  585. }
  586. let program_kp = Keypair::generate(&mut rand::rngs::OsRng);
  587. let mut file = File::create(&path)
  588. .with_context(|| format!("Error creating file with path: {}", path.display()))?;
  589. file.write_all(format!("{:?}", &program_kp.to_bytes()).as_bytes())?;
  590. Ok(WithPath::new(file, path))
  591. }
  592. pub fn binary_path(&self) -> PathBuf {
  593. std::env::current_dir()
  594. .expect("Must have current dir")
  595. .join(format!("target/deploy/{}.so", self.lib_name))
  596. }
  597. }
  598. #[derive(Debug, Default)]
  599. pub struct ProgramDeployment {
  600. pub address: Pubkey,
  601. pub path: Option<String>,
  602. pub idl: Option<String>,
  603. }
  604. impl TryFrom<_ProgramDeployment> for ProgramDeployment {
  605. type Error = anyhow::Error;
  606. fn try_from(pd: _ProgramDeployment) -> Result<Self, Self::Error> {
  607. Ok(ProgramDeployment {
  608. address: pd.address.parse()?,
  609. path: pd.path,
  610. idl: pd.idl,
  611. })
  612. }
  613. }
  614. #[derive(Debug, Default, Serialize, Deserialize)]
  615. pub struct _ProgramDeployment {
  616. pub address: String,
  617. pub path: Option<String>,
  618. pub idl: Option<String>,
  619. }
  620. impl From<&ProgramDeployment> for _ProgramDeployment {
  621. fn from(pd: &ProgramDeployment) -> Self {
  622. Self {
  623. address: pd.address.to_string(),
  624. path: pd.path.clone(),
  625. idl: pd.idl.clone(),
  626. }
  627. }
  628. }
  629. pub struct ProgramWorkspace {
  630. pub name: String,
  631. pub program_id: Pubkey,
  632. pub idl: Idl,
  633. }
  634. #[derive(Debug, Serialize, Deserialize)]
  635. pub struct AnchorPackage {
  636. pub name: String,
  637. pub address: String,
  638. pub idl: Option<String>,
  639. }
  640. impl AnchorPackage {
  641. pub fn from(name: String, cfg: &WithPath<Config>) -> Result<Self> {
  642. let cluster = &cfg.provider.cluster;
  643. if cluster != &Cluster::Mainnet {
  644. return Err(anyhow!("Publishing requires the mainnet cluster"));
  645. }
  646. let program_details = cfg
  647. .programs
  648. .get(cluster)
  649. .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?
  650. .get(&name)
  651. .ok_or_else(|| anyhow!("Program not provided in Anchor.toml"))?;
  652. let idl = program_details.idl.clone();
  653. let address = program_details.address.to_string();
  654. Ok(Self { name, address, idl })
  655. }
  656. }
  657. serum_common::home_path!(WalletPath, ".config/solana/id.json");