Mc/keeper next (#45)

* cmd line args / env vars, make generic over admin key

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* simplify

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* add logging

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2022-04-13 16:41:15 +02:00 committed by GitHub
parent 30004eff67
commit 7c5e80ce3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 6287 additions and 55 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ yarn-error.log
programs/margin-trade/src/lib-expanded.rs
programs/mango-v4/src/lib-expanded.rs
keeper/.env

94
Cargo.lock generated
View File

@ -797,12 +797,42 @@ dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"strsim 0.8.0",
"textwrap 0.11.0",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap"
version = "3.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim 0.10.0",
"termcolor",
"textwrap 0.15.0",
]
[[package]]
name = "clap_derive"
version = "3.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
dependencies = [
"heck 0.4.0",
"proc-macro-error",
"proc-macro2 1.0.37",
"quote 1.0.17",
"syn 1.0.91",
]
[[package]]
name = "cmake"
version = "0.1.48"
@ -1153,6 +1183,12 @@ dependencies = [
"syn 0.15.44",
]
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "ed25519"
version = "1.4.1"
@ -1275,6 +1311,19 @@ dependencies = [
"syn 1.0.91",
]
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.9.0"
@ -2130,6 +2179,10 @@ dependencies = [
"anchor-client",
"anchor-lang",
"anyhow",
"clap 3.1.8",
"dotenv",
"env_logger 0.8.4",
"log",
"mango-v4",
"serde",
"serde_json",
@ -2315,7 +2368,7 @@ dependencies = [
"bincode",
"bytemuck",
"checked_math",
"env_logger",
"env_logger 0.9.0",
"fixed",
"fixed-macro",
"itertools 0.10.3",
@ -2698,6 +2751,15 @@ dependencies = [
"thiserror",
]
[[package]]
name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "ouroboros"
version = "0.14.2"
@ -4067,7 +4129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d151864e610f549dbaf236cfff8eccdc5c8bf55c1f6e82d87228284d566d7e"
dependencies = [
"chrono",
"clap",
"clap 2.34.0",
"rpassword",
"solana-perf",
"solana-remote-wallet",
@ -4104,7 +4166,7 @@ dependencies = [
"bincode",
"bs58 0.4.0",
"bytes",
"clap",
"clap 2.34.0",
"crossbeam-channel",
"futures 0.3.21",
"futures-util",
@ -4195,7 +4257,7 @@ checksum = "b3bc98b1e0150adb2680a3d7189edbc7856b53c5838bac89018b93a739ccae8f"
dependencies = [
"bincode",
"byteorder",
"clap",
"clap 2.34.0",
"crossbeam-channel",
"log",
"serde",
@ -4253,7 +4315,7 @@ checksum = "c64eb26bdcfb275dd88ad78ed747b131ac86f78bd88fcf252266598fc078847d"
dependencies = [
"bincode",
"bv",
"clap",
"clap 2.34.0",
"crossbeam-channel",
"flate2",
"indexmap",
@ -4350,7 +4412,7 @@ version = "1.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75bee4762c5db25ae6d665abaddb93d1b5bb97944c0660639431057a3e336e8"
dependencies = [
"env_logger",
"env_logger 0.9.0",
"lazy_static",
"log",
]
@ -4397,7 +4459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da87e633efb261bcd877e392c452459dfc00352539d77d78000b4b84fbcdeffb"
dependencies = [
"bincode",
"clap",
"clap 2.34.0",
"crossbeam-channel",
"log",
"nix",
@ -4862,7 +4924,7 @@ version = "1.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f383814106f44f274c49ed9d947263d7fccbfe33297366a8071964adf9ae7eb3"
dependencies = [
"clap",
"clap 2.34.0",
"libc",
"log",
"nix",
@ -5142,6 +5204,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.4.1"
@ -5295,6 +5363,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.30"

3
keeper/.env.example Normal file
View File

@ -0,0 +1,3 @@
RPC_URL=
PAYER_KEYPAIR=
ADMIN_KEYPAIR=

6017
keeper/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,12 @@ edition = "2021"
anchor-client = "0.22.0"
anchor-lang = "0.22.0"
anyhow = "1.0"
clap = { version = "3.1.8", features = ["derive", "env"] }
dotenv = "0.15.0"
mango-v4 = { path = "../programs/mango-v4" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
shellexpand = "2.1"
solana-account-decoder = "1.9.5"
solana-client = "1.9.5"
solana-program = "1.9.5"
@ -17,6 +22,5 @@ solana-rpc = "1.9.5"
solana-sdk = "1.9.5"
solana-transaction-status = "1.9.5"
tokio = { version = "1.17", features = ["rt-multi-thread", "time", "macros", "sync"] }
shellexpand = "2.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4.0"
env_logger = "0.8.4"

View File

@ -1,66 +1,199 @@
use std::{rc::Rc, str::FromStr, time::Duration};
use std::{env, time::Duration};
use solana_sdk::{instruction::Instruction, signature::Keypair};
use anchor_client::{Client, Cluster, Program};
use clap::{Parser, Subcommand};
use log::info;
use mango_v4::state::Bank;
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType};
use solana_sdk::signature::Keypair;
use solana_sdk::{
commitment_config::CommitmentConfig,
instruction::Instruction,
pubkey::Pubkey,
signer::{keypair, Signer},
};
use tokio::time;
// TODO:
// cmd line args with defaults
// make keypair, rpc server, net, etc. configurable
// expand to various tasks e.g. crank event queue, crank banks, run liquidators
// support multiple workers
// logging facility
// robust error handling
/// Wrapper around anchor client with some mango specific useful things
pub struct MangoClient {
pub program: Program,
pub rpc: RpcClient,
pub cluster: Cluster,
pub commitment: CommitmentConfig,
pub payer: Keypair,
pub admin: Keypair,
}
impl MangoClient {
pub fn new(
cluster: Cluster,
commitment: CommitmentConfig,
payer: Keypair,
admin: Keypair,
) -> Self {
let program = Client::new_with_options(
cluster.clone(),
std::rc::Rc::new(Keypair::from_bytes(&payer.to_bytes()).unwrap()),
commitment,
)
.program(mango_v4::ID);
let rpc = program.rpc();
Self {
program,
rpc,
cluster,
commitment,
admin,
payer,
}
}
pub fn payer(&self) -> Pubkey {
self.payer.pubkey()
}
pub fn admin(&self) -> Pubkey {
self.payer.pubkey()
}
}
#[derive(Parser)]
#[clap()]
struct Cli {
#[clap(short, long, env = "RPC_URL")]
rpc_url: Option<String>,
#[clap(short, long, env = "PAYER_KEYPAIR")]
payer: Option<std::path::PathBuf>,
#[clap(short, long, env = "ADMIN_KEYPAIR")]
admin: Option<std::path::PathBuf>,
#[clap(subcommand)]
command: Command,
}
// future: more subcommands e.g. Liquidator
#[derive(Subcommand)]
enum Command {
Crank {},
}
fn main() {
env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
);
dotenv::dotenv().ok();
let Cli {
rpc_url,
payer,
admin,
command,
} = 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())),
None => match env::var("PAYER_KEYPAIR").ok() {
Some(k) => {
keypair::read_keypair(&mut k.as_bytes()).expect("Failed to parse $PAYER_KEYPAIR")
}
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 => match env::var("ADMIN_KEYPAIR").ok() {
Some(k) => {
keypair::read_keypair(&mut k.as_bytes()).expect("Failed to parse $ADMIN_KEYPAIR")
}
None => panic!("Admin keypair not provided..."),
},
};
let rpc_url = match rpc_url {
Some(rpc_url) => rpc_url,
None => match env::var("RPC_URL").ok() {
Some(rpc_url) => rpc_url,
None => panic!("Rpc URL not provided..."),
},
};
let ws_url = rpc_url.replace("https", "wss");
let cluster = Cluster::Custom(rpc_url, ws_url);
let commitment = match command {
Command::Crank { .. } => CommitmentConfig::processed(),
};
let mango_client = MangoClient::new(cluster, commitment, payer, admin);
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(update_index_runner())
// future: match on various subcommands
rt.block_on(update_index_runner(&mango_client))
.expect("Something went wrong here...");
}
pub async fn update_index_runner() -> anyhow::Result<()> {
pub async fn update_index_runner(mango_client: &MangoClient) -> anyhow::Result<()> {
// future: make configurable
let mut interval = time::interval(Duration::from_millis(10));
loop {
interval.tick().await;
update_index().await?;
update_index(mango_client)
.await
.expect("Something went wrong here...");
}
}
pub async fn update_index() -> anyhow::Result<()> {
let keypair = load_default_keypair()?;
let rpc = "https://mango.devnet.rpcpool.com".to_owned();
let wss = rpc.replace("https", "wss");
let connection =
anchor_client::Client::new(anchor_client::Cluster::Custom(rpc, wss), Rc::new(keypair));
let client = connection.program(mango_v4::ID);
pub async fn update_index(mango_client: &MangoClient) -> anyhow::Result<()> {
// Collect all banks for a group belonging to an admin
let banks = mango_client
.program
.accounts::<Bank>(vec![RpcFilterType::Memcmp(Memcmp {
offset: 24,
bytes: MemcmpEncodedBytes::Base58({
// find group belonging to admin
Pubkey::find_program_address(
&["Group".as_ref(), mango_client.admin.pubkey().as_ref()],
&mango_client.program.id(),
)
.0
.to_string()
}),
encoding: None,
})])?;
let update_index_ix = Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::UpdateIndex {
bank: anchor_lang::prelude::Pubkey::from_str(
"9xmZdkWbYNYsBshr7PwjhU8c7mmrvzmocu8dSQeNCKTG",
)?,
},
None,
),
data: anchor_lang::InstructionData::data(&mango_v4::instruction::UpdateIndex {}),
};
assert!(
!banks.is_empty(),
"Found 0 banks, some part of the configuration might be wrong..."
);
let sig = client.request().instruction(update_index_ix).send()?;
println!("update_index: {:?}", sig);
// Call update index ix
for bank in banks {
let sig = mango_client
.program
.request()
.instruction(Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::UpdateIndex { bank: bank.0 },
None,
),
data: anchor_lang::InstructionData::data(&mango_v4::instruction::UpdateIndex {}),
})
.send()?;
info!("update_index ix signature: {:?}", sig);
}
Ok(())
}
fn load_default_keypair() -> anyhow::Result<Keypair> {
let keypair_path = shellexpand::tilde("~/.config/solana/mango-devnet.json");
let keypair_data = std::fs::read_to_string(keypair_path.to_string())?;
let keypair_bytes: Vec<u8> = serde_json::from_str(&keypair_data)?;
let keypair = Keypair::from_bytes(&keypair_bytes)?;
Ok(keypair)
}