Skip to content

Commit

Permalink
feat: updates curves to support bundled operations without iterating
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinrodriguez-io committed Sep 5, 2024
1 parent 112e543 commit 0c61a85
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 114 deletions.
64 changes: 40 additions & 24 deletions crates/magic-curves/src/core/exponential.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::f64::consts::E;

use super::{BondingCurve, OperationSide};

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ExponentialBondingCurve {
pub base: f64,
Expand All @@ -10,47 +12,61 @@ 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<f64> 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);
}

#[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);
}
}
142 changes: 109 additions & 33 deletions crates/magic-curves/src/core/linear.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
pub linear: T,
pub base: T,
pub struct LinearBondingCurve {
pub linear: u64,
pub base: u64,
}

impl<T> LinearBondingCurve<T>
where
T: Copy + Add<Output = T> + Mul<Output = T> + 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<u64> 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<T, BondingCurveError> {
impl BondingCurveWithCheckedOperations<u64> for LinearBondingCurve {
fn calculate_price_checked(&self, supply: u64) -> Result<u64, BondingCurveError> {
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<u64, BondingCurveError> {
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);
Expand All @@ -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);
}
}
69 changes: 52 additions & 17 deletions crates/magic-curves/src/core/logarithmic.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use super::BondingCurve;

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct LogarithmicBondingCurve {
pub base: f64,
Expand All @@ -8,43 +10,76 @@ 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<f64> 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);
}

#[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);
}
}
6 changes: 4 additions & 2 deletions crates/magic-curves/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
pub use types::*;
Loading

0 comments on commit 0c61a85

Please sign in to comment.