Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ flow-lib = { path = "lib/flow-lib", version = "0.1.0" }
space-lib = { path = "lib/space-lib", version = "0.5.0" }
space-macro = { path = "lib/space-macro", version = "0.2.1" }
spo-helius = { path = "./lib/spo-helius", version = "0.1.0" }
lbc = { path = "./lib/lbc", version = "0.1.0" }

# Non-local crates
serde = { version = "1", features = ["derive"] }
Expand Down
Empty file.
1 change: 1 addition & 0 deletions crates/cmds-std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod std;
pub mod storage;
pub mod supabase;
pub mod wait_cmd;
pub mod lbc_curve;

pub mod prelude {
pub use async_trait::async_trait;
Expand Down
8 changes: 8 additions & 0 deletions lib/lbc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "lbc"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow.workspace = true
rust_decimal = { version = "1.35.0", features = ["maths"] }
133 changes: 133 additions & 0 deletions lib/lbc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use anyhow::ensure;
use rust_decimal::{Decimal, MathematicalOps};

pub fn lbc_curve(
interval: Decimal,
start_price: Decimal,
mut min_price: Decimal,
mut max_supply: Decimal,
time_decay: Option<Decimal>,
) -> Result<TimeDecayExponentialCurve, anyhow::Error> {
ensure!(
start_price >= min_price,
"Max price must be more than min price"
);
ensure!(min_price <= Decimal::ZERO, "Min price must be more than 0");
max_supply *= Decimal::TWO;
min_price /= Decimal::TWO; // Account for ending with k = 1 instead of k = 0

// end price = start price / (1 + k0)
// (1 + k0) (end price) = start price
// (1 + k0) = (start price) / (end price)
// k0 = (start price) / (end price) - 1
let k0 = start_price / min_price - Decimal::ONE;
let k1 = Decimal::ONE; // Price should never stop increasing, or it's easy to have a big price drop at the end.

Ok(TimeDecayExponentialCurve {
k1,
k0,
interval,
c: Decimal::ONE,
d: time_decay.unwrap_or_else(|| Decimal::ONE / (k0 - Decimal::ONE).max(Decimal::ONE)),
})
}

pub struct TimeDecayExponentialCurve {
pub c: Decimal,
pub k1: Decimal,
pub k0: Decimal,
pub interval: Decimal,
pub d: Decimal,
}

impl TimeDecayExponentialCurve {
pub fn price(
&self,
time_offset: Decimal,
base_amount: Decimal,
target_supply: Decimal,
amount: Decimal,
sell: bool,
) -> Option<Decimal> {
if base_amount.eq(&Decimal::ZERO) || target_supply.eq(&Decimal::ZERO) {
price_exp_initial(
self.c,
time_decay_k(self.d, self.k0, self.k1, time_offset, self.interval)?,
amount,
)
} else {
price_exp(
time_decay_k(self.d, self.k0, self.k1, time_offset, self.interval)?,
amount,
base_amount,
target_supply,
sell,
)
}
}
}

fn price_exp(
k_prec: Decimal,
amount: Decimal,
base_amount: Decimal,
target_supply: Decimal,
sell: bool,
) -> Option<Decimal> {
/*
dR = (R / S^(1 + k)) ((S + dS)^(1 + k) - S^(1 + k))
dR = (R(S + dS)^(1 + k))/S^(1 + k) - R
log(dR + R) = log((R(S + dS)^(1 + k))/S^(1 + k))
log(dR + R) = log((R(S + dS)^(1 + k))) - log(S^(1 + k))
log(dR + R) = log(R) + (1 + k) log((S + dS)) - (1 + k)log(S)
log(dR + R) = (1 + k) (log(R(S + dS)) - log(S))
dR + R = e^(log(R) + (1 + k) (log((S + dS)) - log(S)))
dR = e^(log(R) + (1 + k) (log((S + dS)) - log(S))) - R
dR = e^(log(R) + (1 + k) (log((S + dS) / S))) - R

Edge case: selling where dS = S. Just charge them the full base amount
*/
let s_plus_ds = if sell {
target_supply.checked_sub(amount)?
} else {
target_supply.checked_add(amount)?
};
let one_plus_k_prec = Decimal::ONE.checked_add(k_prec)?;

// They're killing the curve, so it should cost the full reserves
if s_plus_ds.eq(&Decimal::ZERO) {
return Some(base_amount.clone());
}

let log1 = base_amount.ln();
let log2 = s_plus_ds.checked_div(target_supply)?.ln();
let logs = log1.checked_add(one_plus_k_prec.checked_mul(log2)?)?;
let exp = logs.exp();

Some(exp.checked_sub(base_amount)?)
}

fn price_exp_initial(c_prec: Decimal, k_prec: Decimal, amount: Decimal) -> Option<Decimal> {
// (c dS^(1 + pow/frac))/(1 + pow/frac)
let one_plus_k_prec = Decimal::ONE.checked_add(k_prec)?;
c_prec
.checked_mul(amount.powd(one_plus_k_prec))?
.checked_div(one_plus_k_prec)
}

fn time_decay_k(
d: Decimal,
k0: Decimal,
k1: Decimal,
time_offset: Decimal,
interval: Decimal,
) -> Option<Decimal> {
let time_multiplier = if time_offset.lt(&interval) {
let interval_completion = time_offset.checked_div(interval)?;
interval_completion.ln().checked_mul(d)?.exp()
} else {
Decimal::ONE
};

Some(k0.checked_sub(k0.checked_sub(k1)?.checked_mul(time_multiplier)?)?)
}