Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 controller/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ sanctum-u64-ratio = { workspace = true }
[dev-dependencies]
borsh = { workspace = true, features = ["derive", "std"] }
expect-test = { workspace = true }
inf1-test-utils = { workspace = true }
proptest = { workspace = true, features = ["std"] }
5 changes: 5 additions & 0 deletions controller/core/src/accounts/pool_state/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod v1;
mod v2;

pub use v1::*;
pub use v2::*;
239 changes: 239 additions & 0 deletions controller/core/src/accounts/pool_state/v2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
use core::mem::{align_of, size_of};

use generic_array_struct::generic_array_struct;

use crate::{
accounts::pool_state::PoolState,
internal_utils::{impl_cast_from_acc_data, impl_cast_to_acc_data},
typedefs::{fee_nanos::FeeNanos, rps::Rps},
};

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PoolStateV2 {
pub total_sol_value: u64,

// combined from `trading_protocol_fee_bps`
// and `lp_protocol_fee_bps` in v1
pub protocol_fee_nanos: u32,

pub version: u8,
pub is_disabled: u8,
pub is_rebalancing: u8,
pub padding: [u8; 1],
pub admin: [u8; 32],
pub rebalance_authority: [u8; 32],
pub protocol_fee_beneficiary: [u8; 32],
pub pricing_program: [u8; 32],
pub lp_token_mint: [u8; 32],

// new fields added over V1
pub rps_authority: [u8; 32],
pub rps: u64,
pub withheld_lamports: u64,
pub protocol_fee_lamports: u64,
pub last_release_slot: u64,
}
impl_cast_from_acc_data!(PoolStateV2);
impl_cast_to_acc_data!(PoolStateV2);

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PoolStateV2Packed {
total_sol_value: [u8; 8],
protocol_fee_nanos: [u8; 4],
version: u8,
is_disabled: u8,
is_rebalancing: u8,
padding: [u8; 1],
admin: [u8; 32],
rebalance_authority: [u8; 32],
protocol_fee_beneficiary: [u8; 32],
pricing_program: [u8; 32],
lp_token_mint: [u8; 32],
rps_authority: [u8; 32],
rps: [u8; 8],
withheld_lamports: [u8; 8],
protocol_fee_lamports: [u8; 8],
last_release_slot: [u8; 8],
}
impl_cast_from_acc_data!(PoolStateV2Packed, packed);
impl_cast_to_acc_data!(PoolStateV2Packed, packed);

impl PoolStateV2Packed {
#[inline]
pub const fn into_pool_state_v2(self) -> PoolStateV2 {
let Self {
total_sol_value,
protocol_fee_nanos,
version,
is_disabled,
is_rebalancing,
padding,
admin,
rebalance_authority,
protocol_fee_beneficiary,
pricing_program,
lp_token_mint,
withheld_lamports,
protocol_fee_lamports,
last_release_slot,
rps,
rps_authority,
} = self;
PoolStateV2 {
total_sol_value: u64::from_le_bytes(total_sol_value),
protocol_fee_nanos: u32::from_le_bytes(protocol_fee_nanos),
version,
is_disabled,
is_rebalancing,
padding,
admin,
rebalance_authority,
protocol_fee_beneficiary,
pricing_program,
lp_token_mint,
withheld_lamports: u64::from_le_bytes(withheld_lamports),
protocol_fee_lamports: u64::from_le_bytes(protocol_fee_lamports),
last_release_slot: u64::from_le_bytes(last_release_slot),
rps: u64::from_le_bytes(rps),
rps_authority,
}
}

/// # Safety
/// - `self` must be pointing to mem that has same align as `PoolState`.
/// This is true onchain for a PoolState account since account data
/// is always 8-byte aligned onchain.
#[inline]
pub const unsafe fn as_pool_state_v2(&self) -> &PoolStateV2 {
&*(self as *const Self).cast()
}

/// # Safety
/// - same rules as [`Self::as_pool_state`] apply
#[inline]
pub const unsafe fn as_pool_state_v2_mut(&mut self) -> &mut PoolStateV2 {
&mut *(self as *mut Self).cast()
}
}

impl From<PoolStateV2Packed> for PoolStateV2 {
#[inline]
fn from(value: PoolStateV2Packed) -> Self {
value.into_pool_state_v2()
}
}

// field type aggregations
// NB: v1's are in test-utils but for v2, we move it into core since they may be
// generally useful, and also so that they can be used for unit tests

#[generic_array_struct(builder pub)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PoolStateV2Addrs<T> {
pub admin: T,
pub rebalance_authority: T,
pub protocol_fee_beneficiary: T,
pub pricing_program: T,
pub lp_token_mint: T,
pub rps_authority: T,
}

#[generic_array_struct(builder pub)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PoolStateV2U64s<T> {
pub total_sol_value: T,
pub withheld_lamports: T,
pub protocol_fee_lamports: T,
pub last_release_slot: T,
// rps excluded due to its different type
// despite same repr
}

#[generic_array_struct(builder pub)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PoolStateV2U8Bools<T> {
pub is_disabled: T,
pub is_rebalancing: T,
}

// TODO: if we were disciplined about packing all fields of the same type
// at the same region and didnt care about backward compatibility, then
// we could just use this type as the account data repr and woudlnt need
// conversion functions
/// Field-Type aggregations
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PoolStateV2FT<A, U, V, W, X> {
pub addrs: PoolStateV2Addrs<A>,
pub u64s: PoolStateV2U64s<U>,
pub u8_bools: PoolStateV2U8Bools<V>,
pub protocol_fee_nanos: W,
pub rps: X,
}

pub type PoolStateV2FTVals = PoolStateV2FT<[u8; 32], u64, u8, FeeNanos, Rps>;

impl PoolStateV2FTVals {
#[inline]
pub const fn into_pool_state_v2(self) -> PoolStateV2 {
let Self {
addrs,
u64s,
u8_bools,
protocol_fee_nanos,
rps,
} = self;
PoolStateV2 {
total_sol_value: *u64s.total_sol_value(),
protocol_fee_nanos: protocol_fee_nanos.get(),
version: 2u8,
is_disabled: *u8_bools.is_disabled(),
is_rebalancing: *u8_bools.is_rebalancing(),
padding: [0u8],
admin: *addrs.admin(),
rebalance_authority: *addrs.rebalance_authority(),
protocol_fee_beneficiary: *addrs.protocol_fee_beneficiary(),
pricing_program: *addrs.pricing_program(),
lp_token_mint: *addrs.lp_token_mint(),
rps_authority: *addrs.rps_authority(),
rps: *rps.as_inner().as_raw(),
withheld_lamports: *u64s.withheld_lamports(),
protocol_fee_lamports: *u64s.protocol_fee_lamports(),
last_release_slot: *u64s.last_release_slot(),
}
}
}

impl From<PoolStateV2FTVals> for PoolStateV2 {
#[inline]
fn from(value: PoolStateV2FTVals) -> Self {
value.into_pool_state_v2()
}
}

const _ASSERT_PACKED_UNPACKED_SIZES_EQ: () =
assert!(size_of::<PoolStateV2>() == size_of::<PoolStateV2Packed>());

const _ASSERT_SAME_ALIGN_AS_V1: () = assert!(align_of::<PoolStateV2>() == align_of::<PoolState>());

/// Check we didn't mess up existing fields from v1
/// `assert_offset_unchanged`
macro_rules! aou {
($ASSERTION:ident, $field:ident) => {
const $ASSERTION: () = assert!(
core::mem::offset_of!(PoolStateV2, $field) == core::mem::offset_of!(PoolState, $field)
);
};
}

aou!(_TOTAL_SOL_VALUE, total_sol_value);
aou!(_VERSION, version);
aou!(_IS_DISABLED, is_disabled);
aou!(_IS_REBALANCING, is_rebalancing);
aou!(_PADDING, padding);
aou!(_ADMIN, admin);
aou!(_REBALANCE_AUTH, rebalance_authority);
aou!(_PROTOCOL_FEE_BENEFICIARY, protocol_fee_beneficiary);
aou!(_PRICING_PROGRAM, pricing_program);
aou!(_LP_TOKEN_MINT, lp_token_mint);
2 changes: 1 addition & 1 deletion controller/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ pub mod instructions;
pub mod keys;
pub mod pda;
pub mod typedefs;
pub mod yield_release;
pub mod yields;

keys::id_str!(ID_STR, ID, "5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx");
102 changes: 102 additions & 0 deletions controller/core/src/typedefs/fee_nanos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use core::{error::Error, fmt::Display, ops::Deref};

use sanctum_fee_ratio::Fee;
use sanctum_u64_ratio::{Ceil, Ratio};

pub const NANOS_DENOM: u32 = 1_000_000_000;

/// 100%
pub const MAX_FEE_NANOS: u32 = NANOS_DENOM;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(transparent)]
pub struct FeeNanos(u32);

impl FeeNanos {
/// 0%
pub const ZERO: Self = Self(0);

/// 100%
pub const MAX: Self = Self(MAX_FEE_NANOS);

#[inline]
pub const fn new(n: u32) -> Result<Self, FeeNanosTooLargeErr> {
if n > MAX_FEE_NANOS {
Err(FeeNanosTooLargeErr { actual: n })
} else {
Ok(Self(n))
}
}

#[inline]
pub const fn get(&self) -> u32 {
self.0
}

#[inline]
pub const fn into_fee(self) -> F {
// safety: n <= d checked at construction (::new())
unsafe {
F::new_unchecked(Ratio {
n: self.0,
d: NANOS_DENOM,
})
}
}
}

type F = Fee<Ceil<Ratio<u32, u32>>>;

impl Deref for FeeNanos {
type Target = u32;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FeeNanosTooLargeErr {
pub actual: u32,
}

impl Display for FeeNanosTooLargeErr {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let Self { actual } = self;
f.write_fmt(format_args!("fee nanos {actual} > {MAX_FEE_NANOS} (max)"))
}
}

impl Error for FeeNanosTooLargeErr {}

#[cfg(test)]
pub mod test_utils {
use proptest::prelude::*;

use super::*;

pub fn any_fee_nanos_strat() -> impl Strategy<Value = FeeNanos> {
(0..=MAX_FEE_NANOS)
.prop_map(FeeNanos::new)
.prop_map(Result::unwrap)
}
}

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

#[test]
fn new_range_sc() {
const FAIL: u32 = NANOS_DENOM + 1;
const SUCC: u32 = NANOS_DENOM;

assert_eq!(
FeeNanos::new(FAIL),
Err(FeeNanosTooLargeErr { actual: FAIL })
);
assert!(FeeNanos::new(SUCC).is_ok());
}
}
2 changes: 2 additions & 0 deletions controller/core/src/typedefs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod fee_nanos;
pub mod lst_state;
pub mod rps;
pub mod snap;
pub mod u8bool;
pub mod uq0_63;
18 changes: 18 additions & 0 deletions controller/core/src/typedefs/snap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use generic_array_struct::generic_array_struct;

/// A state snapshot across time
#[generic_array_struct(builder pub)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Snap<T> {
pub old: T,
pub new: T,
}

impl<T: Copy> Snap<T> {
#[inline]
pub const fn memset(v: T) -> Self {
Self([v; SNAP_LEN])
}
}

pub type SnapU64 = Snap<u64>;
Loading