add yellowstone grpc

This commit is contained in:
William
2025-01-23 17:48:10 +08:00
parent 429fc78ecf
commit 42b7267c2c
36 changed files with 897 additions and 3356 deletions
+333
View File
@@ -0,0 +1,333 @@
//! 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);
}
}
+201
View File
@@ -0,0 +1,201 @@
//! Global account for the Pump.fun Solana Program
//!
//! This module contains the definition for the global configuration account.
//!
//! # Global Account
//!
//! The global account is used to store the global configuration for the Pump.fun program.
//!
//! # Fields
//!
//! - `discriminator`: Unique identifier for the global account
//! - `initialized`: Whether the global account has been initialized
//! - `authority`: Authority pubkey that can modify settings
//! - `fee_recipient`: Account that receives fees
//! - `initial_virtual_token_reserves`: Initial virtual token reserves for price calculations
//! - `initial_virtual_sol_reserves`: Initial virtual SOL reserves for price calculations
//! - `initial_real_token_reserves`: Initial actual token reserves available for trading
//! - `token_total_supply`: Total supply of tokens
//! - `fee_basis_points`: Fee in basis points (1/100th of a percent)
//!
//! # Methods
//!
//! - `new`: Creates a new global account instance
//! - `get_initial_buy_price`: Calculates the initial amount of tokens received for a given SOL amount
use solana_sdk::pubkey::Pubkey;
use borsh::{BorshDeserialize, BorshSerialize};
/// Represents the global configuration account for token pricing and fees
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct GlobalAccount {
/// Unique identifier for the global account
pub discriminator: u64,
/// Whether the global account has been initialized
pub initialized: bool,
/// Authority that can modify global settings
pub authority: Pubkey,
/// Account that receives fees
pub fee_recipient: Pubkey,
/// Initial virtual token reserves for price calculations
pub initial_virtual_token_reserves: u64,
/// Initial virtual SOL reserves for price calculations
pub initial_virtual_sol_reserves: u64,
/// Initial actual token reserves available for trading
pub initial_real_token_reserves: u64,
/// Total supply of tokens
pub token_total_supply: u64,
/// Fee in basis points (1/100th of a percent)
pub fee_basis_points: u64,
}
impl GlobalAccount {
/// Creates a new global account instance
///
/// # Arguments
/// * `discriminator` - Unique identifier for the account
/// * `initialized` - Whether the account is initialized
/// * `authority` - Authority pubkey that can modify settings
/// * `fee_recipient` - Account that receives fees
/// * `initial_virtual_token_reserves` - Initial virtual token reserves
/// * `initial_virtual_sol_reserves` - Initial virtual SOL reserves
/// * `initial_real_token_reserves` - Initial actual token reserves
/// * `token_total_supply` - Total supply of tokens
/// * `fee_basis_points` - Fee in basis points
#[allow(clippy::too_many_arguments)]
pub fn new(
discriminator: u64,
initialized: bool,
authority: Pubkey,
fee_recipient: Pubkey,
initial_virtual_token_reserves: u64,
initial_virtual_sol_reserves: u64,
initial_real_token_reserves: u64,
token_total_supply: u64,
fee_basis_points: u64,
) -> Self {
Self {
discriminator,
initialized,
authority,
fee_recipient,
initial_virtual_token_reserves,
initial_virtual_sol_reserves,
initial_real_token_reserves,
token_total_supply,
fee_basis_points,
}
}
/// Calculates the initial amount of tokens received for a given SOL amount
///
/// # Arguments
/// * `amount` - Amount of SOL to spend
///
/// # Returns
/// Amount of tokens that would be received
pub fn get_initial_buy_price(&self, amount: u64) -> u64 {
if amount == 0 {
return 0;
}
let n: u128 = (self.initial_virtual_sol_reserves as u128)
* (self.initial_virtual_token_reserves as u128);
let i: u128 = (self.initial_virtual_sol_reserves as u128) + (amount as u128);
let r: u128 = n / i + 1;
let s: u128 = (self.initial_virtual_token_reserves as u128) - r;
if s < (self.initial_real_token_reserves as u128) {
s as u64
} else {
self.initial_real_token_reserves
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn get_global() -> GlobalAccount {
GlobalAccount::new(
1,
true,
Pubkey::new_unique(),
Pubkey::new_unique(),
1000,
1000,
500,
1000,
250,
)
}
fn get_large_global() -> GlobalAccount {
GlobalAccount::new(
1,
true,
Pubkey::new_unique(),
Pubkey::new_unique(),
u64::MAX,
u64::MAX,
u64::MAX / 2,
u64::MAX,
250,
)
}
#[test]
fn test_global_account() {
let global: GlobalAccount = get_global();
// Test initial buy price calculation
assert_eq!(global.get_initial_buy_price(0), 0);
let price: u64 = global.get_initial_buy_price(100);
assert!(price > 0);
assert!(price <= global.initial_real_token_reserves);
}
#[test]
fn test_global_account_max_reserves() {
let mut global: GlobalAccount = get_global();
global.initial_real_token_reserves = 100;
// Test that returned amount is capped by real_token_reserves
let price: u64 = global.get_initial_buy_price(1000);
assert_eq!(price, global.initial_real_token_reserves);
}
#[test]
fn test_global_account_overflow() {
let global: GlobalAccount = get_large_global();
// Test with maximum possible SOL amount
let price: u64 = global.get_initial_buy_price(u64::MAX);
assert!(price > 0);
assert!(price <= global.initial_real_token_reserves);
// Test with large but not maximum SOL amount
let price: u64 = global.get_initial_buy_price(u64::MAX / 2);
assert!(price > 0);
assert!(price <= global.initial_real_token_reserves);
}
#[test]
fn test_global_account_overflow_edge_cases() {
let mut global: GlobalAccount = get_large_global();
global.initial_virtual_sol_reserves = u64::MAX - 1000;
global.initial_virtual_token_reserves = u64::MAX - 1000;
global.initial_real_token_reserves = u64::MAX / 4;
// Test with amounts near u64::MAX
let price: u64 = global.get_initial_buy_price(u64::MAX - 1);
assert!(price > 0);
assert!(price <= global.initial_real_token_reserves);
let price: u64 = global.get_initial_buy_price(u64::MAX - 1000);
assert!(price > 0);
assert!(price <= global.initial_real_token_reserves);
}
}
+14
View File
@@ -0,0 +1,14 @@
//! Accounts for the Pump.fun Solana Program
//!
//! This module contains the definitions for the accounts used by the Pump.fun program.
//!
//! # Accounts
//!
//! - `BondingCurve`: Represents a bonding curve account.
//! - `Global`: Represents the global configuration account.
mod bonding_curve;
mod global;
pub use bonding_curve::*;
pub use global::*;