diff --git a/Cargo.lock b/Cargo.lock index 74f4fd11..356df98e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,7 @@ dependencies = [ "anchor-lang", "anchor-syn", "anyhow", + "bs58", "clap 3.0.0-beta.2", "dirs", "flate2", @@ -113,6 +114,7 @@ dependencies = [ "serde_yaml", "serum-common", "shellexpand", + "solana-account-decoder", "solana-client", "solana-program", "solana-sdk", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b004c478..0f799da7 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -22,7 +22,9 @@ serde = { version = "1.0", features = ["derive"] } solana-sdk = "1.5.0" solana-program = "1.5.0" solana-client = "1.4.4" +solana-account-decoder = "1.4.13" serum-common = { git = "https://github.com/project-serum/serum-dex", features = ["client"] } dirs = "3.0" heck = "0.3.1" flate2 = "1.0.19" +bs58 = "0.3.1" \ No newline at end of file diff --git a/cli/src/main.rs b/cli/src/main.rs index 9122b47a..dffd456e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -8,8 +8,12 @@ use flate2::read::ZlibDecoder; use flate2::write::ZlibEncoder; use flate2::Compression; use serde::{Deserialize, Serialize}; +use solana_account_decoder::UiAccountEncoding; use solana_client::rpc_client::RpcClient; -use solana_client::rpc_config::RpcSendTransactionConfig; +use solana_client::rpc_config::{ + RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig, +}; +use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}; use solana_program::instruction::{AccountMeta, Instruction}; use solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::pubkey::Pubkey; @@ -34,17 +38,13 @@ pub struct Opts { pub enum Command { /// Initializes a workspace. Init { name: String }, - /// Builds a Solana program. - Build { - /// Output directory for the IDL. - #[clap(short, long)] - idl: Option, - }, - /// Runs integration tests against a localnetwork. + /// Builds programs. + Build, + /// Runs integration tests. Test, /// Creates a new program. New { name: String }, - /// Commands for interact with interface definitions. + /// Commands for interface definitions. Idl { #[clap(subcommand)] subcmd: IdlCommand, @@ -65,6 +65,10 @@ pub enum Command { /// Not yet implemented. Please use `solana program deploy` command to /// upgrade your program. Upgrade {}, + Account { + #[clap(subcommand)] + subcmd: AccountCommand, + }, } #[derive(Debug, Clap)] @@ -93,12 +97,25 @@ pub enum IdlCommand { }, } +#[derive(Debug, Clap)] +pub enum AccountCommand { + /// Outputs program owned accounts. + List { + #[clap(short, long)] + program: Pubkey, + /// The account kind. Matches the type defined in the program source, + /// lowercase. + #[clap(short, long)] + kind: Option, + }, +} + fn main() -> Result<()> { let opts = Opts::parse(); match opts.command { Command::Init { name } => init(name), - Command::Build { idl } => build(idl), + Command::Build => build(None), Command::Test => test(), Command::New { name } => new(name), Command::Idl { subcmd } => idl(subcmd), @@ -108,14 +125,61 @@ fn main() -> Result<()> { Ok(()) } Command::Migrate { url } => migrate(&url), + Command::Account { subcmd } => account(subcmd), } } +fn account(cmd: AccountCommand) -> Result<()> { + match cmd { + AccountCommand::List { program, kind } => account_list(program, kind), + } +} + +fn account_list(program: Pubkey, kind: Option) -> Result<()> { + let cfg = Config::discover()?.expect("Must be inside a workspace").0; + let client = RpcClient::new(cfg.cluster.url().to_string()); + + // Filter on the 8 byte account discriminator. + let filters = kind.map(|kind| { + let filter_bytes = { + let discriminator_preimage = format!("account:{}", kind); + let mut disc = [0u8; 8]; + disc.copy_from_slice( + &solana_program::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8], + ); + bs58::encode(disc).into_string() + }; + let disc_filter = RpcFilterType::Memcmp(Memcmp { + offset: 0, + bytes: MemcmpEncodedBytes::Binary(filter_bytes), + encoding: None, + }); + + vec![disc_filter] + }); + + let resp = client.get_program_accounts_with_config( + &program, + RpcProgramAccountsConfig { + filters, + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + ..RpcAccountInfoConfig::default() + }, + }, + )?; + + println!("RESP {:?}", resp); + + Ok(()) +} + fn init(name: String) -> Result<()> { let cfg = Config::discover()?; if cfg.is_some() { println!("Anchor workspace already initialized"); + return Ok(()); } fs::create_dir(name.clone())?;