use borsh::{BorshDeserialize, BorshSerialize};
use solana_sdk::pubkey::Pubkey;
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct BondingCurveAccount {
pub discriminator: u64,
pub virtual_token_reserves: u64,
pub virtual_sol_reserves: u64,
pub real_token_reserves: u64,
pub real_sol_reserves: u64,
pub token_total_supply: u64,
pub complete: bool,
pub creator: Pubkey,
}
impl BondingCurveAccount {
#[allow(clippy::too_many_arguments)]
pub fn new(
discriminator: u64,
virtual_token_reserves: u64,
virtual_sol_reserves: u64,
real_token_reserves: u64,
real_sol_reserves: u64,
token_total_supply: u64,
complete: bool,
creator: Pubkey,
) -> Self {
Self {
discriminator,
virtual_token_reserves,
virtual_sol_reserves,
real_token_reserves,
real_sol_reserves,
token_total_supply,
complete,
creator,
}
}
pub fn get_buy_price(&self, amount: u64) -> Result<u64, &'static str> {
if self.complete {
return Err("Curve is complete");
}
if amount == 0 {
return Ok(0);
}
let n: u128 = (self.virtual_sol_reserves as u128) * (self.virtual_token_reserves as u128);
let i: u128 = (self.virtual_sol_reserves as u128) + (amount as u128);
let r: u128 = n / i + 1;
let s: u128 = (self.virtual_token_reserves as u128) - r;
let s_u64 = s as u64;
Ok(if s_u64 < self.real_token_reserves {
s_u64
} else {
self.real_token_reserves
})
}
pub fn get_sell_price(&self, amount: u64, fee_basis_points: u64) -> Result<u64, &'static str> {
if self.complete {
return Err("Curve is complete");
}
if amount == 0 {
return Ok(0);
}
let n: u128 = ((amount as u128) * (self.virtual_sol_reserves as u128))
/ ((self.virtual_token_reserves as u128) + (amount as u128));
let a: u128 = (n * (fee_basis_points as u128)) / 10000;
Ok((n - a) as u64)
}
pub fn get_market_cap_sol(&self) -> u64 {
if self.virtual_token_reserves == 0 {
return 0;
}
((self.token_total_supply as u128) * (self.virtual_sol_reserves as u128)
/ (self.virtual_token_reserves as u128)) as u64
}
pub fn get_final_market_cap_sol(&self, fee_basis_points: u64) -> u64 {
let total_sell_value: u128 =
self.get_buy_out_price(self.real_token_reserves, fee_basis_points) as u128;
let total_virtual_value: u128 = (self.virtual_sol_reserves as u128) + total_sell_value;
let total_virtual_tokens: u128 =
(self.virtual_token_reserves as u128) - (self.real_token_reserves as u128);
if total_virtual_tokens == 0 {
return 0;
}
((self.token_total_supply as u128) * total_virtual_value / total_virtual_tokens) as u64
}
pub fn get_buy_out_price(&self, amount: u64, fee_basis_points: u64) -> u64 {
let sol_tokens: u128 = if amount < self.real_sol_reserves {
self.real_sol_reserves as u128
} else {
amount as u128
};
let total_sell_value: u128 = (sol_tokens * (self.virtual_sol_reserves as u128))
/ ((self.virtual_token_reserves as u128) - sol_tokens)
+ 1;
let fee: u128 = (total_sell_value * (fee_basis_points as u128)) / 10000;
(total_sell_value + fee) as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
fn get_bonding_curve() -> BondingCurveAccount {
BondingCurveAccount::new(
1, 1000, 1000, 500, 500, 1000, false, Pubkey::new_unique(), )
}
fn get_large_bonding_curve() -> BondingCurveAccount {
BondingCurveAccount::new(
1, u64::MAX / 2, u64::MAX / 2, u64::MAX / 4, u64::MAX / 4, u64::MAX / 2, false, Pubkey::new_unique(), )
}
#[test]
fn test_bonding_curve_account() {
let bonding_curve: BondingCurveAccount = get_bonding_curve();
assert_eq!(bonding_curve.get_buy_price(0).unwrap(), 0);
let buy_price = bonding_curve.get_buy_price(100).unwrap();
assert!(buy_price > 0);
assert!(buy_price <= bonding_curve.real_token_reserves);
assert_eq!(bonding_curve.get_sell_price(0, 250).unwrap(), 0);
let sell_price = bonding_curve.get_sell_price(100, 250).unwrap();
assert!(sell_price > 0);
}
#[test]
fn test_bonding_curve_complete() {
let mut bonding_curve: BondingCurveAccount = get_bonding_curve();
assert!(bonding_curve.get_buy_price(100).is_ok());
assert!(bonding_curve.get_sell_price(100, 250).is_ok());
bonding_curve.complete = true;
assert!(bonding_curve.get_buy_price(100).is_err());
assert!(bonding_curve.get_sell_price(100, 250).is_err());
}
#[test]
fn test_market_cap_calculations() {
let bonding_curve: BondingCurveAccount = get_bonding_curve();
let market_cap = bonding_curve.get_market_cap_sol();
assert!(market_cap > 0);
let final_market_cap = bonding_curve.get_final_market_cap_sol(250);
assert!(final_market_cap > 0);
}
#[test]
fn test_buy_out_price() {
let bonding_curve: BondingCurveAccount = get_bonding_curve();
let buy_out_price = bonding_curve.get_buy_out_price(100, 250);
assert!(buy_out_price > 0);
let small_buy_out = bonding_curve.get_buy_out_price(400, 250);
assert!(small_buy_out > 0);
}
#[test]
fn test_overflow_buy_price() {
let bonding_curve = get_large_bonding_curve();
let buy_price = bonding_curve.get_buy_price(u64::MAX).unwrap();
assert!(buy_price > 0);
assert!(buy_price <= bonding_curve.real_token_reserves);
}
#[test]
fn test_overflow_sell_price() {
let bonding_curve = get_large_bonding_curve();
let sell_price = bonding_curve.get_sell_price(u64::MAX / 4, 250).unwrap();
assert!(sell_price > 0);
}
#[test]
fn test_overflow_market_cap() {
let bonding_curve = get_large_bonding_curve();
let market_cap = bonding_curve.get_market_cap_sol();
assert!(market_cap > 0);
let final_market_cap = bonding_curve.get_final_market_cap_sol(250);
assert!(final_market_cap > 0);
}
#[test]
fn test_overflow_buy_out_price() {
let bonding_curve = get_large_bonding_curve();
let buy_out_price = bonding_curve.get_buy_out_price(u64::MAX / 4, 250);
assert!(buy_out_price > 0);
}
}