Merge branch 'dev'
This commit is contained in:
commit
bb6621c308
|
@ -13,6 +13,13 @@ jobs:
|
|||
build:
|
||||
name: Soteria
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
program: [ 'programs/mango-v4', 'programs/margin-trade' ]
|
||||
env:
|
||||
PROGRAM_PATH: ${{ matrix.program }}
|
||||
|
||||
steps:
|
||||
- name: Check-out repo
|
||||
uses: actions/checkout@v2
|
||||
|
@ -52,5 +59,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- name: Run Soteria
|
||||
run: soteria -analyzeAll .
|
||||
run: |
|
||||
cd ${{ matrix.program }}
|
||||
soteria -analyzeAll .
|
||||
shell: bash
|
||||
|
|
|
@ -16,3 +16,4 @@ yarn-error.log
|
|||
programs/margin-trade/src/lib-expanded.rs
|
||||
programs/mango-v4/src/lib-expanded.rs
|
||||
|
||||
keeper/.env
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
RPC_URL=
|
||||
PAYER_KEYPAIR=
|
||||
ADMIN_KEYPAIR=
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
|
@ -1,66 +1,206 @@
|
|||
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 anyhow::ensure;
|
||||
use clap::{Parser, Subcommand};
|
||||
use log::{error, 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
|
||||
// TODO
|
||||
// - may be nice to have one-shot cranking as well as the interval cranking
|
||||
// - doing a gPA for all banks call every 10millis may be too often,
|
||||
// might make sense that we maintain a service when users should query group for changes
|
||||
// - I'm really annoyed about Keypair not being clonable. Seems everyone works around that manually. Should make a PR to solana to newtype it and provide that function.
|
||||
// keypair_from_arg_or_env could be a function
|
||||
|
||||
/// 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?;
|
||||
}
|
||||
}
|
||||
|
||||
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 {}),
|
||||
};
|
||||
ensure!(!banks.is_empty());
|
||||
|
||||
let sig = client.request().instruction(update_index_ix).send()?;
|
||||
println!("update_index: {:?}", sig);
|
||||
// Call update index ix
|
||||
for bank in banks {
|
||||
let sig_result = 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();
|
||||
match sig_result {
|
||||
Ok(sig) => {
|
||||
info!("Crank: update_index ix signature: {:?}", sig);
|
||||
}
|
||||
Err(e) => error!("Crank: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue