Skip to content

Commit

Permalink
example jito position calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
0xripleys committed Oct 27, 2023
1 parent 77dc842 commit b25a441
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions token-lending/sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ num-derive = "0.3"
num-traits = "0.2"
pyth-sdk-solana = "0.7.0"
solana-program = ">=1.9, < 1.15"
solana-client = ">=1.9, < 1.15"
spl-token = { version = "3.2.0", features=["no-entrypoint"] }
static_assertions = "1.1.0"
thiserror = "1.0"
Expand Down
74 changes: 74 additions & 0 deletions token-lending/sdk/examples/jito.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use solana_client::rpc_client::RpcClient;
use solana_sdk::pubkey;
use std::collections::HashMap;

use solend_sdk::{
offchain_utils::{
get_solend_accounts_as_map, offchain_refresh_obligation, offchain_refresh_reserve_interest,
},
solend_mainnet,
};

#[derive(Debug, Clone, Default)]
struct Position {
pub deposit_balance: u64,
pub borrow_balance: u64,
}

pub fn main() {
let rpc_url = std::env::var("RPC_URL")
.unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
let rpc_client = RpcClient::new(rpc_url);

let mut accounts = get_solend_accounts_as_map(&solend_mainnet::id(), &rpc_client).unwrap();

// update solend-specific interest variables
let slot = rpc_client.get_slot().unwrap();
for reserve in accounts.reserves.values_mut() {
let _ = offchain_refresh_reserve_interest(reserve, slot);
}

for obligation in accounts.obligations.values_mut() {
offchain_refresh_obligation(obligation, &accounts.reserves).unwrap();
}

// calculate jitosol balances per user across all pools
let jitosol = pubkey!("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn");
let mut user_to_position = HashMap::new();

for obligation in accounts.obligations.values() {
for deposit in &obligation.deposits {
let deposit_reserve = accounts.reserves.get(&deposit.deposit_reserve).unwrap();
if deposit_reserve.liquidity.mint_pubkey == jitosol {
let position = user_to_position
.entry(obligation.owner)
.or_insert(Position::default());

// convert cJitoSol to JitoSol
let cjitosol_deposited = deposit.deposited_amount;
let jitosol_deposited = deposit_reserve
.collateral_exchange_rate()
.unwrap()
.collateral_to_liquidity(cjitosol_deposited)
.unwrap();

position.deposit_balance += jitosol_deposited;
}
}

for borrow in &obligation.borrows {
let borrow_reserve = accounts.reserves.get(&borrow.borrow_reserve).unwrap();
if borrow_reserve.liquidity.mint_pubkey == jitosol {
let position = user_to_position
.entry(obligation.owner)
.or_insert(Position::default());

position.borrow_balance +=
borrow.borrowed_amount_wads.try_round_u64().unwrap();
}
}
}

println!("Done refreshing");
println!("{:#?}", user_to_position);
}
1 change: 1 addition & 0 deletions token-lending/sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod instruction;
pub mod math;
pub mod oracles;
pub mod state;
pub mod offchain_utils;

// Export current sdk types for downstream users building with a different sdk version
pub use solana_program;
Expand Down
185 changes: 185 additions & 0 deletions token-lending/sdk/src/offchain_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#![allow(missing_docs)]
use crate::{self as solend_program, error::LendingError};
use pyth_sdk_solana::Price;
use solana_client::rpc_client::RpcClient;
use solana_program::slot_history::Slot;
// use pyth_sdk_solana;
use solana_program::{
account_info::AccountInfo, msg, program_error::ProgramError, sysvar::clock::Clock,
};
use std::{convert::TryInto, result::Result};

use crate::{state::LastUpdate, NULL_PUBKEY};
use std::time::Duration;

use solana_program::{program_pack::Pack, pubkey::Pubkey};

use crate::math::{Decimal, Rate, TryAdd, TryMul};
use std::collections::HashSet;

use crate::state::{LendingMarket, Obligation, Reserve};
use std::{collections::HashMap, error::Error};

#[derive(Debug, Clone)]
pub struct SolendAccounts {
pub lending_markets: HashMap<Pubkey, LendingMarket>,
pub reserves: HashMap<Pubkey, Reserve>,
pub obligations: HashMap<Pubkey, Obligation>,
}

pub fn get_solend_accounts_as_map(
lending_program_id: &Pubkey,
client: &RpcClient,
) -> Result<SolendAccounts, Box<dyn Error>> {
let accounts = client.get_program_accounts(lending_program_id)?;

let (lending_markets, reserves, obligations) = accounts.into_iter().fold(
(HashMap::new(), HashMap::new(), HashMap::new()),
|(mut lending_markets, mut reserves, mut obligations), (pubkey, account)| {
match account.data.len() {
Obligation::LEN => {
if let Ok(o) = Obligation::unpack(&account.data) {
if !o.borrows.is_empty() {
obligations.insert(pubkey, o);
}
}
}
Reserve::LEN => {
if let Ok(r) = Reserve::unpack(&account.data) {
reserves.insert(pubkey, r);
}
}
LendingMarket::LEN => {
if let Ok(l) = LendingMarket::unpack(&account.data) {
lending_markets.insert(pubkey, l);
}
}
_ => (),
};
(lending_markets, reserves, obligations)
},
);

Ok(SolendAccounts {
lending_markets,
reserves,
obligations,
})
}

pub fn offchain_refresh_reserve_interest(
reserve: &mut Reserve,
slot: Slot,
) -> Result<(), Box<dyn Error>> {
reserve.accrue_interest(slot)?;
reserve.last_update = LastUpdate { slot, stale: false };

Ok(())
}

pub fn offchain_refresh_reserve(
_pubkey: &Pubkey,
reserve: &mut Reserve,
slot: Slot,
prices: &HashMap<Pubkey, Option<Decimal>>,
) -> Result<(), Box<dyn Error>> {
let pyth_oracle = reserve.liquidity.pyth_oracle_pubkey;
let switchboard_oracle = reserve.liquidity.switchboard_oracle_pubkey;

let price = if let Some(Some(price)) = prices.get(&pyth_oracle) {
if pyth_oracle != NULL_PUBKEY {
Some(*price)
} else {
None
}
} else if let Some(Some(price)) = prices.get(&switchboard_oracle) {
if switchboard_oracle != NULL_PUBKEY {
Some(*price)
} else {
None
}
} else {
None
};

if let Some(price) = price {
reserve.liquidity.market_price = price;
} else {
return Err("No price".into());
}

reserve.accrue_interest(slot)?;
reserve.last_update = LastUpdate { slot, stale: false };

Ok(())
}

pub fn offchain_refresh_obligation(
o: &mut Obligation,
reserves: &HashMap<Pubkey, Reserve>,
) -> Result<(), Box<dyn Error>> {
o.deposited_value = Decimal::zero();
o.super_unhealthy_borrow_value = Decimal::zero();
o.unhealthy_borrow_value = Decimal::zero();
o.borrowed_value = Decimal::zero();

for collateral in &mut o.deposits {
let deposit_reserve = reserves
.get(&collateral.deposit_reserve)
.ok_or(ProgramError::Custom(35))?;

let liquidity_amount = deposit_reserve
.collateral_exchange_rate()?
.decimal_collateral_to_liquidity(collateral.deposited_amount.into())?;

let market_value = deposit_reserve.market_value(liquidity_amount)?;
let liquidation_threshold_rate =
Rate::from_percent(deposit_reserve.config.liquidation_threshold);
let max_liquidation_threshold_rate =
Rate::from_percent(deposit_reserve.config.max_liquidation_threshold);

collateral.market_value = market_value;

o.deposited_value = o.deposited_value.try_add(market_value)?;
o.unhealthy_borrow_value = o
.unhealthy_borrow_value
.try_add(market_value.try_mul(liquidation_threshold_rate)?)?;
o.super_unhealthy_borrow_value = o
.super_unhealthy_borrow_value
.try_add(market_value.try_mul(max_liquidation_threshold_rate)?)?;
}

let mut max_borrow_weight = None;

for (index, liquidity) in o.borrows.iter_mut().enumerate() {
let borrow_reserve = reserves.get(&liquidity.borrow_reserve).unwrap();
liquidity.accrue_interest(borrow_reserve.liquidity.cumulative_borrow_rate_wads)?;

let market_value = borrow_reserve.market_value(liquidity.borrowed_amount_wads)?;
liquidity.market_value = market_value;

o.borrowed_value = o
.borrowed_value
.try_add(market_value.try_mul(borrow_reserve.borrow_weight())?)?;

let borrow_weight_and_pubkey = (
borrow_reserve.config.added_borrow_weight_bps,
borrow_reserve.liquidity.mint_pubkey,
);

max_borrow_weight = match max_borrow_weight {
None => Some((borrow_weight_and_pubkey, index)),
Some((max_borrow_weight_and_pubkey, _)) => {
if liquidity.borrowed_amount_wads > Decimal::zero()
&& borrow_weight_and_pubkey > max_borrow_weight_and_pubkey
{
Some((borrow_weight_and_pubkey, index))
} else {
max_borrow_weight
}
}
};
}

Ok(())
}

0 comments on commit b25a441

Please sign in to comment.