Skip to content

Commit

Permalink
Creating data structures for LM
Browse files Browse the repository at this point in the history
  • Loading branch information
vanitymnm committed Feb 25, 2025
1 parent 9ffd16d commit 49a6c03
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 12 deletions.
137 changes: 137 additions & 0 deletions token-lending/sdk/src/state/liquidity_mining.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use crate::math::Decimal;
use solana_program::pubkey::Pubkey;

/// Determines the size of [PoolRewardManager]
const MAX_REWARDS: usize = 44;

/// Each reserve has two managers:
/// - one for deposits
/// - one for borrows
pub struct PoolRewardManager {
/// Is updated when we change user shares in the reserve.
pub total_shares: u64,
/// Monotonically increasing time taken from clock sysvar.
pub last_update_time_secs: u64,
/// New [PoolReward] are added to the first vacant slot.
pub pool_rewards: [PoolRewardSlot; MAX_REWARDS],
}

/// Each pool reward gets an ID which is monotonically increasing with each
/// new reward added to the pool at the particular slot.
///
/// This helps us distinguish between two distinct rewards in the same array
/// index across time.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PoolRewardId(pub u32);

/// # (Un)Packing
/// This is unpacked representation.
/// When packing we use the [PoolReward] `reward_mint` to determine whether the
/// reward is vacant or not to save space.
///
/// If the pubkey is eq to default pubkey then slot is vacant.
pub enum PoolRewardSlot {
/// New reward can be added to this slot.
Vacant {
/// Increment this ID when adding new [PoolReward].
last_pool_reward_id: PoolRewardId,
},
/// Reward has not been closed yet.
Occupied(PoolReward),
}

/// Tracks rewards in a specific mint over some period of time.
pub struct PoolReward {
/// Unique ID for this slot that has never been used before, and will never
/// be used again.
pub id: PoolRewardId,
/// # (Un)Packing
/// When we pack the reward we set this to default pubkey for vacant slots.
pub vault: Pubkey,
/// Monotonically increasing time taken from clock sysvar.
pub start_time_secs: u64,
/// For how long (since start time) will this reward be releasing tokens.
pub duration_secs: u32,
/// Total token amount to distribute.
/// The token account that holds the rewards holds at least this much in
/// the beginning.
pub total_rewards: u64,
/// How many users are still tracking this reward.
/// Once this reaches zero we can close this reward.
/// There's a permission-less ix with which user rewards can be distributed
/// that's used for cranking remaining rewards.
pub num_user_reward_managers: u64,
/// Amount of rewards that have been made available to users.
///
/// We keep adding `(total_rewards * time_passed) / (total_time)` every
/// time someone interacts with the manager
/// ([update_pool_reward_manager]).
pub allocated_rewards: Decimal,
/// We keep adding `(unlocked_rewards) / (total_shares)` every time
/// someone interacts with the manager ([update_pool_reward_manager])
/// where
/// `unlocked_rewards = (total_rewards * time_passed) / (total_time)`
pub cumulative_rewards_per_share: Decimal,
}

/// Tracks user's LM rewards for a specific pool (reserve.)
pub struct UserRewardManager {
/// User cannot both borrow and deposit in the same reserve.
/// This manager is unique for this reserve within the [Obligation].
///
/// We know whether to use [crate::state::Reserve]'s
/// `deposits_pool_reward_manager` or `borrows_pool_reward_manager` based on
/// this field.
///
/// One optimization we could make is to link the [UserRewardManager] via
/// index which would save 32 bytes per [UserRewardManager].
/// However, that does make the program logic more error prone.
pub reserve: Pubkey,
/// For deposits, this is the amount of collateral token user has in
/// their obligation deposit.
///
/// For borrows, this is (borrow_amount / cumulative_borrow_rate) user
/// has in their obligation borrow.
pub share: u64,
/// Monotonically increasing time taken from clock sysvar.
pub last_update_time_secs: u64,
/// The index of each reward is important.
/// It will match the index in the [PoolRewardManager] of the reserve.
pub rewards: Vec<Option<UserReward>>,
}

/// Track user rewards for a specific [PoolReward].
pub struct UserReward {
/// Each pool reward gets an ID which is monotonically increasing with each
/// new reward added to the pool.
pub pool_reward_id: PoolRewardId,
/// Before [UserReward.cumulative_rewards_per_share] is copied we find
/// time difference between current global rewards and last user update
/// rewards:
/// [PoolReward.cumulative_rewards_per_share] - [UserReward.cumulative_rewards_per_share]
///
/// Then, we multiply that difference by [UserRewardManager.share] and
/// add the result to this counter.
pub earned_rewards: Decimal,
/// copied from [PoolReward.cumulative_rewards_per_share] at the time of the last update
pub cumulative_rewards_per_share: Decimal,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_fits_reserve_realloc_into_single_ix() {
const MAX_REALLOC: usize = 10 * 1024;

let size_of_discriminant = 1;
let const_size_of_pool_manager = 8 + 8;
let required_realloc = size_of_discriminant
+ const_size_of_pool_manager
+ 2 * MAX_REWARDS * std::mem::size_of::<PoolReward>();

println!("assert {required_realloc} <= {MAX_REALLOC}");
assert!(required_realloc <= MAX_REALLOC);
}
}
25 changes: 18 additions & 7 deletions token-lending/sdk/src/state/obligation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ pub struct Obligation {
pub closeable: bool,
}

/// These are the two foundational user interactions in a borrow-lending protocol.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PositionKind {
/// User is providing liquidity.
Deposit = 0,
/// User is owing liquidity.
Borrow = 1,
}

impl Obligation {
/// Create a new obligation
pub fn new(params: InitObligationParams) -> Self {
Expand Down Expand Up @@ -414,13 +423,14 @@ impl ObligationLiquidity {

const OBLIGATION_COLLATERAL_LEN: usize = 88; // 32 + 8 + 16 + 32
const OBLIGATION_LIQUIDITY_LEN: usize = 112; // 32 + 16 + 16 + 16 + 32
const OBLIGATION_LEN: usize = 1300; // 1 + 8 + 1 + 32 + 32 + 16 + 16 + 16 + 16 + 64 + 1 + 1 + (88 * 1) + (112 * 9)
// @TODO: break this up by obligation / collateral / liquidity https://git.io/JOCca
const OBLIGATION_LEN_V1: usize = 1300; // 1 + 8 + 1 + 32 + 32 + 16 + 16 + 16 + 16 + 64 + 1 + 1 + (88 * 1) + (112 * 9)
// @TODO: break this up by obligation / collateral / liquidity https://git.io/JOCca
impl Pack for Obligation {
const LEN: usize = OBLIGATION_LEN;
const LEN: usize = OBLIGATION_LEN_V1;

// @v2.1.0 TODO: pack vec of user reward managers
fn pack_into_slice(&self, dst: &mut [u8]) {
let output = array_mut_ref![dst, 0, OBLIGATION_LEN];
let output = array_mut_ref![dst, 0, OBLIGATION_LEN_V1];
#[allow(clippy::ptr_offset_with_cast)]
let (
version,
Expand Down Expand Up @@ -527,9 +537,10 @@ impl Pack for Obligation {
}
}

/// Unpacks a byte buffer into an [ObligationInfo](struct.ObligationInfo.html).
/// Unpacks a byte buffer into an [Obligation].
// @v2.1.0 TODO: unpack vector of optional user reward managers
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let input = array_ref![src, 0, OBLIGATION_LEN];
let input = array_ref![src, 0, OBLIGATION_LEN_V1];
#[allow(clippy::ptr_offset_with_cast)]
let (
version,
Expand Down Expand Up @@ -693,7 +704,7 @@ mod test {
closeable: rng.gen(),
};

let mut packed = [0u8; OBLIGATION_LEN];
let mut packed = [0u8; OBLIGATION_LEN_V1];
Obligation::pack(obligation.clone(), &mut packed).unwrap();
let unpacked = Obligation::unpack(&packed).unwrap();
assert_eq!(obligation, unpacked);
Expand Down
15 changes: 10 additions & 5 deletions token-lending/sdk/src/state/reserve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1228,13 +1228,15 @@ impl IsInitialized for Reserve {
}
}

const RESERVE_LEN: usize = 619; // 1 + 8 + 1 + 32 + 32 + 1 + 32 + 32 + 32 + 8 + 16 + 16 + 16 + 32 + 8 + 32 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 8 + 8 + 1 + 8 + 8 + 32 + 1 + 1 + 16 + 230
/// This is the size of the account _before_ LM feature was added.
const RESERVE_LEN_V1: usize = 619; // 1 + 8 + 1 + 32 + 32 + 1 + 32 + 32 + 32 + 8 + 16 + 16 + 16 + 32 + 8 + 32 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 8 + 8 + 1 + 8 + 8 + 32 + 1 + 1 + 16 + 230
impl Pack for Reserve {
const LEN: usize = RESERVE_LEN;
const LEN: usize = RESERVE_LEN_V1;

// @TODO: break this up by reserve / liquidity / collateral / config https://git.io/JOCca
// @v2.1.0 TODO: pack deposits_pool_reward_manager and borrows_pool_reward_manager
fn pack_into_slice(&self, output: &mut [u8]) {
let output = array_mut_ref![output, 0, RESERVE_LEN];
let output = array_mut_ref![output, 0, RESERVE_LEN_V1];
#[allow(clippy::ptr_offset_with_cast)]
let (
version,
Expand Down Expand Up @@ -1422,9 +1424,12 @@ impl Pack for Reserve {
pack_decimal(self.attributed_borrow_value, attributed_borrow_value);
}

/// Unpacks a byte buffer into a [ReserveInfo](struct.ReserveInfo.html).
/// Unpacks a byte buffer into a [Reserve].
// @v2.1.0 TODO: unpack deposits_pool_reward_manager and borrows_pool_reward_manager
// but default them if they are not present, this is part of the
// migration process
fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
let input = array_ref![input, 0, RESERVE_LEN];
let input = array_ref![input, 0, RESERVE_LEN_V1];
#[allow(clippy::ptr_offset_with_cast)]
let (
version,
Expand Down

0 comments on commit 49a6c03

Please sign in to comment.