Skip to content

Commit

Permalink
cli: Add shell command (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante authored May 22, 2021
1 parent 74424fe commit ccf1855
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ incremented for features.
* ts: Add `program.simulate` namespace ([#266](https://github.com/project-serum/anchor/pull/266)).
* cli: Add yarn flag to test command ([#267](https://github.com/project-serum/anchor/pull/267)).
* cli: Add `--skip-build` flag to test command ([301](https://github.com/project-serum/anchor/pull/301)).
* cli: Add `anchor shell` command to spawn a node shell populated with an Anchor.toml based environment ([#303](https://github.com/project-serum/anchor/pull/303)).

## Breaking Changes

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ clap = "3.0.0-beta.1"
anyhow = "1.0.32"
syn = { version = "1.0.60", features = ["full", "extra-traits"] }
anchor-lang = { path = "../lang" }
anchor-client = { path = "../client" }
anchor-syn = { path = "../lang/syn", features = ["idl"] }
serde_json = "1.0"
shellexpand = "2.1.0"
Expand Down
72 changes: 71 additions & 1 deletion cli/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use anchor_client::Cluster;
use anchor_syn::idl::Idl;
use anyhow::{anyhow, Error, Result};
use serde::{Deserialize, Serialize};
use serum_common::client::Cluster;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Keypair;
use std::collections::BTreeMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::Path;
Expand All @@ -12,6 +14,7 @@ use std::str::FromStr;
#[derive(Debug, Default)]
pub struct Config {
pub cluster: Cluster,
pub clusters: Clusters,
pub wallet: WalletPath,
pub test: Option<Test>,
}
Expand Down Expand Up @@ -73,14 +76,24 @@ struct _Config {
cluster: String,
wallet: String,
test: Option<Test>,
clusters: Option<BTreeMap<String, BTreeMap<String, String>>>,
}

impl ToString for Config {
fn to_string(&self) -> String {
let clusters = {
let c = ser_clusters(&self.clusters);
if c.len() == 0 {
None
} else {
Some(c)
}
};
let cfg = _Config {
cluster: format!("{}", self.cluster),
wallet: self.wallet.to_string(),
test: self.test.clone(),
clusters,
};

toml::to_string(&cfg).expect("Must be well formed")
Expand All @@ -97,10 +110,53 @@ impl FromStr for Config {
cluster: cfg.cluster.parse()?,
wallet: shellexpand::tilde(&cfg.wallet).parse()?,
test: cfg.test,
clusters: cfg
.clusters
.map_or(Ok(BTreeMap::new()), |c| deser_clusters(c))?,
})
}
}

fn ser_clusters(
clusters: &BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>,
) -> BTreeMap<String, BTreeMap<String, String>> {
clusters
.iter()
.map(|(cluster, programs)| {
let cluster = cluster.to_string();
let programs = programs
.iter()
.map(|(name, deployment)| (name.clone(), deployment.program_id.to_string()))
.collect::<BTreeMap<String, String>>();
(cluster, programs)
})
.collect::<BTreeMap<String, BTreeMap<String, String>>>()
}

fn deser_clusters(
clusters: BTreeMap<String, BTreeMap<String, String>>,
) -> Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>> {
clusters
.iter()
.map(|(cluster, programs)| {
let cluster: Cluster = cluster.parse()?;
let programs = programs
.iter()
.map(|(name, program_id)| {
Ok((
name.clone(),
ProgramDeployment {
name: name.clone(),
program_id: program_id.parse()?,
},
))
})
.collect::<Result<BTreeMap<String, ProgramDeployment>>>()?;
Ok((cluster, programs))
})
.collect::<Result<BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>>>()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Test {
pub genesis: Vec<GenesisEntry>,
Expand Down Expand Up @@ -177,4 +233,18 @@ impl Program {
}
}

pub type Clusters = BTreeMap<Cluster, BTreeMap<String, ProgramDeployment>>;

#[derive(Debug, Default)]
pub struct ProgramDeployment {
pub name: String,
pub program_id: Pubkey,
}

pub struct ProgramWorkspace {
pub name: String,
pub program_id: Pubkey,
pub idl: Idl,
}

serum_common::home_path!(WalletPath, ".config/solana/id.json");
65 changes: 64 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! CLI for workspace management of anchor programs.

use crate::config::{read_all_programs, Config, Program};
use crate::config::{read_all_programs, Config, Program, ProgramWorkspace};
use anchor_client::Cluster;
use anchor_lang::idl::{IdlAccount, IdlInstruction};
use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
use anchor_syn::idl::Idl;
Expand All @@ -22,10 +23,12 @@ use solana_sdk::signature::Keypair;
use solana_sdk::signature::Signer;
use solana_sdk::sysvar;
use solana_sdk::transaction::Transaction;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::{Child, Stdio};
use std::str::FromStr;
use std::string::ToString;

mod config;
Expand Down Expand Up @@ -139,6 +142,16 @@ pub enum Command {
#[clap(subcommand)]
subcmd: ClusterCommand,
},
/// Starts a node shell with an Anchor client setup according to the local
/// config.
Shell {
/// The cluster config to use.
#[clap(short, long)]
cluster: Option<String>,
/// Local path to the wallet keypair file.
#[clap(short, long)]
wallet: Option<String>,
},
}

#[derive(Debug, Clap)]
Expand Down Expand Up @@ -253,6 +266,7 @@ fn main() -> Result<()> {
#[cfg(feature = "dev")]
Command::Airdrop { url } => airdrop(url),
Command::Cluster { subcmd } => cluster(subcmd),
Command::Shell { cluster, wallet } => shell(cluster, wallet),
}
}

Expand Down Expand Up @@ -1589,3 +1603,52 @@ fn cluster(_cmd: ClusterCommand) -> Result<()> {
println!("* Testnet - https://testnet.solana.com");
Ok(())
}

fn shell(cluster: Option<String>, wallet: Option<String>) -> Result<()> {
with_workspace(|cfg, _path, _cargo| {
let cluster = match cluster {
None => cfg.cluster.clone(),
Some(c) => Cluster::from_str(&c)?,
};
let wallet = match wallet {
None => cfg.wallet.to_string(),
Some(c) => c,
};
let programs = {
let idls: HashMap<String, Idl> = read_all_programs()?
.iter()
.map(|program| (program.idl.name.clone(), program.idl.clone()))
.collect();
match cfg.clusters.get(&cluster) {
None => Vec::new(),
Some(programs) => programs
.iter()
.map(|(name, program_deployment)| ProgramWorkspace {
name: name.to_string(),
program_id: program_deployment.program_id,
idl: match idls.get(name) {
None => {
println!("Unable to find IDL for {}", name);
std::process::exit(1);
}
Some(idl) => idl.clone(),
},
})
.collect::<Vec<ProgramWorkspace>>(),
}
};
let js_code = template::node_shell(cluster.url(), &wallet, programs)?;
let mut child = std::process::Command::new("node")
.args(&["-e", &js_code, "-i", "--experimental-repl-await"])
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.map_err(|e| anyhow::format_err!("{}", e.to_string()))?;

if !child.wait()?.success() {
println!("Error running node shell");
return Ok(());
}
Ok(())
})
}
49 changes: 49 additions & 0 deletions cli/src/template.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::config::ProgramWorkspace;
use crate::VERSION;
use anyhow::Result;
use heck::{CamelCase, SnakeCase};

pub fn virtual_manifest() -> &'static str {
Expand Down Expand Up @@ -190,3 +192,50 @@ target
**/*.rs.bk
"#
}

pub fn node_shell(
cluster_url: &str,
wallet_path: &str,
programs: Vec<ProgramWorkspace>,
) -> Result<String> {
let mut eval_string = format!(
r#"
const anchor = require('@project-serum/anchor');
const web3 = anchor.web3;
const PublicKey = anchor.web3.PublicKey;
const __wallet = new anchor.Wallet(
Buffer.from(
JSON.parse(
require('fs').readFileSync(
"{}",
{{
encoding: "utf-8",
}},
),
),
),
);
const __connection = new web3.Connection("{}", "processed");
const provider = new anchor.Provider(__connection, __wallet, {{
commitment: "processed",
preflightcommitment: "processed",
}});
anchor.setProvider(provider);
"#,
wallet_path, cluster_url,
);

for program in programs {
eval_string.push_str(&format!(
r#"
anchor.workspace.{} = new anchor.Program({}, new PublicKey("{}"), provider);
"#,
program.name,
serde_json::to_string(&program.idl)?,
program.program_id.to_string()
));
}

Ok(eval_string)
}
1 change: 1 addition & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ description = "Rust client for Anchor programs"
anchor-lang = { path = "../lang", version = "0.5.0" }
anyhow = "1.0.32"
regex = "1.4.5"
serde = { version = "1.0.122", features = ["derive"] }
solana-client = "1.6.6"
solana-sdk = "1.6.6"
thiserror = "1.0.20"
3 changes: 2 additions & 1 deletion client/src/cluster.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Cluster {
Testnet,
Mainnet,
Expand Down

0 comments on commit ccf1855

Please sign in to comment.