From 5266668bc255b5a7727c4b7f05e73e2486e974c6 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Fri, 6 May 2022 14:19:49 +0200 Subject: [PATCH] keeper: consume events Signed-off-by: microwavedcola1 --- keeper/src/consume_events.rs | 133 ++++++++++++++++++ keeper/src/crank.rs | 67 +++++++++ keeper/src/main.rs | 12 +- keeper/src/update_index.rs | 45 +----- .../src/instructions/perp_create_market.rs | 4 + programs/mango-v4/src/lib.rs | 2 + programs/mango-v4/src/state/perp_market.rs | 19 ++- programs/mango-v4/src/state/serum3_market.rs | 8 ++ .../tests/program_test/mango_client.rs | 1 + 9 files changed, 243 insertions(+), 48 deletions(-) create mode 100644 keeper/src/consume_events.rs create mode 100644 keeper/src/crank.rs diff --git a/keeper/src/consume_events.rs b/keeper/src/consume_events.rs new file mode 100644 index 000000000..4607f382d --- /dev/null +++ b/keeper/src/consume_events.rs @@ -0,0 +1,133 @@ +use std::time::Duration; + +use anchor_lang::{AccountDeserialize, __private::bytemuck::cast_ref}; + +use log::{error, info, warn}; +use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, PerpMarket}; + +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use tokio::time; + +use crate::MangoClient; + +pub async fn loop_blocking( + mango_client: &'static MangoClient, + pk: Pubkey, + perp_market: PerpMarket, +) { + let mut interval = time::interval(Duration::from_secs(5)); + loop { + interval.tick().await; + tokio::task::spawn_blocking(move || { + perform_operation(mango_client, pk, perp_market).expect("Something went wrong here..."); + }); + } +} + +pub fn perform_operation( + mango_client: &'static MangoClient, + pk: Pubkey, + perp_market: PerpMarket, +) -> anyhow::Result<()> { + let mut event_queue = match get_event_queue(mango_client, &perp_market) { + Ok(value) => value, + Err(value) => return value, + }; + + let mut ams_ = vec![]; + + for _ in 0..10 { + let event = match event_queue.peek_front() { + None => break, + Some(e) => e, + }; + match EventType::try_from(event.event_type)? { + EventType::Fill => { + let fill: &FillEvent = cast_ref(event); + ams_.push(AccountMeta { + pubkey: fill.maker, + is_signer: false, + is_writable: true, + }); + ams_.push(AccountMeta { + pubkey: fill.taker, + is_signer: false, + is_writable: true, + }); + } + EventType::Out => { + let out: &OutEvent = cast_ref(event); + ams_.push(AccountMeta { + pubkey: out.owner, + is_signer: false, + is_writable: true, + }); + } + EventType::Liquidate => {} + } + event_queue.pop_front()?; + } + + let sig_result = mango_client + .program() + .request() + .instruction(Instruction { + program_id: mango_v4::id(), + accounts: { + let mut ams = anchor_lang::ToAccountMetas::to_account_metas( + &mango_v4::accounts::PerpConsumeEvents { + group: perp_market.group, + perp_market: pk, + event_queue: perp_market.event_queue, + }, + None, + ); + ams.append(&mut ams_); + ams + }, + data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpConsumeEvents { + limit: 10, + }), + }) + .send(); + match sig_result { + Ok(sig) => { + info!( + "Crank: consume event for perp_market {:?} ix signature: {:?}", + format!("{: >6}", perp_market.name()), + sig + ); + } + Err(e) => error!("Crank: {:?}", e), + } + + Ok(()) +} + +fn get_event_queue( + mango_client: &MangoClient, + perp_market: &PerpMarket, +) -> Result> { + let event_queue_opt: Option = { + let res = mango_client + .rpc + .get_account_with_commitment(&perp_market.event_queue, mango_client.commitment); + + let res = match res { + Ok(x) => x, + Err(e) => { + warn!("{}", e); + return Err(Ok(())); + } + }; + + let data = res.value.unwrap().data; + let mut data_slice: &[u8] = &data; + AccountDeserialize::try_deserialize(&mut data_slice).ok() + }; + let mut event_queue = event_queue_opt.unwrap(); + Ok(event_queue) +} diff --git a/keeper/src/crank.rs b/keeper/src/crank.rs new file mode 100644 index 000000000..650210502 --- /dev/null +++ b/keeper/src/crank.rs @@ -0,0 +1,67 @@ +use crate::{consume_events, update_index, MangoClient}; + +use anyhow::ensure; + +use mango_v4::state::{Bank, PerpMarket}; + +use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}; + +use solana_sdk::{pubkey::Pubkey, signer::Signer}; + +pub async fn runner(mango_client: &'static MangoClient) -> Result<(), anyhow::Error> { + // Collect all banks for a group belonging to an admin + let banks = mango_client + .program() + .accounts::(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, + })])?; + + ensure!(!banks.is_empty()); + + let handles1 = banks + .iter() + .map(|(pk, bank)| update_index::loop_blocking(mango_client, *pk, *bank)) + .collect::>(); + + // Collect all perp markets for a group belonging to an admin + let perp_markets = + mango_client + .program() + .accounts::(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, + })])?; + + ensure!(!perp_markets.is_empty()); + + let handles2 = perp_markets + .iter() + .map(|(pk, perp_market)| consume_events::loop_blocking(mango_client, *pk, *perp_market)) + .collect::>(); + + futures::join!( + futures::future::join_all(handles1), + futures::future::join_all(handles2) + ); + + Ok(()) +} diff --git a/keeper/src/main.rs b/keeper/src/main.rs index 07debd0f9..09d308d80 100644 --- a/keeper/src/main.rs +++ b/keeper/src/main.rs @@ -1,3 +1,5 @@ +mod consume_events; +mod crank; mod update_index; use std::env; @@ -92,7 +94,6 @@ struct Cli { command: Command, } -// future: more subcommands e.g. Liquidator #[derive(Subcommand)] enum Command { Crank {}, @@ -146,10 +147,9 @@ fn main() -> Result<(), anyhow::Error> { let cluster = Cluster::Custom(rpc_url, ws_url); let commitment = match command { Command::Crank { .. } => CommitmentConfig::confirmed(), - Command::Liquidator {} => CommitmentConfig::confirmed(), + Command::Liquidator {} => todo!(), }; - // let mango_client = Arc::new(MangoClient::new(cluster, commitment, payer, admin)); let mango_client: &'static _ = Box::leak(Box::new(MangoClient::new( cluster, commitment, payer, admin, ))); @@ -161,10 +161,12 @@ fn main() -> Result<(), anyhow::Error> { match command { Command::Crank { .. } => { - let x: Result<(), anyhow::Error> = rt.block_on(update_index::runner(mango_client)); + let x: Result<(), anyhow::Error> = rt.block_on(crank::runner(mango_client)); x.expect("Something went wrong here..."); } - Command::Liquidator { .. } => {} + Command::Liquidator { .. } => { + todo!() + } } Ok(()) diff --git a/keeper/src/update_index.rs b/keeper/src/update_index.rs index b5eecf05a..911c3471b 100644 --- a/keeper/src/update_index.rs +++ b/keeper/src/update_index.rs @@ -1,53 +1,14 @@ -use std::{time::Duration}; - - -use anyhow::ensure; +use std::time::Duration; use log::{error, info}; use mango_v4::state::Bank; -use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}; - -use solana_sdk::{ - instruction::Instruction, - pubkey::Pubkey, - signer::{Signer}, -}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey}; use tokio::time; use crate::MangoClient; -pub async fn runner(mango_client: &'static MangoClient) -> Result<(), anyhow::Error> { - // Collect all banks for a group belonging to an admin - let banks = mango_client - .program() - .accounts::(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, - })])?; - - ensure!(!banks.is_empty()); - - let handles = banks - .iter() - .map(|(pk, bank)| loop_blocking(mango_client, *pk, *bank)) - .collect::>(); - - futures::join!(futures::future::join_all(handles)); - - Ok(()) -} - -async fn loop_blocking(mango_client: &'static MangoClient, pk: Pubkey, bank: Bank) { +pub async fn loop_blocking(mango_client: &'static MangoClient, pk: Pubkey, bank: Bank) { let mut interval = time::interval(Duration::from_secs(5)); loop { interval.tick().await; diff --git a/programs/mango-v4/src/instructions/perp_create_market.rs b/programs/mango-v4/src/instructions/perp_create_market.rs index b788cda82..0b1af3ffb 100644 --- a/programs/mango-v4/src/instructions/perp_create_market.rs +++ b/programs/mango-v4/src/instructions/perp_create_market.rs @@ -3,6 +3,7 @@ use fixed::types::I80F48; use crate::error::MangoError; use crate::state::*; +use crate::util::fill16_from_str; #[derive(Accounts)] #[instruction(perp_market_index: PerpMarketIndex)] @@ -43,6 +44,7 @@ pub struct PerpCreateMarket<'info> { pub fn perp_create_market( ctx: Context, perp_market_index: PerpMarketIndex, + name: String, base_token_index_opt: Option, quote_token_index: TokenIndex, quote_lot_size: i64, @@ -57,6 +59,7 @@ pub fn perp_create_market( ) -> Result<()> { let mut perp_market = ctx.accounts.perp_market.load_init()?; *perp_market = PerpMarket { + name: fill16_from_str(name)?, group: ctx.accounts.group.key(), oracle: ctx.accounts.oracle.key(), bids: ctx.accounts.bids.key(), @@ -78,6 +81,7 @@ pub fn perp_create_market( perp_market_index, base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(), quote_token_index, + reserved: Default::default(), }; let mut bids = ctx.accounts.bids.load_init()?; diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index ec60008e0..60ee8409d 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -181,6 +181,7 @@ pub mod mango_v4 { pub fn perp_create_market( ctx: Context, perp_market_index: PerpMarketIndex, + name: String, base_token_index_opt: Option, quote_token_index: TokenIndex, quote_lot_size: i64, @@ -196,6 +197,7 @@ pub mod mango_v4 { instructions::perp_create_market( ctx, perp_market_index, + name, base_token_index_opt, quote_token_index, quote_lot_size, diff --git a/programs/mango-v4/src/state/perp_market.rs b/programs/mango-v4/src/state/perp_market.rs index a45c1b209..f7ec18d85 100644 --- a/programs/mango-v4/src/state/perp_market.rs +++ b/programs/mango-v4/src/state/perp_market.rs @@ -1,5 +1,8 @@ +use std::mem::size_of; + use anchor_lang::prelude::*; use fixed::types::I80F48; +use static_assertions::const_assert_eq; use crate::state::orderbook::order_type::Side; use crate::state::TokenIndex; @@ -9,6 +12,8 @@ pub type PerpMarketIndex = u16; #[account(zero_copy)] pub struct PerpMarket { + pub name: [u8; 16], + pub group: Pubkey, pub oracle: Pubkey, @@ -60,6 +65,7 @@ pub struct PerpMarket { /// PDA bump pub bump: u8, + pub reserved: [u8; 1], /// Lookup indices pub perp_market_index: PerpMarketIndex, @@ -69,8 +75,19 @@ pub struct PerpMarket { pub quote_token_index: TokenIndex, } +const_assert_eq!( + size_of::(), + 16 + 32 * 5 + 8 * 2 + 16 * 7 + 8 * 2 + 16 + 8 +); +const_assert_eq!(size_of::() % 8, 0); + impl PerpMarket { - /// TODO why is this based on price? + pub fn name(&self) -> &str { + std::str::from_utf8(&self.name) + .unwrap() + .trim_matches(char::from(0)) + } + pub fn gen_order_id(&mut self, side: Side, price: i64) -> i128 { self.seq_num += 1; diff --git a/programs/mango-v4/src/state/serum3_market.rs b/programs/mango-v4/src/state/serum3_market.rs index 6b0aac872..59f8edfa7 100644 --- a/programs/mango-v4/src/state/serum3_market.rs +++ b/programs/mango-v4/src/state/serum3_market.rs @@ -23,6 +23,14 @@ pub struct Serum3Market { const_assert_eq!(size_of::(), 16 + 32 * 3 + 3 * 2 + 1 + 1); const_assert_eq!(size_of::() % 8, 0); +impl Serum3Market { + pub fn name(&self) -> &str { + std::str::from_utf8(&self.name) + .unwrap() + .trim_matches(char::from(0)) + } +} + #[macro_export] macro_rules! serum_market_seeds { ( $acc:expr ) => { diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 1f2c11d70..8beaf25ec 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -1293,6 +1293,7 @@ impl<'keypair> ClientInstruction for PerpCreateMarketInstruction<'keypair> { ) -> (Self::Accounts, instruction::Instruction) { let program_id = mango_v4::id(); let instruction = Self::Instruction { + name: "UUU-PERP".to_string(), perp_market_index: self.perp_market_index, base_token_index_opt: Option::from(self.base_token_index), quote_token_index: self.quote_token_index,