From 0c61a850da3b4efd6381431a09c2bf61ff79a97e Mon Sep 17 00:00:00 2001 From: Kevin Rodriguez <_@kevinrodriguez.io> Date: Thu, 5 Sep 2024 11:56:35 -0600 Subject: [PATCH] feat: updates curves to support bundled operations without iterating --- crates/magic-curves/src/core/exponential.rs | 64 +++--- crates/magic-curves/src/core/linear.rs | 142 ++++++++++--- crates/magic-curves/src/core/logarithmic.rs | 69 +++++-- crates/magic-curves/src/core/mod.rs | 6 +- crates/magic-curves/src/core/quadratic.rs | 215 +++++++++++++++++--- crates/magic-curves/src/core/sigmoid.rs | 50 ++++- crates/magic-curves/src/core/types.rs | 31 +++ 7 files changed, 463 insertions(+), 114 deletions(-) create mode 100644 crates/magic-curves/src/core/types.rs diff --git a/crates/magic-curves/src/core/exponential.rs b/crates/magic-curves/src/core/exponential.rs index 0a47d8f..40b3362 100644 --- a/crates/magic-curves/src/core/exponential.rs +++ b/crates/magic-curves/src/core/exponential.rs @@ -1,5 +1,7 @@ use std::f64::consts::E; +use super::{BondingCurve, OperationSide}; + #[derive(Copy, Clone, Debug, PartialEq)] pub struct ExponentialBondingCurve { pub base: f64, @@ -10,38 +12,39 @@ impl ExponentialBondingCurve { pub fn new(base: f64, growth: f64) -> Self { Self { base, growth } } +} - /// Calculates the price based on the supply. - /// - /// This function is lossy because it deals with floating point numbers. - /// In theory, the price should be calculated using fixed point arithmetic, - /// however, using a Taylor series expansion, we can calculate the price - /// without using floating point numbers, but it is more complex, and - /// precision is lost due to type conversions and large exponentiation. - /// - /// # Formula: - /// - /// `f(x) = a*e^(b*x)` - /// - /// where: - /// - /// - e is euler's number. - /// - x is the supply. - /// - a is a constant that scales the price, often called the base price. - /// - b is the growth rate of the curve, which determines how quickly the price increases as the supply increases. - pub fn calculate_price_lossy(&self, supply: u64) -> f64 { +impl BondingCurve for ExponentialBondingCurve { + fn calculate_price(&self, supply: u64) -> f64 { self.base * E.powf(self.growth * supply as f64) } + fn calculate_price_many(&self, starting_supply: u64, amount: u64, side: OperationSide) -> f64 { + let start = starting_supply as f64; + let end = match side { + OperationSide::Add => (starting_supply + amount) as f64, + OperationSide::Remove => (starting_supply - amount) as f64, + }; + // Calculate the integral of the exponential function + let integral = + self.base / self.growth * (E.powf(self.growth * end) - E.powf(self.growth * start)); + match side { + OperationSide::Add => integral, + OperationSide::Remove => -integral, + } + } } #[cfg(test)] mod test { - use crate::{fixed_point_to_float, float_to_fixed_point}; + use crate::{ + fixed_point_to_float, float_to_fixed_point, BondingCurve, ExponentialBondingCurve, + OperationSide, + }; #[test] pub fn test_exponential_price_calculus() { - let curve = crate::ExponentialBondingCurve::new(0.01, 0.02); - let price = curve.calculate_price_lossy(100); + let curve = ExponentialBondingCurve::new(0.01, 0.02); + let price = curve.calculate_price(100); assert_eq!(price, 0.073890560989306492); } @@ -49,8 +52,21 @@ mod test { pub fn test_exponential_price_calculus_fixed_point() { let base = fixed_point_to_float(1, 2); let growth = fixed_point_to_float(2, 2); - let curve = crate::ExponentialBondingCurve::new(base, growth); - let price = curve.calculate_price_lossy(100); + let curve = ExponentialBondingCurve::new(base, growth); + let price = curve.calculate_price(100); assert_eq!(float_to_fixed_point(price, 9), 0_073_890_560); } + + #[test] + pub fn test_exponential_price_calculus_many() { + let amount = 10; + let starting_supply = 1000; + let curve = ExponentialBondingCurve::new(0.05, 0.01); + let add_price_many = + curve.calculate_price_many(starting_supply, amount, OperationSide::Add); + assert_eq!(add_price_many, 11582.718148008316); + let remove_price_many = + curve.calculate_price_many(starting_supply, amount, OperationSide::Remove); + assert_eq!(remove_price_many, 10480.476782882088); + } } diff --git a/crates/magic-curves/src/core/linear.rs b/crates/magic-curves/src/core/linear.rs index 1a9d80c..8db4fdc 100644 --- a/crates/magic-curves/src/core/linear.rs +++ b/crates/magic-curves/src/core/linear.rs @@ -1,58 +1,91 @@ -use super::BondingCurveError; -use num_traits::{CheckedAdd, CheckedMul, Zero}; -use std::ops::{Add, Mul}; +use super::{BondingCurve, BondingCurveError, BondingCurveWithCheckedOperations, OperationSide}; #[derive(Copy, Clone, Debug, PartialEq)] -pub struct LinearBondingCurve { - pub linear: T, - pub base: T, +pub struct LinearBondingCurve { + pub linear: u64, + pub base: u64, } -impl LinearBondingCurve -where - T: Copy + Add + Mul + CheckedMul + CheckedAdd + Zero, -{ - pub fn new(linear: T, base: T) -> Self { +impl LinearBondingCurve { + pub fn new(linear: u64, base: u64) -> Self { Self { linear, base } } +} + +impl BondingCurve for LinearBondingCurve { + fn calculate_price(&self, supply: u64) -> u64 { + self.linear * supply + self.base + } + fn calculate_price_many(&self, starting_supply: u64, amount: u64, side: OperationSide) -> u64 { + let a1 = self.linear * starting_supply + self.base; + let an = match side { + OperationSide::Add => self.linear * (starting_supply + amount - 1) + self.base, + OperationSide::Remove => self.linear * (starting_supply - amount + 1) + self.base, + }; + (amount * (a1 + an)) / 2 + } +} - /// Calculates the price based on the supply. - /// - /// # Formula: - /// - /// f(x) = ax + b - /// - /// where: - /// - /// - x is the supply - /// - a is the linear coefficient - /// - b is the base price - pub fn calculate_price_checked(&self, supply: T) -> Result { +impl BondingCurveWithCheckedOperations for LinearBondingCurve { + fn calculate_price_checked(&self, supply: u64) -> Result { let result = self .linear - .checked_mul(&supply) - .and_then(|x| x.checked_add(&self.base)); + .checked_mul(supply) + .and_then(|x| x.checked_add(self.base)); result.ok_or(BondingCurveError::Overflow) } - pub fn calculate_price(&self, supply: T) -> T { - self.linear * supply + self.base + fn calculate_price_many_checked( + &self, + starting_supply: u64, + amount: u64, + side: OperationSide, + ) -> Result { + let a1 = self + .linear + .checked_mul(starting_supply) + .and_then(|x| x.checked_add(self.base)) + .ok_or(BondingCurveError::Overflow)?; + + let an = match side { + OperationSide::Add => self + .linear + .checked_mul(starting_supply + amount - 1) + .and_then(|x| x.checked_add(self.base)) + .ok_or(BondingCurveError::Overflow)?, + OperationSide::Remove => self + .linear + .checked_mul(starting_supply - amount + 1) + .and_then(|x| x.checked_add(self.base)) + .ok_or(BondingCurveError::Overflow)?, + }; + + let sum = a1 + .checked_add(an) + .and_then(|x| x.checked_mul(amount)) + .and_then(|x| x.checked_div(2)) + .ok_or(BondingCurveError::Overflow)?; + Ok(sum) } } #[cfg(test)] mod test { + use crate::{ + BondingCurve, BondingCurveWithCheckedOperations, LinearBondingCurve, OperationSide, + }; + #[test] pub fn test_linear_price_calculus() { - let linear = 500_000_000u128; - let base = 1_000_000_000u128; + let linear = 500_000_000u64; + let base = 1_000_000_000u64; - let curve = crate::LinearBondingCurve::new(linear, base); + let curve = LinearBondingCurve::new(linear, base); let r1 = curve.base; - let r2 = 1_500_000_000u128; - let r3 = 5_000_000_000u128; - let r4 = 401_000_000_000u128; + let r2 = 1_500_000_000u64; + let r3 = 5_000_000_000u64; + let r4 = 401_000_000_000u64; let price = curve.calculate_price(0); assert_eq!(price, r1); @@ -74,4 +107,47 @@ mod test { let price = curve.calculate_price_checked(800).unwrap(); assert_eq!(price, r4); } + + #[test] + pub fn test_increase_linear_price_many() { + let linear = 500_000_000u64; + let base = 1_000_000_000u64; + let amount = 10u64; + let starting_supply = 100u64; + + let curve = LinearBondingCurve::new(linear, base); + + let many_price_add = + curve.calculate_price_many(starting_supply, amount, OperationSide::Add); + + // Do it with a loop with calculate_price + let mut looped_price_add = 0u64; + for i in 0..amount { + looped_price_add += curve.calculate_price(starting_supply + i); + } + assert_eq!(many_price_add, looped_price_add); + + let checked_many_price_add = curve + .calculate_price_many_checked(starting_supply, amount, OperationSide::Add) + .unwrap(); + + assert_eq!(checked_many_price_add, looped_price_add); + + let many_price_remove = + curve.calculate_price_many(starting_supply, amount, OperationSide::Remove); + + // Do it with a loop with calculate_price + let mut looped_price_remove = 0u64; + for i in 0..amount { + looped_price_remove += curve.calculate_price(starting_supply - i); + } + + assert_eq!(many_price_remove, looped_price_remove); + + let checked_many_price_remove = curve + .calculate_price_many_checked(starting_supply, amount, OperationSide::Remove) + .unwrap(); + + assert_eq!(checked_many_price_remove, looped_price_remove); + } } diff --git a/crates/magic-curves/src/core/logarithmic.rs b/crates/magic-curves/src/core/logarithmic.rs index 2072194..779e2a2 100644 --- a/crates/magic-curves/src/core/logarithmic.rs +++ b/crates/magic-curves/src/core/logarithmic.rs @@ -1,3 +1,5 @@ +use super::BondingCurve; + #[derive(Copy, Clone, Debug, PartialEq)] pub struct LogarithmicBondingCurve { pub base: f64, @@ -8,34 +10,58 @@ impl LogarithmicBondingCurve { pub fn new(base: f64, growth: f64) -> Self { Self { base, growth } } +} - /// Calculates the price based on the supply. - /// - /// # Formula: - /// - /// `f(x) = a*ln(x)+b` - /// - /// where: - /// - /// - x is the supply - /// - a is the growth rate of the curve, which determines how quickly the price increases as the supply increases. - /// - b is a constant that scales the price, often called the base price. - pub fn calculate_price_lossy(&self, supply: u64) -> f64 { +impl BondingCurve for LogarithmicBondingCurve { + fn calculate_price(&self, supply: u64) -> f64 { if supply == 0 { return self.base; // Avoid taking the log of 0 } self.growth * (supply as f64).ln() + self.base } + + fn calculate_price_many( + &self, + starting_supply: u64, + amount: u64, + side: super::OperationSide, + ) -> f64 { + let start = starting_supply as f64; + let end = match side { + super::OperationSide::Add => (starting_supply + amount) as f64, + super::OperationSide::Remove => (starting_supply - amount) as f64, + }; + + // Calculate the integral of the logarithmic function + // The integral of (a * ln(x) + b) is (a * x * ln(x) - a * x + b * x) + let integral = |x: f64| self.growth * x * x.ln() - self.growth * x + self.base * x; + + // Calculate the difference between the integrals at the end and start points + let price = match side { + super::OperationSide::Add => integral(end) - integral(start), + super::OperationSide::Remove => integral(start) - integral(end), + }; + + // Handle the case where starting_supply is 0 for Add operation + if starting_supply == 0 && side == super::OperationSide::Add { + price + self.base // Add base price for the first token + } else { + price + } + } } #[cfg(test)] mod test { - use crate::{fixed_point_to_float, float_to_fixed_point}; + use crate::{ + fixed_point_to_float, float_to_fixed_point, BondingCurve, LogarithmicBondingCurve, + OperationSide, + }; #[test] pub fn test_logarithmic_price_calculus() { - let curve = crate::LogarithmicBondingCurve::new(0.02, 0.01); - let price = curve.calculate_price_lossy(100); + let curve = LogarithmicBondingCurve::new(0.02, 0.01); + let price = curve.calculate_price(100); assert_eq!(price, 0.06605170185988092); } @@ -43,8 +69,17 @@ mod test { pub fn test_logarithmic_price_calculus_fixed_point() { let base = fixed_point_to_float(2, 2); let growth = fixed_point_to_float(1, 2); - let curve = crate::LogarithmicBondingCurve::new(base, growth); - let price = curve.calculate_price_lossy(100); + let curve = LogarithmicBondingCurve::new(base, growth); + let price = curve.calculate_price(100); assert_eq!(float_to_fixed_point(price, 9), 0_066_051_701); } + + #[test] + pub fn test_logarithmic_price_calculus_many() { + let curve = LogarithmicBondingCurve::new(0.02, 0.01); + let price_add = curve.calculate_price_many(100, 10, OperationSide::Add); + assert_eq!(price_add, 0.6653582163835674); + let price_remove = curve.calculate_price_many(100, 10, OperationSide::Remove); + assert_eq!(price_remove, 0.6553414826908526); + } } diff --git a/crates/magic-curves/src/core/mod.rs b/crates/magic-curves/src/core/mod.rs index 98ad0b4..f9bf274 100644 --- a/crates/magic-curves/src/core/mod.rs +++ b/crates/magic-curves/src/core/mod.rs @@ -3,13 +3,15 @@ pub mod exponential; pub mod linear; pub mod logarithmic; pub mod quadratic; -pub mod tools; pub mod sigmoid; +pub mod tools; +pub mod types; pub use error::*; pub use exponential::*; pub use linear::*; pub use logarithmic::*; pub use quadratic::*; +pub use sigmoid::*; pub use tools::*; -pub use sigmoid::*; \ No newline at end of file +pub use types::*; diff --git a/crates/magic-curves/src/core/quadratic.rs b/crates/magic-curves/src/core/quadratic.rs index ee222c2..fa08836 100644 --- a/crates/magic-curves/src/core/quadratic.rs +++ b/crates/magic-curves/src/core/quadratic.rs @@ -1,59 +1,171 @@ -use super::BondingCurveError; -use num_traits::{CheckedAdd, CheckedMul, Zero}; -use std::ops::{Add, Mul}; +use super::{BondingCurve, BondingCurveError, BondingCurveWithCheckedOperations, OperationSide}; #[derive(Copy, Clone, Debug, PartialEq)] -pub struct QuadraticBondingCurve { - pub quadratic: T, - pub linear: T, - pub base: T, +pub struct QuadraticBondingCurve { + pub quadratic: u64, + pub linear: u64, + pub base: u64, } -impl QuadraticBondingCurve -where - T: Copy + Add + Mul + CheckedMul + CheckedAdd + Zero, -{ - pub fn new(quadratic: T, linear: T, base: T) -> Self { +impl QuadraticBondingCurve { + pub fn new(quadratic: u64, linear: u64, base: u64) -> Self { Self { quadratic, linear, base, } } +} + +impl BondingCurve for QuadraticBondingCurve { + fn calculate_price(&self, supply: u64) -> u64 { + self.quadratic * supply * supply + self.linear * supply + self.base + } + + fn calculate_price_many(&self, starting_supply: u64, amount: u64, side: OperationSide) -> u64 { + let n = amount; + let a = starting_supply; - /// Calculates the price based on the supply. - /// - /// # Formula: - /// - /// f(x) = xa^2 + bx + c - /// - /// where: - /// - /// - x is the supply - /// - a is the quadratic coefficient - /// - b is the linear coefficient - /// - c is the base price - pub fn calculate_price_checked(&self, supply: T) -> Result { + let sum_quadratic = match side { + OperationSide::Add => { + // Sum of quadratic terms: (a^2 * n) + (a * n * (n-1)) + (n * (n-1) * (2n-1) / 6) + (self.quadratic * a * a * n) + + (self.quadratic * a * n * (n - 1)) + + (self.quadratic * n * (n - 1) * (2 * n - 1) / 6) + } + OperationSide::Remove => { + // Sum of quadratic terms: (a^2 * n) - (a * n * (n-1)) + (n * (n-1) * (2n-1) / 6) + (self.quadratic * a * a * n) - (self.quadratic * a * n * (n - 1)) + + (self.quadratic * n * (n - 1) * (2 * n - 1) / 6) + } + }; + + let sum_linear = match side { + OperationSide::Add => { + // Sum of linear terms: b * (a * n + n * (n-1) / 2) + self.linear * (a * n + n * (n - 1) / 2) + } + OperationSide::Remove => { + // Sum of linear terms: b * (a * n - n * (n-1) / 2) + self.linear * (a * n - n * (n - 1) / 2) + } + }; + + // Sum of constant terms: c * n + let sum_constant = self.base * n; + + sum_quadratic + sum_linear + sum_constant + } +} + +impl BondingCurveWithCheckedOperations for QuadraticBondingCurve { + fn calculate_price_checked(&self, supply: u64) -> Result { let result = self .quadratic - .checked_mul(&supply) - .and_then(|x| x.checked_mul(&supply)) - .and_then(|x| x.checked_add(&self.linear.checked_mul(&supply)?)) - .and_then(|x| x.checked_add(&self.base)); - + .checked_mul(supply) + .and_then(|x| x.checked_mul(supply)) + .and_then(|x| x.checked_add(self.linear.checked_mul(supply)?)) + .and_then(|x| x.checked_add(self.base)); result.ok_or(BondingCurveError::Overflow) } - pub fn calculate_price(&self, supply: T) -> T { - self.quadratic * supply * supply + self.linear * supply + self.base + fn calculate_price_many_checked( + &self, + starting_supply: u64, + amount: u64, + side: OperationSide, + ) -> Result { + let n = amount; + let a = starting_supply; + let n_minus_1 = n.checked_sub(1).ok_or(BondingCurveError::Overflow)?; + + // Sum of quadratic terms + let first_term = self + .quadratic + .checked_mul( + a.checked_mul(a) + .and_then(|x| x.checked_mul(n)) + .ok_or(BondingCurveError::Overflow)?, + ) + .ok_or(BondingCurveError::Overflow)?; + + let second_term = self + .quadratic + .checked_mul( + a.checked_mul(n) + .and_then(|x| x.checked_mul(n_minus_1)) + .ok_or(BondingCurveError::Overflow)?, + ) + .ok_or(BondingCurveError::Overflow)?; + + let third_term_pow = 2u64 + .checked_mul(n) + .and_then(|x| x.checked_sub(1)) + .ok_or(BondingCurveError::Overflow)?; + + let third_term = self + .quadratic + .checked_mul( + n.checked_mul(n_minus_1) + .and_then(|x| x.checked_mul(third_term_pow)) + .and_then(|x| x.checked_div(6)) + .ok_or(BondingCurveError::Overflow)?, + ) + .ok_or(BondingCurveError::Overflow)?; + + let sum_quadratic = match side { + OperationSide::Add => first_term + .checked_add(second_term) + .and_then(|x| x.checked_add(third_term)), + OperationSide::Remove => first_term + .checked_sub(second_term) + .and_then(|x| x.checked_add(third_term)), + } + .ok_or(BondingCurveError::Overflow)?; + + // Sum of linear terms + let sum_linear = match side { + OperationSide::Add => self + .linear + .checked_mul( + a.checked_mul(n) + .and_then(|x| x.checked_add(n.checked_mul(n_minus_1)?.checked_div(2)?)) + .ok_or(BondingCurveError::Overflow)?, + ) + .ok_or(BondingCurveError::Overflow)?, + OperationSide::Remove => self + .linear + .checked_mul( + a.checked_mul(n) + .and_then(|x| x.checked_sub(n.checked_mul(n_minus_1)?.checked_div(2)?)) + .ok_or(BondingCurveError::Overflow)?, + ) + .ok_or(BondingCurveError::Overflow)?, + }; + + // Sum of constant terms + let sum_constant = self + .base + .checked_mul(n) + .ok_or(BondingCurveError::Overflow)?; + + // Final sum + sum_quadratic + .checked_add(sum_linear) + .and_then(|x| x.checked_add(sum_constant)) + .ok_or(BondingCurveError::Overflow) } } #[cfg(test)] mod test { + use crate::{ + BondingCurve, BondingCurveWithCheckedOperations, OperationSide, QuadraticBondingCurve, + }; + #[test] pub fn test_quadratic_price_calculus() { - let curve = crate::QuadraticBondingCurve::new(10_000_000, 500_000_000, 1_000_000_000); + let curve = QuadraticBondingCurve::new(10_000_000, 500_000_000, 1_000_000_000); let r1 = curve.base; let r2 = 1_510_000_000u64; @@ -81,4 +193,41 @@ mod test { let price = curve.calculate_price_checked(800).unwrap(); assert_eq!(price, r4); } + + #[test] + pub fn test_quadratic_price_many_calculus() { + let quadratic = 10_000_000u64; + let linear = 500_000_000u64; + let base = 1_000_000_000u64; + let amount = 10u64; + let starting_supply = 100u64; + + let curve = QuadraticBondingCurve::new(quadratic, linear, base); + + let many_price_add = + curve.calculate_price_many(starting_supply, amount, OperationSide::Add); + // Do it with a loop with calculate_price + let mut looped_price_add = 0u64; + for i in 0..amount { + looped_price_add += curve.calculate_price(starting_supply + i); + } + assert_eq!(looped_price_add, many_price_add); + let checked_many_price_add = curve + .calculate_price_many_checked(starting_supply, amount, OperationSide::Add) + .unwrap(); + assert_eq!(checked_many_price_add, many_price_add); + + let many_price_remove = + curve.calculate_price_many(starting_supply, amount, OperationSide::Remove); + // Do it with a loop with calculate_price + let mut looped_price_remove = 0u64; + for i in 0..amount { + looped_price_remove += curve.calculate_price(starting_supply - i); + } + assert_eq!(looped_price_remove, many_price_remove); + let checked_many_price_remove = curve + .calculate_price_many_checked(starting_supply, amount, OperationSide::Remove) + .unwrap(); + assert_eq!(checked_many_price_remove, many_price_remove); + } } diff --git a/crates/magic-curves/src/core/sigmoid.rs b/crates/magic-curves/src/core/sigmoid.rs index 82b63cf..24c68ce 100644 --- a/crates/magic-curves/src/core/sigmoid.rs +++ b/crates/magic-curves/src/core/sigmoid.rs @@ -1,3 +1,5 @@ +use super::{BondingCurve, OperationSide}; + #[derive(Copy, Clone, Debug, PartialEq)] pub struct SigmoidBondingCurve { pub max_price: f64, @@ -13,7 +15,9 @@ impl SigmoidBondingCurve { mid_supply, } } +} +impl BondingCurve for SigmoidBondingCurve { /// Calculates the price based on the supply. /// /// # Formula: @@ -30,20 +34,44 @@ impl SigmoidBondingCurve { /// - L is the maximum price (upper asymptote of the curve). /// - k is the growth factor. /// - x0 is the inflection point. (Can be: Max supply / 2) - pub fn calculate_price_lossy(&self, supply: u64) -> f64 { + fn calculate_price(&self, supply: u64) -> f64 { let s = supply as f64; self.max_price / (1.0 + (-self.growth * (s - self.mid_supply as f64)).exp()) } + + fn calculate_price_many(&self, starting_supply: u64, amount: u64, side: OperationSide) -> f64 { + let s = starting_supply as f64; + let n = amount as f64; + let mid_supply = self.mid_supply as f64; + let growth = self.growth; + let max_price = self.max_price; + + // Get the bounds based on whether it's an Add or Remove operation + let (start_supply, end_supply) = match side { + OperationSide::Add => (s, s + n), // Buying (adding supply) + OperationSide::Remove => (s - n, s), // Selling (removing supply) + }; + + // Perform the integral of the sigmoid function over the range + let price_at_end_supply = (1.0 + (growth * (end_supply - mid_supply)).exp()).ln(); + let price_at_start_supply = (1.0 + (growth * (start_supply - mid_supply)).exp()).ln(); + + // Total price is the difference in the integral values + (max_price / growth) * (price_at_end_supply - price_at_start_supply) + } } #[cfg(test)] mod test { - use crate::{fixed_point_to_float, float_to_fixed_point}; + use crate::{ + fixed_point_to_float, float_to_fixed_point, BondingCurve, OperationSide, + SigmoidBondingCurve, + }; #[test] pub fn test_sigmoid_price_calculus() { - let curve = crate::SigmoidBondingCurve::new(100.0, 0.01, 500); - let price = curve.calculate_price_lossy(480); + let curve = SigmoidBondingCurve::new(100.0, 0.01, 500); + let price = curve.calculate_price(480); assert_eq!(price, 45.016600268752214); } @@ -54,7 +82,19 @@ mod test { fixed_point_to_float(1, 2), 500, ); - let price = curve.calculate_price_lossy(480); + let price = curve.calculate_price(480); assert_eq!(float_to_fixed_point(price, 9), 45_016_600_268); } + + #[test] + pub fn test_sigmoid_price_calculus_many() { + let curve = SigmoidBondingCurve::new(100.0, 0.01, 500); + let many_price_add = curve.calculate_price_many(480, 10, OperationSide::Add); + assert_eq!(many_price_add, 462.5779069197911, "Add price is wrong"); + let many_price_remove = curve.calculate_price_many(480, 10, OperationSide::Remove); + assert_eq!( + many_price_remove, 437.83624913064756, + "Remove price is wrong" + ); + } } diff --git a/crates/magic-curves/src/core/types.rs b/crates/magic-curves/src/core/types.rs new file mode 100644 index 0000000..5d14669 --- /dev/null +++ b/crates/magic-curves/src/core/types.rs @@ -0,0 +1,31 @@ +use super::BondingCurveError; + +/// Represents the side of an operation in a bonding curve. +/// +/// This enum is used to specify whether an operation is adding to or removing from +/// the supply of tokens in a bonding curve system. +/// +/// # Variants +/// +/// * `Add` - Represents an operation that adds tokens to the supply. +/// * `Remove` - Represents an operation that removes tokens from the supply. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum OperationSide { + Add, + Remove, +} + +pub trait BondingCurve { + fn calculate_price(&self, supply: u64) -> T; + fn calculate_price_many(&self, starting_supply: u64, amount: u64, side: OperationSide) -> T; +} + +pub trait BondingCurveWithCheckedOperations { + fn calculate_price_checked(&self, supply: u64) -> Result; + fn calculate_price_many_checked( + &self, + starting_supply: u64, + amount: u64, + side: OperationSide, + ) -> Result; +}