diff --git a/Cargo.lock b/Cargo.lock index 53276351c..d2fc62e53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2522,6 +2522,7 @@ dependencies = [ "mango-v4", "pyth-sdk-solana", "serum_dex", + "shellexpand", "solana-client", "solana-sdk", "tokio", @@ -4506,6 +4507,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" +dependencies = [ + "dirs-next", +] + [[package]] name = "shlex" version = "1.1.0" diff --git a/client/src/client.rs b/client/src/client.rs index 7b2620ced..b28f06ca4 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -31,7 +31,6 @@ pub struct MangoClient { pub cluster: Cluster, pub commitment: CommitmentConfig, pub payer: Keypair, - pub admin: Keypair, pub mango_account_cache: (Pubkey, MangoAccount), pub group: Pubkey, pub group_cache: Group, @@ -53,11 +52,19 @@ pub struct MangoClient { // 2/ pubkey, can be both owned, but also delegated accouns impl MangoClient { + pub fn group_for_admin(admin: Pubkey, num: u32) -> Pubkey { + Pubkey::find_program_address( + &["Group".as_ref(), admin.as_ref(), num.to_le_bytes().as_ref()], + &mango_v4::ID, + ) + .0 + } + pub fn new( cluster: Cluster, commitment: CommitmentConfig, payer: Keypair, - admin: Keypair, + group: Pubkey, mango_account_name: &str, ) -> anyhow::Result { let program = @@ -66,16 +73,6 @@ impl MangoClient { let rpc = program.rpc(); - let group = Pubkey::find_program_address( - &[ - "Group".as_ref(), - admin.pubkey().as_ref(), - 0u32.to_le_bytes().as_ref(), - ], - &program.id(), - ) - .0; - let group_data = program.account::(group)?; // Mango Account @@ -237,7 +234,6 @@ impl MangoClient { rpc, cluster, commitment, - admin, payer, mango_account_cache, group, diff --git a/keeper/.env.devnet b/keeper/.env.devnet index 10e4bc984..ad55d8f2b 100644 --- a/keeper/.env.devnet +++ b/keeper/.env.devnet @@ -1,4 +1,4 @@ RPC_URL=https://mango.devnet.rpcpool.com PAYER_KEYPAIR=~/.config/solana/mango-devnet.json -ADMIN_KEYPAIR=~/.config/solana/admin.json +GROUP_FROM_ADMIN_KEYPAIR=~/.config/solana/admin.json MANGO_ACCOUNT_NAME=Account \ No newline at end of file diff --git a/keeper/.env.example b/keeper/.env.example index 9f52373aa..ec532c91e 100644 --- a/keeper/.env.example +++ b/keeper/.env.example @@ -1,4 +1,4 @@ RPC_URL= PAYER_KEYPAIR= -ADMIN_KEYPAIR= +GROUP= MANGO_ACCOUNT_NAME= \ No newline at end of file diff --git a/keeper/.env.mainnet-beta b/keeper/.env.mainnet-beta index 1213b0bda..d1e330de3 100644 --- a/keeper/.env.mainnet-beta +++ b/keeper/.env.mainnet-beta @@ -1,4 +1,4 @@ RPC_URL=https://mango.rpcpool.com/ PAYER_KEYPAIR=~/.config/solana/mango-mainnet.json -ADMIN_KEYPAIR=~/.config/solana/mango-mainnet.json +GROUP=grouppubkey MANGO_ACCOUNT_NAME=Account \ No newline at end of file diff --git a/keeper/Cargo.toml b/keeper/Cargo.toml index 3fcac862e..f4c25d6f3 100644 --- a/keeper/Cargo.toml +++ b/keeper/Cargo.toml @@ -11,16 +11,17 @@ anchor-lang = "0.24.2" anchor-spl = "0.24.2" anyhow = "1.0" clap = { version = "3.1.8", features = ["derive", "env"] } -client = { path = "../client" } +client = { path = "../client" } dotenv = "0.15.0" env_logger = "0.8.4" fixed = { version = "=1.11.0", features = ["serde", "borsh"] } fixed-macro = "^1.1.1" futures = "0.3.21" log = "0.4.0" -mango-v4 = { path = "../programs/mango-v4" } +mango-v4 = { path = "../programs/mango-v4" } pyth-sdk-solana = "0.1.0" serum_dex = { version = "0.4.0", git = "https://github.com/blockworks-foundation/serum-dex.git", default-features=false, features = ["no-entrypoint", "program"] } solana-client = "~1.9.13" solana-sdk = "~1.9.13" -tokio = { version = "1.14.1", features = ["rt-multi-thread", "time", "macros", "sync"] } \ No newline at end of file +tokio = { version = "1.14.1", features = ["rt-multi-thread", "time", "macros", "sync"] } +shellexpand = "2.1.0" \ No newline at end of file diff --git a/keeper/src/main.rs b/keeper/src/main.rs index 58079555d..117767be5 100644 --- a/keeper/src/main.rs +++ b/keeper/src/main.rs @@ -2,13 +2,19 @@ mod crank; mod taker; use std::env; +use std::str::FromStr; use std::sync::Arc; use anchor_client::Cluster; use clap::{Parser, Subcommand}; use client::MangoClient; -use solana_sdk::{commitment_config::CommitmentConfig, signer::keypair}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::{ + commitment_config::CommitmentConfig, + signature::Signer, + signer::{keypair, keypair::Keypair}, +}; use tokio::time; // TODO @@ -27,8 +33,14 @@ struct Cli { #[clap(short, long, env = "PAYER_KEYPAIR")] payer: Option, - #[clap(short, long, env = "ADMIN_KEYPAIR")] - admin: Option, + #[clap(short, long, env = "GROUP")] + group: Option, + + // These exist only as a shorthand to make testing easier. Normal users would provide the group. + #[clap(long, env = "GROUP_FROM_ADMIN_KEYPAIR")] + group_from_admin_keypair: Option, + #[clap(long, env = "GROUP_FROM_ADMIN_NUM", default_value = "0")] + group_from_admin_num: u32, #[clap(short, long, env = "MANGO_ACCOUNT_NAME")] mango_account_name: String, @@ -37,6 +49,12 @@ struct Cli { command: Command, } +fn keypair_from_path(p: &std::path::PathBuf) -> Keypair { + let path = std::path::PathBuf::from_str(&*shellexpand::tilde(p.to_str().unwrap())).unwrap(); + keypair::read_keypair_file(path) + .unwrap_or_else(|_| panic!("Failed to read keypair from {}", p.to_string_lossy())) +} + #[derive(Subcommand)] enum Command { Crank {}, @@ -49,27 +67,14 @@ fn main() -> Result<(), anyhow::Error> { dotenv::dotenv().ok(); - let Cli { - rpc_url, - payer, - admin, - command, - mango_account_name, - } = Cli::parse(); + let cli = Cli::parse(); - let payer = match payer { - Some(p) => keypair::read_keypair_file(&p) - .unwrap_or_else(|_| panic!("Failed to read keypair from {}", p.to_string_lossy())), + let payer = match cli.payer { + Some(p) => keypair_from_path(&p), None => panic!("Payer keypair not provided..."), }; - let admin = match admin { - Some(p) => keypair::read_keypair_file(&p) - .unwrap_or_else(|_| panic!("Failed to read keypair from {}", p.to_string_lossy())), - None => panic!("Admin keypair not provided..."), - }; - - let rpc_url = match rpc_url { + let rpc_url = match cli.rpc_url { Some(rpc_url) => rpc_url, None => match env::var("RPC_URL").ok() { Some(rpc_url) => rpc_url, @@ -79,17 +84,26 @@ fn main() -> Result<(), anyhow::Error> { let ws_url = rpc_url.replace("https", "wss"); let cluster = Cluster::Custom(rpc_url, ws_url); - let commitment = match command { + let commitment = match cli.command { Command::Crank { .. } => CommitmentConfig::confirmed(), Command::Taker { .. } => CommitmentConfig::confirmed(), }; + let group = if let Some(group) = cli.group { + group + } else if let Some(p) = cli.group_from_admin_keypair { + let admin = keypair_from_path(&p); + MangoClient::group_for_admin(admin.pubkey(), cli.group_from_admin_num) + } else { + panic!("Must provide either group or group_from_admin_keypair"); + }; + let mango_client = Arc::new(MangoClient::new( cluster, commitment, payer, - admin, - &mango_account_name, + group, + &cli.mango_account_name, )?); let rt = tokio::runtime::Builder::new_multi_thread() @@ -111,7 +125,7 @@ fn main() -> Result<(), anyhow::Error> { } }; - match command { + match cli.command { Command::Crank { .. } => { let client = mango_client.clone(); rt.block_on(crank::runner(client, debugging_handle)) diff --git a/liquidator/example-config-mango-devnet.toml b/liquidator/example-config-mango-devnet.toml index 9943a4978..ba7ac4e28 100644 --- a/liquidator/example-config-mango-devnet.toml +++ b/liquidator/example-config-mango-devnet.toml @@ -9,5 +9,4 @@ snapshot_interval_secs = 240 parallel_rpc_requests = 10 get_multiple_accounts_count = 100 payer = "/root/.config/solana/mango-devnet.json" -admin = "/root/.config/solana/admin.json" mango_account_name = "Taker" diff --git a/liquidator/example-config.toml b/liquidator/example-config.toml index 77a16e41c..312cc8453 100644 --- a/liquidator/example-config.toml +++ b/liquidator/example-config.toml @@ -27,5 +27,4 @@ get_multiple_accounts_count = 100 # FUTURE: separate into a separate conf file? we are mixing feed + mango client conf in one place here # mango client specific payer = "/root/.config/solana/id.json" -admin = "/root/.config/solana/admin.json" mango_account_name = "Taker" diff --git a/liquidator/src/main.rs b/liquidator/src/main.rs index 8e94be0c8..cb7189189 100644 --- a/liquidator/src/main.rs +++ b/liquidator/src/main.rs @@ -66,7 +66,6 @@ pub struct Config { // FUTURE: split mango client and feed config // mango client specific pub payer: String, - pub admin: String, pub mango_account_name: String, } @@ -89,12 +88,13 @@ async fn main() -> anyhow::Result<()> { toml::from_str(&contents).unwrap() }; + let mango_group_id = Pubkey::from_str(&config.mango_group_id)?; + // // mango client setup // let mango_client = { let payer = keypair::read_keypair_file(&config.payer).unwrap(); - let admin = keypair::read_keypair_file(&config.admin).unwrap(); let rpc_url = config.rpc_http_url.to_owned(); let ws_url = rpc_url.replace("https", "wss"); @@ -106,7 +106,7 @@ async fn main() -> anyhow::Result<()> { cluster, commitment, payer, - admin, + mango_group_id, &config.mango_account_name, )?) }; @@ -123,7 +123,6 @@ async fn main() -> anyhow::Result<()> { // FUTURE: decouple feed setup and liquidator business logic // feed should send updates to a channel which liquidator can consume let mango_program_id = Pubkey::from_str(&config.mango_program_id)?; - let mango_group_id = Pubkey::from_str(&config.mango_group_id)?; solana_logger::setup_with_default("info"); info!("startup");