mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-18 19:38:04 +00:00
334 lines
12 KiB
Rust
Executable File
334 lines
12 KiB
Rust
Executable File
//! Bonding curve account for the Pump.fun Solana Program
|
|
//!
|
|
//! This module contains the definition for the bonding curve account.
|
|
//!
|
|
//! # Bonding Curve Account
|
|
//!
|
|
//! The bonding curve account is used to manage token pricing and liquidity.
|
|
//!
|
|
//! # Fields
|
|
//!
|
|
//! - `discriminator`: Unique identifier for the bonding curve
|
|
//! - `virtual_token_reserves`: Virtual token reserves used for price calculations
|
|
//! - `virtual_sol_reserves`: Virtual SOL reserves used for price calculations
|
|
//! - `real_token_reserves`: Actual token reserves available for trading
|
|
//! - `real_sol_reserves`: Actual SOL reserves available for trading
|
|
//! - `token_total_supply`: Total supply of tokens
|
|
//! - `complete`: Whether the bonding curve is complete/finalized
|
|
//!
|
|
//! # Methods
|
|
//!
|
|
//! - `new`: Creates a new bonding curve instance
|
|
//! - `get_buy_price`: Calculates the amount of tokens received for a given SOL amount
|
|
//! - `get_sell_price`: Calculates the amount of SOL received for selling tokens
|
|
//! - `get_market_cap_sol`: Calculates the current market cap in SOL
|
|
//! - `get_final_market_cap_sol`: Calculates the final market cap in SOL after all tokens are sold
|
|
//! - `get_buy_out_price`: Calculates the price to buy out all remaining tokens
|
|
|
|
use borsh::{BorshDeserialize, BorshSerialize};
|
|
|
|
/// Represents a bonding curve for token pricing and liquidity management
|
|
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
|
pub struct BondingCurveAccount {
|
|
/// Unique identifier for the bonding curve
|
|
pub discriminator: u64,
|
|
/// Virtual token reserves used for price calculations
|
|
pub virtual_token_reserves: u64,
|
|
/// Virtual SOL reserves used for price calculations
|
|
pub virtual_sol_reserves: u64,
|
|
/// Actual token reserves available for trading
|
|
pub real_token_reserves: u64,
|
|
/// Actual SOL reserves available for trading
|
|
pub real_sol_reserves: u64,
|
|
/// Total supply of tokens
|
|
pub token_total_supply: u64,
|
|
/// Whether the bonding curve is complete/finalized
|
|
pub complete: bool,
|
|
}
|
|
|
|
impl BondingCurveAccount {
|
|
/// Creates a new bonding curve instance
|
|
///
|
|
/// # Arguments
|
|
/// * `discriminator` - Unique identifier for the curve
|
|
/// * `virtual_token_reserves` - Virtual token reserves for price calculations
|
|
/// * `virtual_sol_reserves` - Virtual SOL reserves for price calculations
|
|
/// * `real_token_reserves` - Actual token reserves available
|
|
/// * `real_sol_reserves` - Actual SOL reserves available
|
|
/// * `token_total_supply` - Total supply of tokens
|
|
/// * `complete` - Whether the curve is complete
|
|
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,
|
|
) -> Self {
|
|
Self {
|
|
discriminator,
|
|
virtual_token_reserves,
|
|
virtual_sol_reserves,
|
|
real_token_reserves,
|
|
real_sol_reserves,
|
|
token_total_supply,
|
|
complete,
|
|
}
|
|
}
|
|
|
|
/// Calculates the amount of tokens received for a given SOL amount
|
|
///
|
|
/// # Arguments
|
|
/// * `amount` - Amount of SOL to spend
|
|
///
|
|
/// # Returns
|
|
/// * `Ok(u64)` - Amount of tokens that would be received
|
|
/// * `Err(&str)` - Error message if curve is complete
|
|
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);
|
|
}
|
|
|
|
// Calculate the product of virtual reserves using u128 to avoid overflow
|
|
let n: u128 = (self.virtual_sol_reserves as u128) * (self.virtual_token_reserves as u128);
|
|
|
|
// Calculate the new virtual sol reserves after the purchase
|
|
let i: u128 = (self.virtual_sol_reserves as u128) + (amount as u128);
|
|
|
|
// Calculate the new virtual token reserves after the purchase
|
|
let r: u128 = n / i + 1;
|
|
|
|
// Calculate the amount of tokens to be purchased
|
|
let s: u128 = (self.virtual_token_reserves as u128) - r;
|
|
|
|
// Convert back to u64 and return the minimum of calculated tokens and real reserves
|
|
let s_u64 = s as u64;
|
|
Ok(if s_u64 < self.real_token_reserves {
|
|
s_u64
|
|
} else {
|
|
self.real_token_reserves
|
|
})
|
|
}
|
|
|
|
/// Calculates the amount of SOL received for selling tokens
|
|
///
|
|
/// # Arguments
|
|
/// * `amount` - Amount of tokens to sell
|
|
/// * `fee_basis_points` - Fee in basis points (1/100th of a percent)
|
|
///
|
|
/// # Returns
|
|
/// * `Ok(u64)` - Amount of SOL that would be received after fees
|
|
/// * `Err(&str)` - Error message if curve is complete
|
|
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);
|
|
}
|
|
|
|
// Calculate the proportional amount of virtual sol reserves to be received using u128
|
|
let n: u128 = ((amount as u128) * (self.virtual_sol_reserves as u128))
|
|
/ ((self.virtual_token_reserves as u128) + (amount as u128));
|
|
|
|
// Calculate the fee amount in the same units
|
|
let a: u128 = (n * (fee_basis_points as u128)) / 10000;
|
|
|
|
// Return the net amount after deducting the fee, converting back to u64
|
|
Ok((n - a) as u64)
|
|
}
|
|
|
|
/// Calculates the current market cap in SOL
|
|
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
|
|
}
|
|
|
|
/// Calculates the final market cap in SOL after all tokens are sold
|
|
///
|
|
/// # Arguments
|
|
/// * `fee_basis_points` - Fee in basis points (1/100th of a percent)
|
|
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
|
|
}
|
|
|
|
/// Calculates the price to buy out all remaining tokens
|
|
///
|
|
/// # Arguments
|
|
/// * `amount` - Amount of tokens to buy
|
|
/// * `fee_basis_points` - Fee in basis points (1/100th of a percent)
|
|
pub fn get_buy_out_price(&self, amount: u64, fee_basis_points: u64) -> u64 {
|
|
// Get the effective amount of sol tokens
|
|
let sol_tokens: u128 = if amount < self.real_sol_reserves {
|
|
self.real_sol_reserves as u128
|
|
} else {
|
|
amount as u128
|
|
};
|
|
|
|
// Calculate total sell value
|
|
let total_sell_value: u128 = (sol_tokens * (self.virtual_sol_reserves as u128))
|
|
/ ((self.virtual_token_reserves as u128) - sol_tokens)
|
|
+ 1;
|
|
|
|
// Calculate fee
|
|
let fee: u128 = (total_sell_value * (fee_basis_points as u128)) / 10000;
|
|
|
|
// Return total including fee, converting back to u64
|
|
(total_sell_value + fee) as u64
|
|
}
|
|
|
|
pub fn get_token_price(&self) -> f64 {
|
|
let v_sol = self.virtual_sol_reserves as f64 / 100_000_000.0;
|
|
let v_tokens = self.virtual_token_reserves as f64 / 100_000.0;
|
|
let token_price = v_sol / v_tokens;
|
|
token_price
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn get_bonding_curve() -> BondingCurveAccount {
|
|
BondingCurveAccount::new(
|
|
1, // discriminator
|
|
1000, // virtual_token_reserves
|
|
1000, // virtual_sol_reserves
|
|
500, // real_token_reserves
|
|
500, // real_sol_reserves
|
|
1000, // token_total_supply
|
|
false, // complete
|
|
)
|
|
}
|
|
|
|
fn get_large_bonding_curve() -> BondingCurveAccount {
|
|
BondingCurveAccount::new(
|
|
1, // discriminator
|
|
u64::MAX / 2, // virtual_token_reserves
|
|
u64::MAX / 2, // virtual_sol_reserves
|
|
u64::MAX / 4, // real_token_reserves
|
|
u64::MAX / 4, // real_sol_reserves
|
|
u64::MAX / 2, // token_total_supply
|
|
false, // complete
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn test_bonding_curve_account() {
|
|
let bonding_curve: BondingCurveAccount = get_bonding_curve();
|
|
|
|
// Test buy price calculation
|
|
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);
|
|
|
|
// Test sell price calculation
|
|
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();
|
|
|
|
// Test operations work when not complete
|
|
assert!(bonding_curve.get_buy_price(100).is_ok());
|
|
assert!(bonding_curve.get_sell_price(100, 250).is_ok());
|
|
|
|
// Set curve to complete
|
|
bonding_curve.complete = true;
|
|
|
|
// Test operations fail when complete
|
|
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();
|
|
|
|
// Test market cap calculations
|
|
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);
|
|
|
|
// Test with amount less than real_sol_reserves
|
|
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();
|
|
|
|
// Test buying with large SOL amount
|
|
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();
|
|
|
|
// Test selling large token amount
|
|
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();
|
|
|
|
// Test market cap with large values
|
|
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();
|
|
|
|
// Test buy out with large token amount
|
|
let buy_out_price = bonding_curve.get_buy_out_price(u64::MAX / 4, 250);
|
|
assert!(buy_out_price > 0);
|
|
}
|
|
}
|