use anchor_lang::prelude::*;
use borsh::{BorshDeserialize, BorshSerialize};
use gmsol_model::PoolKind;
#[zero_copy]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PoolStorage {
pub(super) rev: u64,
padding: [u8; 8],
pool: Pool,
}
impl PoolStorage {
pub(crate) fn set_is_pure(&mut self, is_pure: bool) {
self.pool.set_is_pure(is_pure);
}
pub fn pool(&self) -> &Pool {
&self.pool
}
pub(super) fn pool_mut(&mut self) -> &mut Pool {
&mut self.pool
}
}
#[zero_copy]
#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(BorshSerialize, BorshDeserialize, InitSpace, PartialEq, Eq)]
pub struct Pool {
is_pure: u8,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "debug", debug(skip))]
padding: [u8; 15],
pub(super) long_token_amount: u128,
pub(super) short_token_amount: u128,
}
const PURE_VALUE: u8 = 1;
impl Pool {
fn set_is_pure(&mut self, is_pure: bool) {
self.is_pure = if is_pure { PURE_VALUE } else { 0 };
}
fn is_pure(&self) -> bool {
!matches!(self.is_pure, 0)
}
}
impl gmsol_model::Balance for Pool {
type Num = u128;
type Signed = i128;
fn long_amount(&self) -> gmsol_model::Result<Self::Num> {
if self.is_pure() {
debug_assert_eq!(
self.short_token_amount, 0,
"short token amount must be zero"
);
Ok(self.long_token_amount.div_ceil(2))
} else {
Ok(self.long_token_amount)
}
}
fn short_amount(&self) -> gmsol_model::Result<Self::Num> {
if self.is_pure() {
debug_assert_eq!(
self.short_token_amount, 0,
"short token amount must be zero"
);
Ok(self.long_token_amount / 2)
} else {
Ok(self.short_token_amount)
}
}
}
impl gmsol_model::Pool for Pool {
fn apply_delta_to_long_amount(&mut self, delta: &Self::Signed) -> gmsol_model::Result<()> {
self.long_token_amount = self.long_token_amount.checked_add_signed(*delta).ok_or(
gmsol_model::Error::Computation("apply delta to long amount"),
)?;
Ok(())
}
fn apply_delta_to_short_amount(&mut self, delta: &Self::Signed) -> gmsol_model::Result<()> {
let amount = if self.is_pure() {
&mut self.long_token_amount
} else {
&mut self.short_token_amount
};
*amount = amount
.checked_add_signed(*delta)
.ok_or(gmsol_model::Error::Computation(
"apply delta to short amount",
))?;
Ok(())
}
fn checked_apply_delta(
&self,
delta: gmsol_model::Delta<&Self::Signed>,
) -> gmsol_model::Result<Self> {
let mut ans = *self;
if let Some(amount) = delta.long() {
ans.apply_delta_to_long_amount(amount)?;
}
if let Some(amount) = delta.short() {
ans.apply_delta_to_short_amount(amount)?;
}
Ok(ans)
}
fn checked_cancel_amounts(&self) -> gmsol_model::Result<Self>
where
Self::Signed: gmsol_model::num_traits::CheckedSub,
{
let mut ans = *self;
if self.is_pure() {
ans.long_token_amount &= 1;
} else {
(ans.long_token_amount, ans.short_token_amount) =
cancel_amounts(ans.long_token_amount, ans.short_token_amount);
}
Ok(ans)
}
}
pub(crate) fn cancel_amounts(long_amount: u128, short_amount: u128) -> (u128, u128) {
let is_long_side_left = long_amount >= short_amount;
let leftover_amount = long_amount.abs_diff(short_amount);
if is_long_side_left {
(leftover_amount, 0)
} else {
(0, leftover_amount)
}
}
#[zero_copy]
#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Pools {
primary: PoolStorage,
swap_impact: PoolStorage,
claimable_fee: PoolStorage,
open_interest_for_long: PoolStorage,
open_interest_for_short: PoolStorage,
open_interest_in_tokens_for_long: PoolStorage,
open_interest_in_tokens_for_short: PoolStorage,
position_impact: PoolStorage,
borrowing_factor: PoolStorage,
funding_amount_per_size_for_long: PoolStorage,
funding_amount_per_size_for_short: PoolStorage,
claimable_funding_amount_per_size_for_long: PoolStorage,
claimable_funding_amount_per_size_for_short: PoolStorage,
collateral_sum_for_long: PoolStorage,
collateral_sum_for_short: PoolStorage,
total_borrowing: PoolStorage,
#[cfg_attr(feature = "debug", debug(skip))]
reserved: [PoolStorage; 16],
}
impl Pools {
pub(super) fn init(&mut self, is_pure: bool) {
self.primary.set_is_pure(is_pure);
self.swap_impact.set_is_pure(is_pure);
self.claimable_fee.set_is_pure(is_pure);
self.open_interest_for_long.set_is_pure(is_pure);
self.open_interest_for_short.set_is_pure(is_pure);
self.open_interest_in_tokens_for_long.set_is_pure(is_pure);
self.open_interest_in_tokens_for_short.set_is_pure(is_pure);
self.position_impact.set_is_pure(false);
self.borrowing_factor.set_is_pure(false);
self.funding_amount_per_size_for_long.set_is_pure(is_pure);
self.funding_amount_per_size_for_short.set_is_pure(is_pure);
self.claimable_funding_amount_per_size_for_long
.set_is_pure(is_pure);
self.claimable_funding_amount_per_size_for_short
.set_is_pure(is_pure);
self.collateral_sum_for_long.set_is_pure(is_pure);
self.collateral_sum_for_short.set_is_pure(is_pure);
self.total_borrowing.set_is_pure(false);
}
pub(super) fn get(&self, kind: PoolKind) -> Option<&PoolStorage> {
let pool = match kind {
PoolKind::Primary => &self.primary,
PoolKind::SwapImpact => &self.swap_impact,
PoolKind::ClaimableFee => &self.claimable_fee,
PoolKind::OpenInterestForLong => &self.open_interest_for_long,
PoolKind::OpenInterestForShort => &self.open_interest_for_short,
PoolKind::OpenInterestInTokensForLong => &self.open_interest_in_tokens_for_long,
PoolKind::OpenInterestInTokensForShort => &self.open_interest_in_tokens_for_short,
PoolKind::PositionImpact => &self.position_impact,
PoolKind::BorrowingFactor => &self.borrowing_factor,
PoolKind::FundingAmountPerSizeForLong => &self.funding_amount_per_size_for_long,
PoolKind::FundingAmountPerSizeForShort => &self.funding_amount_per_size_for_short,
PoolKind::ClaimableFundingAmountPerSizeForLong => {
&self.claimable_funding_amount_per_size_for_long
}
PoolKind::ClaimableFundingAmountPerSizeForShort => {
&self.claimable_funding_amount_per_size_for_short
}
PoolKind::CollateralSumForLong => &self.collateral_sum_for_long,
PoolKind::CollateralSumForShort => &self.collateral_sum_for_short,
PoolKind::TotalBorrowing => &self.total_borrowing,
_ => return None,
};
Some(pool)
}
pub(super) fn get_mut(&mut self, kind: PoolKind) -> Option<&mut PoolStorage> {
let pool = match kind {
PoolKind::Primary => &mut self.primary,
PoolKind::SwapImpact => &mut self.swap_impact,
PoolKind::ClaimableFee => &mut self.claimable_fee,
PoolKind::OpenInterestForLong => &mut self.open_interest_for_long,
PoolKind::OpenInterestForShort => &mut self.open_interest_for_short,
PoolKind::OpenInterestInTokensForLong => &mut self.open_interest_in_tokens_for_long,
PoolKind::OpenInterestInTokensForShort => &mut self.open_interest_in_tokens_for_short,
PoolKind::PositionImpact => &mut self.position_impact,
PoolKind::BorrowingFactor => &mut self.borrowing_factor,
PoolKind::FundingAmountPerSizeForLong => &mut self.funding_amount_per_size_for_long,
PoolKind::FundingAmountPerSizeForShort => &mut self.funding_amount_per_size_for_short,
PoolKind::ClaimableFundingAmountPerSizeForLong => {
&mut self.claimable_funding_amount_per_size_for_long
}
PoolKind::ClaimableFundingAmountPerSizeForShort => {
&mut self.claimable_funding_amount_per_size_for_short
}
PoolKind::CollateralSumForLong => &mut self.collateral_sum_for_long,
PoolKind::CollateralSumForShort => &mut self.collateral_sum_for_short,
PoolKind::TotalBorrowing => &mut self.total_borrowing,
_ => return None,
};
Some(pool)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::events::EventPool;
#[test]
fn test_event_pool() {
let pool = Pool {
is_pure: PURE_VALUE,
padding: Default::default(),
long_token_amount: u128::MAX,
short_token_amount: u128::MAX,
};
let event_pool = EventPool {
is_pure: pool.is_pure,
padding: pool.padding,
long_token_amount: pool.long_token_amount,
short_token_amount: pool.short_token_amount,
};
let mut data = Vec::with_capacity(Pool::INIT_SPACE);
pool.serialize(&mut data)
.expect("failed to serialize `Pool`");
let mut event_data = Vec::with_capacity(Pool::INIT_SPACE);
event_pool
.serialize(&mut event_data)
.expect("failed to serialize `EventPool`");
assert_eq!(data, event_data);
}
#[cfg(feature = "debug")]
#[test]
fn cancel_amounts() -> gmsol_model::Result<()> {
use bytemuck::Zeroable;
use gmsol_model::{Balance, Delta, Pool};
let pool = super::Pool::zeroed();
let pool_1 = pool.checked_apply_delta(Delta::new_both_sides(true, &1_000, &3_000))?;
let expected_1 = pool.checked_apply_delta(Delta::new_both_sides(true, &0, &2_000))?;
assert_eq!(pool_1.checked_cancel_amounts()?, expected_1);
let pool_2 = pool.checked_apply_delta(Delta::new_both_sides(true, &3_005, &3_000))?;
let expected_2 = pool.checked_apply_delta(Delta::new_both_sides(true, &5, &0))?;
assert_eq!(pool_2.checked_cancel_amounts()?, expected_2);
let pool_3 = pool.checked_apply_delta(Delta::new_both_sides(true, &3_000, &3_000))?;
let expected_3 = pool.checked_apply_delta(Delta::new_both_sides(true, &0, &0))?;
assert_eq!(pool_3.checked_cancel_amounts()?, expected_3);
let pool_4 = pool
.checked_apply_delta(Delta::new_both_sides(true, &i128::MAX, &i128::MAX))?
.checked_apply_delta(Delta::new_both_sides(true, &i128::MAX, &i128::MAX))?
.checked_apply_delta(Delta::new_both_sides(true, &1, &1))?;
let expected_4 = pool.checked_apply_delta(Delta::new_both_sides(true, &0, &0))?;
assert_eq!(pool_4.long_amount()?, u128::MAX);
assert_eq!(pool_4.short_amount()?, u128::MAX);
assert_eq!(pool_4.checked_cancel_amounts()?, expected_4);
let pool_5 =
pool.checked_apply_delta(Delta::new_both_sides(true, &i128::MAX, &i128::MAX))?;
let expected_5 = pool.checked_apply_delta(Delta::new_both_sides(true, &0, &0))?;
assert_eq!(pool_5.checked_cancel_amounts()?, expected_5);
let pool_5 = pool
.checked_apply_delta(Delta::new_both_sides(true, &i128::MAX, &i128::MAX))?
.checked_apply_delta(Delta::new_both_sides(true, &i128::MAX, &0))?
.checked_apply_delta(Delta::new_both_sides(true, &1, &0))?;
let expected_5 = pool
.checked_apply_delta(Delta::new_both_sides(true, &i128::MAX, &0))?
.checked_apply_delta(Delta::new_both_sides(true, &1, &0))?;
assert_eq!(pool_5.long_amount()?, u128::MAX);
assert_eq!(pool_5.short_amount()?, i128::MAX.unsigned_abs());
assert_eq!(pool_5.checked_cancel_amounts()?, expected_5);
Ok(())
}
#[cfg(feature = "debug")]
#[test]
fn cancel_amounts_for_pure_pools() -> gmsol_model::Result<()> {
use bytemuck::Zeroable;
use gmsol_model::{Balance, Delta, Pool};
let mut pool = super::Pool::zeroed();
pool.set_is_pure(true);
let pool_1 = pool.checked_apply_delta(Delta::new_both_sides(true, &1_001, &3_000))?;
let expected_1 = pool.checked_apply_delta(Delta::new_both_sides(true, &1, &0))?;
assert_eq!(pool_1.checked_cancel_amounts()?, expected_1);
let pool_2 = pool.checked_apply_delta(Delta::new_both_sides(true, &3_005, &3005))?;
let expected_2 = pool.checked_apply_delta(Delta::new_both_sides(true, &0, &0))?;
assert_eq!(pool_2.checked_cancel_amounts()?, expected_2);
let pool_3 = pool
.checked_apply_delta(Delta::new_both_sides(true, &i128::MAX, &i128::MAX))?
.checked_apply_delta(Delta::new_both_sides(true, &0, &1))?;
let expected_3 = pool.checked_apply_delta(Delta::new_both_sides(true, &1, &0))?;
assert_eq!(pool_3.long_amount()?, u128::MAX / 2 + 1);
assert_eq!(pool_3.short_amount()?, u128::MAX / 2);
assert_eq!(pool_3.checked_cancel_amounts()?, expected_3);
Ok(())
}
}