spl-math: Add separate package to test instruction usage (#1205)
* Refactor to use spl-math * Run cargo fmt * Fixup fuzz * Add spl math program * Add u64-based approximation * Cleanup * Downgrade solana sdk for CI * Move U256 impl to stable curve * Remove generic newtonian, use traits * Cargo fmt * Move math/program -> libraries/math * Revert Cargo.lock changes * Add u128 instruction count tests * cargo fmt
This commit is contained in:
parent
b823ca88a4
commit
beb4aa7e7f
|
@ -3721,6 +3721,23 @@ dependencies = [
|
|||
"spl-feature-proposal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-math"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"borsh",
|
||||
"borsh-derive",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"proptest",
|
||||
"solana-program",
|
||||
"solana-program-test",
|
||||
"solana-sdk",
|
||||
"thiserror",
|
||||
"tokio 0.3.6",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-memo"
|
||||
version = "2.0.1"
|
||||
|
@ -3901,9 +3918,9 @@ dependencies = [
|
|||
"sim",
|
||||
"solana-program",
|
||||
"solana-sdk",
|
||||
"spl-math",
|
||||
"spl-token 3.0.1",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3913,6 +3930,7 @@ dependencies = [
|
|||
"arbitrary",
|
||||
"honggfuzz",
|
||||
"solana-program",
|
||||
"spl-math",
|
||||
"spl-token 3.0.1",
|
||||
"spl-token-swap",
|
||||
]
|
||||
|
|
|
@ -8,6 +8,7 @@ members = [
|
|||
"examples/rust/transfer-lamports",
|
||||
"feature-proposal/program",
|
||||
"feature-proposal/cli",
|
||||
"libraries/math",
|
||||
"memo/program",
|
||||
"shared-memory/program",
|
||||
"stake-pool/cli",
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
[package]
|
||||
name = "spl-math"
|
||||
version = "0.1.0"
|
||||
description = "Solana Program Library Math"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana-program-library"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
test-bpf = []
|
||||
|
||||
[dependencies]
|
||||
borsh = "0.7.1"
|
||||
borsh-derive = "0.7.1"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
solana-program = "1.5.4"
|
||||
thiserror = "1.0"
|
||||
uint = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "0.10"
|
||||
solana-program-test = "1.5.4"
|
||||
solana-sdk = "1.5.4"
|
||||
tokio = { version = "0.3", features = ["macros"]}
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,61 @@
|
|||
//! Approximation calculations
|
||||
|
||||
use {
|
||||
num_traits::{CheckedAdd, CheckedDiv, One, Zero},
|
||||
std::cmp::Eq,
|
||||
};
|
||||
|
||||
const SQRT_ITERATIONS: u8 = 50;
|
||||
|
||||
/// Perform square root
|
||||
pub fn sqrt<T: CheckedAdd + CheckedDiv + One + Zero + Eq + Copy>(radicand: T) -> Option<T> {
|
||||
if radicand == T::zero() {
|
||||
return Some(T::zero());
|
||||
}
|
||||
// A good initial guess is the average of the interval that contains the
|
||||
// input number. For all numbers, that will be between 1 and the given number.
|
||||
let one = T::one();
|
||||
let two = one.checked_add(&one)?;
|
||||
let mut guess = radicand.checked_div(&two)?.checked_add(&one)?;
|
||||
let mut last_guess = guess;
|
||||
for _ in 0..SQRT_ITERATIONS {
|
||||
// x_k+1 = (x_k + radicand / x_k) / 2
|
||||
guess = last_guess
|
||||
.checked_add(&radicand.checked_div(&last_guess)?)?
|
||||
.checked_div(&two)?;
|
||||
if last_guess == guess {
|
||||
break;
|
||||
} else {
|
||||
last_guess = guess;
|
||||
}
|
||||
}
|
||||
Some(guess)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use {super::*, proptest::prelude::*};
|
||||
|
||||
fn check_square_root(radicand: u128) {
|
||||
let root = sqrt(radicand).unwrap();
|
||||
let lower_bound = root.saturating_sub(1).checked_pow(2).unwrap();
|
||||
let upper_bound = root.checked_add(1).unwrap().checked_pow(2).unwrap();
|
||||
assert!(radicand as u128 <= upper_bound);
|
||||
assert!(radicand as u128 >= lower_bound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_square_root_min_max() {
|
||||
let test_roots = [0, u64::MAX];
|
||||
for i in test_roots.iter() {
|
||||
check_square_root(*i as u128);
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_square_root(a in 0..u64::MAX) {
|
||||
check_square_root(a as u128);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
//! Program entrypoint
|
||||
|
||||
#![cfg(not(feature = "no-entrypoint"))]
|
||||
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
entrypoint!(process_instruction);
|
||||
fn process_instruction(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
instruction_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
crate::processor::process_instruction(program_id, accounts, instruction_data)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
//! Error types
|
||||
|
||||
use {
|
||||
num_derive::FromPrimitive,
|
||||
solana_program::{decode_error::DecodeError, program_error::ProgramError},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
/// Errors that may be returned by the Math program.
|
||||
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
|
||||
pub enum MathError {
|
||||
/// Calculation overflowed the destination number
|
||||
#[error("Calculation overflowed the destination number")]
|
||||
Overflow,
|
||||
/// Calculation underflowed the destination number
|
||||
#[error("Calculation underflowed the destination number")]
|
||||
Underflow,
|
||||
}
|
||||
impl From<MathError> for ProgramError {
|
||||
fn from(e: MathError) -> Self {
|
||||
ProgramError::Custom(e as u32)
|
||||
}
|
||||
}
|
||||
impl<T> DecodeError<T> for MathError {
|
||||
fn type_of() -> &'static str {
|
||||
"Math Error"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
//! Program instructions, used for end-to-end testing and instruction counts
|
||||
|
||||
use {
|
||||
crate::id,
|
||||
borsh::{BorshDeserialize, BorshSerialize},
|
||||
solana_program::instruction::Instruction,
|
||||
};
|
||||
|
||||
/// Instructions supported by the math program, used for testing instruction
|
||||
/// counts
|
||||
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
|
||||
pub enum MathInstruction {
|
||||
/// Calculate the square root of the given u64 with decimals
|
||||
///
|
||||
/// No accounts required for this instruction
|
||||
PreciseSquareRoot {
|
||||
/// Number underneath the square root sign, whose square root will be
|
||||
/// calculated
|
||||
radicand: u64,
|
||||
},
|
||||
/// Calculate the integer square root of the given u64
|
||||
///
|
||||
/// No accounts required for this instruction
|
||||
SquareRootU64 {
|
||||
/// Number underneath the square root sign, whose square root will be
|
||||
/// calculated
|
||||
radicand: u64,
|
||||
},
|
||||
/// Calculate the integer square root of the given u128
|
||||
///
|
||||
/// No accounts required for this instruction
|
||||
SquareRootU128 {
|
||||
/// Number underneath the square root sign, whose square root will be
|
||||
/// calculated
|
||||
radicand: u128,
|
||||
},
|
||||
}
|
||||
|
||||
/// Create PreciseSquareRoot instruction
|
||||
pub fn precise_sqrt(radicand: u64) -> Instruction {
|
||||
Instruction {
|
||||
program_id: id(),
|
||||
accounts: vec![],
|
||||
data: MathInstruction::PreciseSquareRoot { radicand }
|
||||
.try_to_vec()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create SquareRoot instruction
|
||||
pub fn sqrt_u64(radicand: u64) -> Instruction {
|
||||
Instruction {
|
||||
program_id: id(),
|
||||
accounts: vec![],
|
||||
data: MathInstruction::SquareRootU64 { radicand }
|
||||
.try_to_vec()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create SquareRoot instruction
|
||||
pub fn sqrt_u128(radicand: u128) -> Instruction {
|
||||
Instruction {
|
||||
program_id: id(),
|
||||
accounts: vec![],
|
||||
data: MathInstruction::SquareRootU128 { radicand }
|
||||
.try_to_vec()
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
//! Math operations using unsigned integers
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod approximations;
|
||||
mod entrypoint;
|
||||
pub mod error;
|
||||
pub mod instruction;
|
||||
pub mod precise_number;
|
||||
pub mod processor;
|
||||
pub mod uint;
|
||||
|
||||
solana_program::declare_id!("Math111111111111111111111111111111111111111");
|
|
@ -0,0 +1,573 @@
|
|||
//! Defines PreciseNumber, a U256 wrapper with float-like operations
|
||||
|
||||
use crate::uint::U256;
|
||||
|
||||
// Allows for easy swapping between different internal representations
|
||||
type InnerUint = U256;
|
||||
|
||||
/// The representation of the number one as a precise number as 10^12
|
||||
pub const ONE: u128 = 1_000_000_000_000;
|
||||
|
||||
/// Struct encapsulating a fixed-point number that allows for decimal calculations
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PreciseNumber {
|
||||
/// Wrapper over the inner value, which is multiplied by ONE
|
||||
pub value: InnerUint,
|
||||
}
|
||||
|
||||
/// The precise-number 1 as a InnerUint
|
||||
fn one() -> InnerUint {
|
||||
InnerUint::from(ONE)
|
||||
}
|
||||
|
||||
/// The number 0 as a PreciseNumber, used for easier calculations.
|
||||
fn zero() -> InnerUint {
|
||||
InnerUint::from(0)
|
||||
}
|
||||
|
||||
impl PreciseNumber {
|
||||
/// Correction to apply to avoid truncation errors on division. Since
|
||||
/// integer operations will always floor the result, we artifically bump it
|
||||
/// up by one half to get the expect result.
|
||||
fn rounding_correction() -> InnerUint {
|
||||
InnerUint::from(ONE / 2)
|
||||
}
|
||||
|
||||
/// Desired precision for the correction factor applied during each
|
||||
/// iteration of checked_pow_approximation. Once the correction factor is
|
||||
/// smaller than this number, or we reach the maxmium number of iterations,
|
||||
/// the calculation ends.
|
||||
fn precision() -> InnerUint {
|
||||
InnerUint::from(100)
|
||||
}
|
||||
|
||||
fn zero() -> Self {
|
||||
Self { value: zero() }
|
||||
}
|
||||
|
||||
fn one() -> Self {
|
||||
Self { value: one() }
|
||||
}
|
||||
|
||||
/// Maximum number iterations to apply on checked_pow_approximation.
|
||||
const MAX_APPROXIMATION_ITERATIONS: u128 = 100;
|
||||
|
||||
/// Minimum base allowed when calculating exponents in checked_pow_fraction
|
||||
/// and checked_pow_approximation. This simply avoids 0 as a base.
|
||||
fn min_pow_base() -> InnerUint {
|
||||
InnerUint::from(1)
|
||||
}
|
||||
|
||||
/// Maximum base allowed when calculating exponents in checked_pow_fraction
|
||||
/// and checked_pow_approximation. The calculation use a Taylor Series
|
||||
/// approxmation around 1, which converges for bases between 0 and 2. See
|
||||
/// https://en.wikipedia.org/wiki/Binomial_series#Conditions_for_convergence
|
||||
/// for more information.
|
||||
fn max_pow_base() -> InnerUint {
|
||||
InnerUint::from(2 * ONE)
|
||||
}
|
||||
|
||||
/// Create a precise number from an imprecise u128, should always succeed
|
||||
pub fn new(value: u128) -> Option<Self> {
|
||||
let value = InnerUint::from(value).checked_mul(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Convert a precise number back to u128
|
||||
pub fn to_imprecise(&self) -> Option<u128> {
|
||||
match self
|
||||
.value
|
||||
.checked_add(Self::rounding_correction())?
|
||||
.checked_div(one())
|
||||
{
|
||||
Some(v) => Some(v.as_u128()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that two PreciseNumbers are equal within some tolerance
|
||||
pub fn almost_eq(&self, rhs: &Self, precision: InnerUint) -> bool {
|
||||
let (difference, _) = self.unsigned_sub(rhs);
|
||||
difference.value < precision
|
||||
}
|
||||
|
||||
/// Checks that a number is less than another
|
||||
pub fn less_than(&self, rhs: &Self) -> bool {
|
||||
self.value < rhs.value
|
||||
}
|
||||
|
||||
/// Checks that a number is greater than another
|
||||
pub fn greater_than(&self, rhs: &Self) -> bool {
|
||||
self.value > rhs.value
|
||||
}
|
||||
|
||||
/// Checks that a number is less than another
|
||||
pub fn less_than_or_equal(&self, rhs: &Self) -> bool {
|
||||
self.value <= rhs.value
|
||||
}
|
||||
|
||||
/// Checks that a number is greater than another
|
||||
pub fn greater_than_or_equal(&self, rhs: &Self) -> bool {
|
||||
self.value >= rhs.value
|
||||
}
|
||||
|
||||
/// Floors a precise value to a precision of ONE
|
||||
pub fn floor(&self) -> Option<Self> {
|
||||
let value = self.value.checked_div(one())?.checked_mul(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Ceiling a precise value to a precision of ONE
|
||||
pub fn ceiling(&self) -> Option<Self> {
|
||||
let value = self
|
||||
.value
|
||||
.checked_add(one().checked_sub(InnerUint::from(1))?)?
|
||||
.checked_div(one())?
|
||||
.checked_mul(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Performs a checked division on two precise numbers
|
||||
pub fn checked_div(&self, rhs: &Self) -> Option<Self> {
|
||||
if *rhs == Self::zero() {
|
||||
return None;
|
||||
}
|
||||
match self.value.checked_mul(one()) {
|
||||
Some(v) => {
|
||||
let value = v
|
||||
.checked_add(Self::rounding_correction())?
|
||||
.checked_div(rhs.value)?;
|
||||
Some(Self { value })
|
||||
}
|
||||
None => {
|
||||
let value = self
|
||||
.value
|
||||
.checked_add(Self::rounding_correction())?
|
||||
.checked_div(rhs.value)?
|
||||
.checked_mul(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a multiplication on two precise numbers
|
||||
pub fn checked_mul(&self, rhs: &Self) -> Option<Self> {
|
||||
match self.value.checked_mul(rhs.value) {
|
||||
Some(v) => {
|
||||
let value = v
|
||||
.checked_add(Self::rounding_correction())?
|
||||
.checked_div(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
None => {
|
||||
let value = if self.value >= rhs.value {
|
||||
self.value.checked_div(one())?.checked_mul(rhs.value)?
|
||||
} else {
|
||||
rhs.value.checked_div(one())?.checked_mul(self.value)?
|
||||
};
|
||||
Some(Self { value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs addition of two precise numbers
|
||||
pub fn checked_add(&self, rhs: &Self) -> Option<Self> {
|
||||
let value = self.value.checked_add(rhs.value)?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Subtracts the argument from self
|
||||
pub fn checked_sub(&self, rhs: &Self) -> Option<Self> {
|
||||
let value = self.value.checked_sub(rhs.value)?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Performs a subtraction, returning the result and whether the result is negative
|
||||
pub fn unsigned_sub(&self, rhs: &Self) -> (Self, bool) {
|
||||
match self.value.checked_sub(rhs.value) {
|
||||
None => {
|
||||
let value = rhs.value.checked_sub(self.value).unwrap();
|
||||
(Self { value }, true)
|
||||
}
|
||||
Some(value) => (Self { value }, false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs pow on a precise number
|
||||
pub fn checked_pow(&self, exponent: u128) -> Option<Self> {
|
||||
// For odd powers, start with a multiplication by base since we halve the
|
||||
// exponent at the start
|
||||
let value = if exponent.checked_rem(2)? == 0 {
|
||||
one()
|
||||
} else {
|
||||
self.value
|
||||
};
|
||||
let mut result = Self { value };
|
||||
|
||||
// To minimize the number of operations, we keep squaring the base, and
|
||||
// only push to the result on odd exponents, like a binary decomposition
|
||||
// of the exponent.
|
||||
let mut squared_base = self.clone();
|
||||
let mut current_exponent = exponent.checked_div(2)?;
|
||||
while current_exponent != 0 {
|
||||
squared_base = squared_base.checked_mul(&squared_base)?;
|
||||
|
||||
// For odd exponents, "push" the base onto the value
|
||||
if current_exponent.checked_rem(2)? != 0 {
|
||||
result = result.checked_mul(&squared_base)?;
|
||||
}
|
||||
|
||||
current_exponent = current_exponent.checked_div(2)?;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Approximate the nth root of a number using a Taylor Series around 1 on
|
||||
/// x ^ n, where 0 < n < 1, result is a precise number.
|
||||
/// Refine the guess for each term, using:
|
||||
/// 1 2
|
||||
/// f(x) = f(a) + f'(a) * (x - a) + --- * f''(a) * (x - a) + ...
|
||||
/// 2!
|
||||
/// For x ^ n, this gives:
|
||||
/// n n n-1 1 n-2 2
|
||||
/// x = a + n * a (x - a) + --- * n * (n - 1) a (x - a) + ...
|
||||
/// 2!
|
||||
///
|
||||
/// More simply, this means refining the term at each iteration with:
|
||||
///
|
||||
/// t_k+1 = t_k * (x - a) * (n + 1 - k) / k
|
||||
///
|
||||
/// where a = 1, n = power, x = precise_num
|
||||
/// NOTE: this function is private because its accurate range and precision
|
||||
/// have not been estbalished.
|
||||
fn checked_pow_approximation(&self, exponent: &Self, max_iterations: u128) -> Option<Self> {
|
||||
assert!(self.value >= Self::min_pow_base());
|
||||
assert!(self.value <= Self::max_pow_base());
|
||||
let one = Self::one();
|
||||
if *exponent == Self::zero() {
|
||||
return Some(one);
|
||||
}
|
||||
let mut precise_guess = one.clone();
|
||||
let mut term = precise_guess.clone();
|
||||
let (x_minus_a, x_minus_a_negative) = self.unsigned_sub(&precise_guess);
|
||||
let exponent_plus_one = exponent.checked_add(&one)?;
|
||||
let mut negative = false;
|
||||
for k in 1..max_iterations {
|
||||
let k = Self::new(k)?;
|
||||
let (current_exponent, current_exponent_negative) = exponent_plus_one.unsigned_sub(&k);
|
||||
term = term.checked_mul(¤t_exponent)?;
|
||||
term = term.checked_mul(&x_minus_a)?;
|
||||
term = term.checked_div(&k)?;
|
||||
if term.value < Self::precision() {
|
||||
break;
|
||||
}
|
||||
if x_minus_a_negative {
|
||||
negative = !negative;
|
||||
}
|
||||
if current_exponent_negative {
|
||||
negative = !negative;
|
||||
}
|
||||
if negative {
|
||||
precise_guess = precise_guess.checked_sub(&term)?;
|
||||
} else {
|
||||
precise_guess = precise_guess.checked_add(&term)?;
|
||||
}
|
||||
}
|
||||
Some(precise_guess)
|
||||
}
|
||||
|
||||
/// Get the power of a number, where the exponent is expressed as a fraction
|
||||
/// (numerator / denominator)
|
||||
/// NOTE: this function is private because its accurate range and precision
|
||||
/// have not been estbalished.
|
||||
#[allow(dead_code)]
|
||||
fn checked_pow_fraction(&self, exponent: &Self) -> Option<Self> {
|
||||
assert!(self.value >= Self::min_pow_base());
|
||||
assert!(self.value <= Self::max_pow_base());
|
||||
let whole_exponent = exponent.floor()?;
|
||||
let precise_whole = self.checked_pow(whole_exponent.to_imprecise()?)?;
|
||||
let (remainder_exponent, negative) = exponent.unsigned_sub(&whole_exponent);
|
||||
assert!(!negative);
|
||||
if remainder_exponent.value == InnerUint::from(0) {
|
||||
return Some(precise_whole);
|
||||
}
|
||||
let precise_remainder = self
|
||||
.checked_pow_approximation(&remainder_exponent, Self::MAX_APPROXIMATION_ITERATIONS)?;
|
||||
precise_whole.checked_mul(&precise_remainder)
|
||||
}
|
||||
|
||||
/// Approximate the nth root of a number using Newton's method
|
||||
/// https://en.wikipedia.org/wiki/Newton%27s_method
|
||||
/// NOTE: this function is private because its accurate range and precision
|
||||
/// have not been established.
|
||||
fn newtonian_root_approximation(
|
||||
&self,
|
||||
root: &Self,
|
||||
mut guess: Self,
|
||||
iterations: u128,
|
||||
) -> Option<Self> {
|
||||
let zero = Self::zero();
|
||||
if *self == zero {
|
||||
return Some(zero);
|
||||
}
|
||||
if *root == zero {
|
||||
return None;
|
||||
}
|
||||
let one = Self::new(1)?;
|
||||
let root_minus_one = root.checked_sub(&one)?;
|
||||
let root_minus_one_whole = root_minus_one.to_imprecise()?;
|
||||
let mut last_guess = guess.clone();
|
||||
let precision = Self::precision();
|
||||
for _ in 0..iterations {
|
||||
// x_k+1 = ((n - 1) * x_k + A / (x_k ^ (n - 1))) / n
|
||||
let first_term = root_minus_one.checked_mul(&guess)?;
|
||||
let power = guess.checked_pow(root_minus_one_whole);
|
||||
let second_term = match power {
|
||||
Some(num) => self.checked_div(&num)?,
|
||||
None => Self::new(0)?,
|
||||
};
|
||||
guess = first_term.checked_add(&second_term)?.checked_div(&root)?;
|
||||
if last_guess.almost_eq(&guess, precision) {
|
||||
break;
|
||||
} else {
|
||||
last_guess = guess.clone();
|
||||
}
|
||||
}
|
||||
Some(guess)
|
||||
}
|
||||
|
||||
/// Based on testing around the limits, this base is the smallest value that
|
||||
/// provides an epsilon 11 digits
|
||||
fn minimum_sqrt_base() -> Self {
|
||||
Self {
|
||||
value: InnerUint::from(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Based on testing around the limits, this base is the smallest value that
|
||||
/// provides an epsilon of 11 digits
|
||||
fn maximum_sqrt_base() -> Self {
|
||||
Self::new(std::u128::MAX).unwrap()
|
||||
}
|
||||
|
||||
/// Approximate the square root using Newton's method. Based on testing,
|
||||
/// this provides a precision of 11 digits for inputs between 0 and u128::MAX
|
||||
pub fn sqrt(&self) -> Option<Self> {
|
||||
if self.less_than(&Self::minimum_sqrt_base())
|
||||
|| self.greater_than(&Self::maximum_sqrt_base())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let two = PreciseNumber::new(2)?;
|
||||
let one = PreciseNumber::new(1)?;
|
||||
// A good initial guess is the average of the interval that contains the
|
||||
// input number. For all numbers, that will be between 1 and the given number.
|
||||
let guess = self.checked_add(&one)?.checked_div(&two)?;
|
||||
self.newtonian_root_approximation(&two, guess, Self::MAX_APPROXIMATION_ITERATIONS)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
fn check_pow_approximation(base: InnerUint, exponent: InnerUint, expected: InnerUint) {
|
||||
let precision = InnerUint::from(5_000_000); // correct to at least 3 decimal places
|
||||
let base = PreciseNumber { value: base };
|
||||
let exponent = PreciseNumber { value: exponent };
|
||||
let root = base
|
||||
.checked_pow_approximation(&exponent, PreciseNumber::MAX_APPROXIMATION_ITERATIONS)
|
||||
.unwrap();
|
||||
let expected = PreciseNumber { value: expected };
|
||||
assert!(root.almost_eq(&expected, precision));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_approximation() {
|
||||
let one = one();
|
||||
// square root
|
||||
check_pow_approximation(one / 4, one / 2, one / 2); // 1/2
|
||||
check_pow_approximation(one * 11 / 10, one / 2, InnerUint::from(1_048808848161u128)); // 1.048808848161
|
||||
|
||||
// 5th root
|
||||
check_pow_approximation(one * 4 / 5, one * 2 / 5, InnerUint::from(914610103850u128));
|
||||
// 0.91461010385
|
||||
|
||||
// 10th root
|
||||
check_pow_approximation(one / 2, one * 4 / 50, InnerUint::from(946057646730u128));
|
||||
// 0.94605764673
|
||||
}
|
||||
|
||||
fn check_pow_fraction(
|
||||
base: InnerUint,
|
||||
exponent: InnerUint,
|
||||
expected: InnerUint,
|
||||
precision: InnerUint,
|
||||
) {
|
||||
let base = PreciseNumber { value: base };
|
||||
let exponent = PreciseNumber { value: exponent };
|
||||
let power = base.checked_pow_fraction(&exponent).unwrap();
|
||||
let expected = PreciseNumber { value: expected };
|
||||
assert!(power.almost_eq(&expected, precision));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pow_fraction() {
|
||||
let one = one();
|
||||
let precision = InnerUint::from(50_000_000); // correct to at least 3 decimal places
|
||||
let less_precision = precision * 1_000; // correct to at least 1 decimal place
|
||||
check_pow_fraction(one, one, one, precision);
|
||||
check_pow_fraction(
|
||||
one * 20 / 13,
|
||||
one * 50 / 3,
|
||||
InnerUint::from(1312_534484739100u128),
|
||||
precision,
|
||||
); // 1312.5344847391
|
||||
check_pow_fraction(one * 2 / 7, one * 49 / 4, InnerUint::from(2163), precision);
|
||||
check_pow_fraction(
|
||||
one * 5000 / 5100,
|
||||
one / 9,
|
||||
InnerUint::from(997802126900u128),
|
||||
precision,
|
||||
); // 0.99780212695
|
||||
// results get less accurate as the base gets further from 1, so allow
|
||||
// for a greater margin of error
|
||||
check_pow_fraction(
|
||||
one * 2,
|
||||
one * 27 / 5,
|
||||
InnerUint::from(42_224253144700u128),
|
||||
less_precision,
|
||||
); // 42.2242531447
|
||||
check_pow_fraction(
|
||||
one * 18 / 10,
|
||||
one * 11 / 3,
|
||||
InnerUint::from(8_629769290500u128),
|
||||
less_precision,
|
||||
); // 8.629769290
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newtonian_approximation() {
|
||||
// square root
|
||||
let test = PreciseNumber::new(9).unwrap();
|
||||
let nth_root = PreciseNumber::new(2).unwrap();
|
||||
let guess = test.checked_div(&nth_root).unwrap();
|
||||
let root = test
|
||||
.newtonian_root_approximation(
|
||||
&nth_root,
|
||||
guess,
|
||||
PreciseNumber::MAX_APPROXIMATION_ITERATIONS,
|
||||
)
|
||||
.unwrap()
|
||||
.to_imprecise()
|
||||
.unwrap();
|
||||
assert_eq!(root, 3); // actually 3
|
||||
|
||||
let test = PreciseNumber::new(101).unwrap();
|
||||
let nth_root = PreciseNumber::new(2).unwrap();
|
||||
let guess = test.checked_div(&nth_root).unwrap();
|
||||
let root = test
|
||||
.newtonian_root_approximation(
|
||||
&nth_root,
|
||||
guess,
|
||||
PreciseNumber::MAX_APPROXIMATION_ITERATIONS,
|
||||
)
|
||||
.unwrap()
|
||||
.to_imprecise()
|
||||
.unwrap();
|
||||
assert_eq!(root, 10); // actually 10.049875
|
||||
|
||||
let test = PreciseNumber::new(1_000_000_000).unwrap();
|
||||
let nth_root = PreciseNumber::new(2).unwrap();
|
||||
let guess = test.checked_div(&nth_root).unwrap();
|
||||
let root = test
|
||||
.newtonian_root_approximation(
|
||||
&nth_root,
|
||||
guess,
|
||||
PreciseNumber::MAX_APPROXIMATION_ITERATIONS,
|
||||
)
|
||||
.unwrap()
|
||||
.to_imprecise()
|
||||
.unwrap();
|
||||
assert_eq!(root, 31_623); // actually 31622.7766
|
||||
|
||||
// 5th root
|
||||
let test = PreciseNumber::new(500).unwrap();
|
||||
let nth_root = PreciseNumber::new(5).unwrap();
|
||||
let guess = test.checked_div(&nth_root).unwrap();
|
||||
let root = test
|
||||
.newtonian_root_approximation(
|
||||
&nth_root,
|
||||
guess,
|
||||
PreciseNumber::MAX_APPROXIMATION_ITERATIONS,
|
||||
)
|
||||
.unwrap()
|
||||
.to_imprecise()
|
||||
.unwrap();
|
||||
assert_eq!(root, 3); // actually 3.46572422
|
||||
}
|
||||
|
||||
fn check_square_root(check: &PreciseNumber) {
|
||||
let epsilon = PreciseNumber {
|
||||
value: InnerUint::from(10),
|
||||
}; // correct within 11 decimals
|
||||
let one = PreciseNumber::one();
|
||||
let one_plus_epsilon = one.checked_add(&epsilon).unwrap();
|
||||
let one_minus_epsilon = one.checked_sub(&epsilon).unwrap();
|
||||
let approximate_root = check.sqrt().unwrap();
|
||||
let lower_bound = approximate_root
|
||||
.checked_mul(&one_minus_epsilon)
|
||||
.unwrap()
|
||||
.checked_pow(2)
|
||||
.unwrap();
|
||||
let upper_bound = approximate_root
|
||||
.checked_mul(&one_plus_epsilon)
|
||||
.unwrap()
|
||||
.checked_pow(2)
|
||||
.unwrap();
|
||||
assert!(check.less_than_or_equal(&upper_bound));
|
||||
assert!(check.greater_than_or_equal(&lower_bound));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_square_root_min_max() {
|
||||
let test_roots = [
|
||||
PreciseNumber::minimum_sqrt_base(),
|
||||
PreciseNumber::maximum_sqrt_base(),
|
||||
];
|
||||
for i in test_roots.iter() {
|
||||
check_square_root(i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_floor() {
|
||||
let whole_number = PreciseNumber::new(2).unwrap();
|
||||
let mut decimal_number = PreciseNumber::new(2).unwrap();
|
||||
decimal_number.value += InnerUint::from(1);
|
||||
let floor = decimal_number.floor().unwrap();
|
||||
let floor_again = floor.floor().unwrap();
|
||||
assert_eq!(whole_number.value, floor.value);
|
||||
assert_eq!(whole_number.value, floor_again.value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ceiling() {
|
||||
let whole_number = PreciseNumber::new(2).unwrap();
|
||||
let mut decimal_number = PreciseNumber::new(2).unwrap();
|
||||
decimal_number.value -= InnerUint::from(1);
|
||||
let ceiling = decimal_number.ceiling().unwrap();
|
||||
let ceiling_again = ceiling.ceiling().unwrap();
|
||||
assert_eq!(whole_number.value, ceiling.value);
|
||||
assert_eq!(whole_number.value, ceiling_again.value);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_square_root(a in 0..u128::MAX) {
|
||||
let a = PreciseNumber { value: InnerUint::from(a) };
|
||||
check_square_root(&a);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//! Program state processor
|
||||
|
||||
use {
|
||||
crate::{approximations::sqrt, instruction::MathInstruction, precise_number::PreciseNumber},
|
||||
borsh::BorshDeserialize,
|
||||
solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey},
|
||||
};
|
||||
|
||||
/// Instruction processor
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
_accounts: &[AccountInfo],
|
||||
input: &[u8],
|
||||
) -> ProgramResult {
|
||||
let instruction = MathInstruction::try_from_slice(input).unwrap();
|
||||
match instruction {
|
||||
MathInstruction::PreciseSquareRoot { radicand } => {
|
||||
msg!("Calculating square root using PreciseNumber");
|
||||
let radicand = PreciseNumber::new(radicand as u128).unwrap();
|
||||
let result = radicand.sqrt().unwrap().to_imprecise().unwrap() as u64;
|
||||
msg!("{}", result);
|
||||
Ok(())
|
||||
}
|
||||
MathInstruction::SquareRootU64 { radicand } => {
|
||||
msg!("Calculating u64 square root");
|
||||
let result = sqrt(radicand).unwrap();
|
||||
msg!("{}", result);
|
||||
Ok(())
|
||||
}
|
||||
MathInstruction::SquareRootU128 { radicand } => {
|
||||
msg!("Calculating u128 square root");
|
||||
let result = sqrt(radicand).unwrap();
|
||||
msg!("{}", result);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
//! Large uint types
|
||||
|
||||
// required for clippy
|
||||
#![allow(clippy::assign_op_pattern)]
|
||||
#![allow(clippy::ptr_offset_with_cast)]
|
||||
#![allow(clippy::unknown_clippy_lints)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
use uint::construct_uint;
|
||||
|
||||
construct_uint! {
|
||||
pub struct U256(4);
|
||||
}
|
||||
construct_uint! {
|
||||
pub struct U192(3);
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// Mark this test as BPF-only due to current `ProgramTest` limitations when CPIing into the system program
|
||||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use {
|
||||
solana_program::pubkey::Pubkey,
|
||||
solana_program_test::{processor, ProgramTest},
|
||||
solana_sdk::{signature::Signer, transaction::Transaction},
|
||||
spl_math::{id, instruction, processor::process_instruction},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_precise_sqrt_u64_max() {
|
||||
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||
|
||||
// This is way too big! It's possible to dial down the numbers to get to
|
||||
// something reasonable, but the better option is to do everything in u64
|
||||
pc.set_bpf_compute_max_units(350_000);
|
||||
|
||||
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[instruction::precise_sqrt(u64::MAX)],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_precise_sqrt_u32_max() {
|
||||
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||
|
||||
pc.set_bpf_compute_max_units(170_000);
|
||||
|
||||
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[instruction::precise_sqrt(u32::MAX as u64)],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sqrt_u64() {
|
||||
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||
|
||||
// Dial down the BPF compute budget to detect if the operation gets bloated in the future
|
||||
pc.set_bpf_compute_max_units(2_500);
|
||||
|
||||
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
||||
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[instruction::sqrt_u64(u64::MAX)], Some(&payer.pubkey()));
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sqrt_u128() {
|
||||
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||
|
||||
// Dial down the BPF compute budget to detect if the operation gets bloated in the future
|
||||
pc.set_bpf_compute_max_units(5_500);
|
||||
|
||||
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
||||
|
||||
let mut transaction = Transaction::new_with_payer(
|
||||
&[instruction::sqrt_u128(u64::MAX as u128)],
|
||||
Some(&payer.pubkey()),
|
||||
);
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sqrt_u128_max() {
|
||||
let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
|
||||
|
||||
// This is pretty big too!
|
||||
pc.set_bpf_compute_max_units(90_000);
|
||||
|
||||
let (mut banks_client, payer, recent_blockhash) = pc.start().await;
|
||||
|
||||
let mut transaction =
|
||||
Transaction::new_with_payer(&[instruction::sqrt_u128(u128::MAX)], Some(&payer.pubkey()));
|
||||
transaction.sign(&[&payer], recent_blockhash);
|
||||
banks_client.process_transaction(transaction).await.unwrap();
|
||||
}
|
|
@ -18,9 +18,9 @@ enum_dispatch = "0.3.4"
|
|||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
solana-program = "1.5.1"
|
||||
spl-math = { version = "0.1", path = "../../libraries/math", features = [ "no-entrypoint" ] }
|
||||
spl-token = { version = "3.0", path = "../../token/program", features = [ "no-entrypoint" ] }
|
||||
thiserror = "1.0"
|
||||
uint = "0.8"
|
||||
arbitrary = { version = "0.4", features = ["derive"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -12,6 +12,7 @@ publish = false
|
|||
honggfuzz = { version = "0.5.52" }
|
||||
arbitrary = { version = "0.4", features = ["derive"] }
|
||||
solana-program = "1.5.1"
|
||||
spl-math = { version = "0.1", path = "../../../libraries/math", features = [ "no-entrypoint" ] }
|
||||
spl-token = { version = "3.0", path = "../../../token/program", features = [ "no-entrypoint" ] }
|
||||
spl-token-swap = { path = "..", features = ["fuzz", "no-entrypoint"] }
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ use spl_token_swap::{
|
|||
calculator::CurveCalculator,
|
||||
constant_product::ConstantProductCurve,
|
||||
fees::Fees,
|
||||
math::PreciseNumber,
|
||||
},
|
||||
error::SwapError,
|
||||
instruction::{
|
||||
|
@ -19,6 +18,7 @@ use spl_token_swap::{
|
|||
},
|
||||
};
|
||||
|
||||
use spl_math::precise_number::PreciseNumber;
|
||||
use spl_token::error::TokenError;
|
||||
|
||||
use honggfuzz::fuzz;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! Swap calculations
|
||||
|
||||
use crate::{curve::math::PreciseNumber, error::SwapError};
|
||||
use std::fmt::Debug;
|
||||
use {crate::error::SwapError, spl_math::precise_number::PreciseNumber, std::fmt::Debug};
|
||||
|
||||
/// Initial amount of pool tokens for swap contract, hard-coded to something
|
||||
/// "sensible" given a maximum of u128.
|
||||
|
@ -174,8 +173,8 @@ pub trait CurveCalculator: Debug + DynPack {
|
|||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use crate::curve::math::U256;
|
||||
use proptest::prelude::*;
|
||||
use spl_math::uint::U256;
|
||||
|
||||
/// The epsilon for most curves when performing the conversion test,
|
||||
/// comparing a one-sided deposit to a swap + deposit.
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
map_zero_to_none, CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult,
|
||||
TradeDirection, TradingTokenResult,
|
||||
},
|
||||
curve::math::{CheckedCeilDiv, PreciseNumber, U256},
|
||||
curve::math::CheckedCeilDiv,
|
||||
error::SwapError,
|
||||
};
|
||||
use arrayref::{array_mut_ref, array_ref};
|
||||
|
@ -13,6 +13,7 @@ use solana_program::{
|
|||
program_error::ProgramError,
|
||||
program_pack::{IsInitialized, Pack, Sealed},
|
||||
};
|
||||
use spl_math::{precise_number::PreciseNumber, uint::U256};
|
||||
|
||||
/// ConstantPriceCurve struct implementing CurveCalculator
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
//! The Uniswap invariant calculator.
|
||||
|
||||
use solana_program::{
|
||||
program_error::ProgramError,
|
||||
program_pack::{IsInitialized, Pack, Sealed},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
curve::calculator::{
|
||||
map_zero_to_none, CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult,
|
||||
TradeDirection, TradingTokenResult,
|
||||
use {
|
||||
crate::{
|
||||
curve::calculator::{
|
||||
map_zero_to_none, CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult,
|
||||
TradeDirection, TradingTokenResult,
|
||||
},
|
||||
curve::math::CheckedCeilDiv,
|
||||
error::SwapError,
|
||||
},
|
||||
curve::math::{CheckedCeilDiv, PreciseNumber},
|
||||
error::SwapError,
|
||||
solana_program::{
|
||||
program_error::ProgramError,
|
||||
program_pack::{IsInitialized, Pack, Sealed},
|
||||
},
|
||||
spl_math::precise_number::PreciseNumber,
|
||||
};
|
||||
|
||||
/// ConstantProductCurve struct implementing CurveCalculator
|
||||
|
|
|
@ -1,44 +1,6 @@
|
|||
//! Defines useful math utils
|
||||
|
||||
#![allow(clippy::assign_op_pattern)]
|
||||
#![allow(clippy::ptr_offset_with_cast)]
|
||||
#![allow(clippy::unknown_clippy_lints)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
use uint::construct_uint;
|
||||
|
||||
construct_uint! {
|
||||
pub struct U256(4);
|
||||
}
|
||||
|
||||
impl U256 {
|
||||
/// Returns selt to the power of b
|
||||
pub fn checked_u8_power(&self, b: u8) -> Option<U256> {
|
||||
let mut result = *self;
|
||||
for _ in 1..b {
|
||||
result = result.checked_mul(*self)?;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Returns self multiplied by b
|
||||
pub fn checked_u8_mul(&self, b: u8) -> Option<U256> {
|
||||
let mut result = *self;
|
||||
for _ in 1..b {
|
||||
result = result.checked_add(*self)?;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Returns true of values differ not more than by 1
|
||||
pub fn almost_equal(&self, b: &U256) -> Option<bool> {
|
||||
if self > b {
|
||||
Some(self.checked_sub(*b)? <= U256::one())
|
||||
} else {
|
||||
Some(b.checked_sub(*self)? <= U256::one())
|
||||
}
|
||||
}
|
||||
}
|
||||
use spl_math::uint::U256;
|
||||
|
||||
/// Perform a division that does not truncate value from either side, returning
|
||||
/// the (quotient, divisor) as a tuple
|
||||
|
@ -116,572 +78,3 @@ impl CheckedCeilDiv for U256 {
|
|||
Some((quotient, rhs))
|
||||
}
|
||||
}
|
||||
|
||||
/// The representation of the number one as a precise number as 10^12
|
||||
pub const ONE: u128 = 1_000_000_000_000;
|
||||
|
||||
/// Maximum weight for token in swap. This number is meant to stay small to
|
||||
/// so that it is possible to accurately calculate x^(MAX_WEIGHT / MIN_WEIGHT).
|
||||
pub const MAX_WEIGHT: u8 = 100;
|
||||
|
||||
/// Minimum weight for token in swap
|
||||
pub const MIN_WEIGHT: u8 = 1;
|
||||
|
||||
/// Struct encapsulating a fixed-point number that allows for decimal calculations
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PreciseNumber {
|
||||
/// Wrapper over the inner value, which is multiplied by ONE
|
||||
pub value: U256,
|
||||
}
|
||||
|
||||
/// The precise-number 1 as a U256
|
||||
fn one() -> U256 {
|
||||
U256::from(ONE)
|
||||
}
|
||||
|
||||
/// The number 0 as a PreciseNumber, used for easier calculations.
|
||||
fn zero() -> U256 {
|
||||
U256::from(0)
|
||||
}
|
||||
|
||||
impl PreciseNumber {
|
||||
/// Correction to apply to avoid truncation errors on division. Since
|
||||
/// integer operations will always floor the result, we artifically bump it
|
||||
/// up by one half to get the expect result.
|
||||
fn rounding_correction() -> U256 {
|
||||
U256::from(ONE / 2)
|
||||
}
|
||||
|
||||
/// Desired precision for the correction factor applied during each
|
||||
/// iteration of checked_pow_approximation. Once the correction factor is
|
||||
/// smaller than this number, or we reach the maxmium number of iterations,
|
||||
/// the calculation ends.
|
||||
fn precision() -> U256 {
|
||||
U256::from(100)
|
||||
}
|
||||
|
||||
fn zero() -> Self {
|
||||
Self { value: zero() }
|
||||
}
|
||||
|
||||
fn one() -> Self {
|
||||
Self { value: one() }
|
||||
}
|
||||
|
||||
/// Maximum number iterations to apply on checked_pow_approximation.
|
||||
const MAX_APPROXIMATION_ITERATIONS: u128 = 100;
|
||||
|
||||
/// Minimum base allowed when calculating exponents in checked_pow_fraction
|
||||
/// and checked_pow_approximation. This simply avoids 0 as a base.
|
||||
fn min_pow_base() -> U256 {
|
||||
U256::from(1)
|
||||
}
|
||||
|
||||
/// Maximum base allowed when calculating exponents in checked_pow_fraction
|
||||
/// and checked_pow_approximation. The calculation use a Taylor Series
|
||||
/// approxmation around 1, which converges for bases between 0 and 2. See
|
||||
/// https://en.wikipedia.org/wiki/Binomial_series#Conditions_for_convergence
|
||||
/// for more information.
|
||||
fn max_pow_base() -> U256 {
|
||||
U256::from(2 * ONE)
|
||||
}
|
||||
|
||||
/// Create a precise number from an imprecise u128, should always succeed
|
||||
pub fn new(value: u128) -> Option<Self> {
|
||||
let value = U256::from(value).checked_mul(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Convert a precise number back to u128
|
||||
pub fn to_imprecise(&self) -> Option<u128> {
|
||||
match self
|
||||
.value
|
||||
.checked_add(Self::rounding_correction())?
|
||||
.checked_div(one())
|
||||
{
|
||||
Some(v) => Some(v.as_u128()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that two PreciseNumbers are equal within some tolerance
|
||||
pub fn almost_eq(&self, rhs: &Self, precision: U256) -> bool {
|
||||
let (difference, _) = self.unsigned_sub(rhs);
|
||||
difference.value < precision
|
||||
}
|
||||
|
||||
/// Checks that a number is less than another
|
||||
pub fn less_than(&self, rhs: &Self) -> bool {
|
||||
self.value < rhs.value
|
||||
}
|
||||
|
||||
/// Checks that a number is greater than another
|
||||
pub fn greater_than(&self, rhs: &Self) -> bool {
|
||||
self.value > rhs.value
|
||||
}
|
||||
|
||||
/// Checks that a number is less than another
|
||||
pub fn less_than_or_equal(&self, rhs: &Self) -> bool {
|
||||
self.value <= rhs.value
|
||||
}
|
||||
|
||||
/// Checks that a number is greater than another
|
||||
pub fn greater_than_or_equal(&self, rhs: &Self) -> bool {
|
||||
self.value >= rhs.value
|
||||
}
|
||||
|
||||
/// Floors a precise value to a precision of ONE
|
||||
pub fn floor(&self) -> Option<Self> {
|
||||
let value = self.value.checked_div(one())?.checked_mul(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Ceiling a precise value to a precision of ONE
|
||||
pub fn ceiling(&self) -> Option<Self> {
|
||||
let value = self
|
||||
.value
|
||||
.checked_add(one().checked_sub(U256::from(1))?)?
|
||||
.checked_div(one())?
|
||||
.checked_mul(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Performs a checked division on two precise numbers
|
||||
pub fn checked_div(&self, rhs: &Self) -> Option<Self> {
|
||||
if *rhs == Self::zero() {
|
||||
return None;
|
||||
}
|
||||
match self.value.checked_mul(one()) {
|
||||
Some(v) => {
|
||||
let value = v
|
||||
.checked_add(Self::rounding_correction())?
|
||||
.checked_div(rhs.value)?;
|
||||
Some(Self { value })
|
||||
}
|
||||
None => {
|
||||
let value = self
|
||||
.value
|
||||
.checked_add(Self::rounding_correction())?
|
||||
.checked_div(rhs.value)?
|
||||
.checked_mul(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a multiplication on two precise numbers
|
||||
pub fn checked_mul(&self, rhs: &Self) -> Option<Self> {
|
||||
match self.value.checked_mul(rhs.value) {
|
||||
Some(v) => {
|
||||
let value = v
|
||||
.checked_add(Self::rounding_correction())?
|
||||
.checked_div(one())?;
|
||||
Some(Self { value })
|
||||
}
|
||||
None => {
|
||||
let value = if self.value >= rhs.value {
|
||||
self.value.checked_div(one())?.checked_mul(rhs.value)?
|
||||
} else {
|
||||
rhs.value.checked_div(one())?.checked_mul(self.value)?
|
||||
};
|
||||
Some(Self { value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs addition of two precise numbers
|
||||
pub fn checked_add(&self, rhs: &Self) -> Option<Self> {
|
||||
let value = self.value.checked_add(rhs.value)?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Subtracts the argument from self
|
||||
pub fn checked_sub(&self, rhs: &Self) -> Option<Self> {
|
||||
let value = self.value.checked_sub(rhs.value)?;
|
||||
Some(Self { value })
|
||||
}
|
||||
|
||||
/// Performs a subtraction, returning the result and whether the result is negative
|
||||
pub fn unsigned_sub(&self, rhs: &Self) -> (Self, bool) {
|
||||
match self.value.checked_sub(rhs.value) {
|
||||
None => {
|
||||
let value = rhs.value.checked_sub(self.value).unwrap();
|
||||
(Self { value }, true)
|
||||
}
|
||||
Some(value) => (Self { value }, false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs pow on a precise number
|
||||
pub fn checked_pow(&self, exponent: u128) -> Option<Self> {
|
||||
// For odd powers, start with a multiplication by base since we halve the
|
||||
// exponent at the start
|
||||
let value = if exponent.checked_rem(2)? == 0 {
|
||||
one()
|
||||
} else {
|
||||
self.value
|
||||
};
|
||||
let mut result = Self { value };
|
||||
|
||||
// To minimize the number of operations, we keep squaring the base, and
|
||||
// only push to the result on odd exponents, like a binary decomposition
|
||||
// of the exponent.
|
||||
let mut squared_base = self.clone();
|
||||
let mut current_exponent = exponent.checked_div(2)?;
|
||||
while current_exponent != 0 {
|
||||
squared_base = squared_base.checked_mul(&squared_base)?;
|
||||
|
||||
// For odd exponents, "push" the base onto the value
|
||||
if current_exponent.checked_rem(2)? != 0 {
|
||||
result = result.checked_mul(&squared_base)?;
|
||||
}
|
||||
|
||||
current_exponent = current_exponent.checked_div(2)?;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Approximate the nth root of a number using a Taylor Series around 1 on
|
||||
/// x ^ n, where 0 < n < 1, result is a precise number.
|
||||
/// Refine the guess for each term, using:
|
||||
/// 1 2
|
||||
/// f(x) = f(a) + f'(a) * (x - a) + --- * f''(a) * (x - a) + ...
|
||||
/// 2!
|
||||
/// For x ^ n, this gives:
|
||||
/// n n n-1 1 n-2 2
|
||||
/// x = a + n * a (x - a) + --- * n * (n - 1) a (x - a) + ...
|
||||
/// 2!
|
||||
///
|
||||
/// More simply, this means refining the term at each iteration with:
|
||||
///
|
||||
/// t_k+1 = t_k * (x - a) * (n + 1 - k) / k
|
||||
///
|
||||
/// where a = 1, n = power, x = precise_num
|
||||
/// NOTE: this function is private because its accurate range and precision
|
||||
/// have not been estbalished.
|
||||
fn checked_pow_approximation(&self, exponent: &Self, max_iterations: u128) -> Option<Self> {
|
||||
assert!(self.value >= Self::min_pow_base());
|
||||
assert!(self.value <= Self::max_pow_base());
|
||||
let one = Self::one();
|
||||
if *exponent == Self::zero() {
|
||||
return Some(one);
|
||||
}
|
||||
let mut precise_guess = one.clone();
|
||||
let mut term = precise_guess.clone();
|
||||
let (x_minus_a, x_minus_a_negative) = self.unsigned_sub(&precise_guess);
|
||||
let exponent_plus_one = exponent.checked_add(&one)?;
|
||||
let mut negative = false;
|
||||
for k in 1..max_iterations {
|
||||
let k = Self::new(k)?;
|
||||
let (current_exponent, current_exponent_negative) = exponent_plus_one.unsigned_sub(&k);
|
||||
term = term.checked_mul(¤t_exponent)?;
|
||||
term = term.checked_mul(&x_minus_a)?;
|
||||
term = term.checked_div(&k)?;
|
||||
if term.value < Self::precision() {
|
||||
break;
|
||||
}
|
||||
if x_minus_a_negative {
|
||||
negative = !negative;
|
||||
}
|
||||
if current_exponent_negative {
|
||||
negative = !negative;
|
||||
}
|
||||
if negative {
|
||||
precise_guess = precise_guess.checked_sub(&term)?;
|
||||
} else {
|
||||
precise_guess = precise_guess.checked_add(&term)?;
|
||||
}
|
||||
}
|
||||
Some(precise_guess)
|
||||
}
|
||||
|
||||
/// Get the power of a number, where the exponent is expressed as a fraction
|
||||
/// (numerator / denominator)
|
||||
/// NOTE: this function is private because its accurate range and precision
|
||||
/// have not been estbalished.
|
||||
#[allow(dead_code)]
|
||||
fn checked_pow_fraction(&self, exponent: &Self) -> Option<Self> {
|
||||
assert!(self.value >= Self::min_pow_base());
|
||||
assert!(self.value <= Self::max_pow_base());
|
||||
let whole_exponent = exponent.floor()?;
|
||||
let precise_whole = self.checked_pow(whole_exponent.to_imprecise()?)?;
|
||||
let (remainder_exponent, negative) = exponent.unsigned_sub(&whole_exponent);
|
||||
assert!(!negative);
|
||||
if remainder_exponent.value == U256::from(0) {
|
||||
return Some(precise_whole);
|
||||
}
|
||||
let precise_remainder = self
|
||||
.checked_pow_approximation(&remainder_exponent, Self::MAX_APPROXIMATION_ITERATIONS)?;
|
||||
precise_whole.checked_mul(&precise_remainder)
|
||||
}
|
||||
|
||||
/// Approximate the nth root of a number using Newton's method
|
||||
/// https://en.wikipedia.org/wiki/Newton%27s_method
|
||||
/// NOTE: this function is private because its accurate range and precision
|
||||
/// have not been established.
|
||||
fn newtonian_root_approximation(
|
||||
&self,
|
||||
root: &Self,
|
||||
mut guess: Self,
|
||||
iterations: u128,
|
||||
) -> Option<Self> {
|
||||
let zero = Self::zero();
|
||||
if *self == zero {
|
||||
return Some(zero);
|
||||
}
|
||||
if *root == zero {
|
||||
return None;
|
||||
}
|
||||
let one = Self::new(1)?;
|
||||
let root_minus_one = root.checked_sub(&one)?;
|
||||
let root_minus_one_whole = root_minus_one.to_imprecise()?;
|
||||
let mut last_guess = guess.clone();
|
||||
let precision = Self::precision();
|
||||
for _ in 0..iterations {
|
||||
// x_k+1 = ((n - 1) * x_k + A / (x_k ^ (n - 1))) / n
|
||||
let first_term = root_minus_one.checked_mul(&guess)?;
|
||||
let power = guess.checked_pow(root_minus_one_whole);
|
||||
let second_term = match power {
|
||||
Some(num) => self.checked_div(&num)?,
|
||||
None => Self::new(0)?,
|
||||
};
|
||||
guess = first_term.checked_add(&second_term)?.checked_div(&root)?;
|
||||
if last_guess.almost_eq(&guess, precision) {
|
||||
break;
|
||||
} else {
|
||||
last_guess = guess.clone();
|
||||
}
|
||||
}
|
||||
Some(guess)
|
||||
}
|
||||
|
||||
/// Based on testing around the limits, this base is the smallest value that
|
||||
/// provides an epsilon 11 digits
|
||||
fn minimum_sqrt_base() -> Self {
|
||||
Self {
|
||||
value: U256::from(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Based on testing around the limits, this base is the smallest value that
|
||||
/// provides an epsilon of 11 digits
|
||||
fn maximum_sqrt_base() -> Self {
|
||||
Self::new(std::u128::MAX).unwrap()
|
||||
}
|
||||
|
||||
/// Approximate the square root using Newton's method. Based on testing,
|
||||
/// this provides a precision of 11 digits for inputs between 0 and u128::MAX
|
||||
pub fn sqrt(&self) -> Option<Self> {
|
||||
if self.less_than(&Self::minimum_sqrt_base())
|
||||
|| self.greater_than(&Self::maximum_sqrt_base())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let two = PreciseNumber::new(2)?;
|
||||
let one = PreciseNumber::new(1)?;
|
||||
// A good initial guess is the average of the interval that contains the
|
||||
// input number. For all numbers, that will be between 1 and the given number.
|
||||
let guess = self.checked_add(&one)?.checked_div(&two)?;
|
||||
self.newtonian_root_approximation(&two, guess, Self::MAX_APPROXIMATION_ITERATIONS)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
||||
fn check_pow_approximation(base: U256, exponent: U256, expected: U256) {
|
||||
let precision = U256::from(5_000_000); // correct to at least 3 decimal places
|
||||
let base = PreciseNumber { value: base };
|
||||
let exponent = PreciseNumber { value: exponent };
|
||||
let root = base
|
||||
.checked_pow_approximation(&exponent, PreciseNumber::MAX_APPROXIMATION_ITERATIONS)
|
||||
.unwrap();
|
||||
let expected = PreciseNumber { value: expected };
|
||||
assert!(root.almost_eq(&expected, precision));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_root_approximation() {
|
||||
let one = one();
|
||||
// square root
|
||||
check_pow_approximation(one / 4, one / 2, one / 2); // 1/2
|
||||
check_pow_approximation(one * 11 / 10, one / 2, U256::from(1_048808848161u128)); // 1.048808848161
|
||||
|
||||
// 5th root
|
||||
check_pow_approximation(one * 4 / 5, one * 2 / 5, U256::from(914610103850u128));
|
||||
// 0.91461010385
|
||||
|
||||
// 10th root
|
||||
check_pow_approximation(one / 2, one * 4 / 50, U256::from(946057646730u128));
|
||||
// 0.94605764673
|
||||
}
|
||||
|
||||
fn check_pow_fraction(base: U256, exponent: U256, expected: U256, precision: U256) {
|
||||
let base = PreciseNumber { value: base };
|
||||
let exponent = PreciseNumber { value: exponent };
|
||||
let power = base.checked_pow_fraction(&exponent).unwrap();
|
||||
let expected = PreciseNumber { value: expected };
|
||||
assert!(power.almost_eq(&expected, precision));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pow_fraction() {
|
||||
let one = one();
|
||||
let precision = U256::from(50_000_000); // correct to at least 3 decimal places
|
||||
let less_precision = precision * 1_000; // correct to at least 1 decimal place
|
||||
check_pow_fraction(one, one, one, precision);
|
||||
check_pow_fraction(
|
||||
one * 20 / 13,
|
||||
one * 50 / 3,
|
||||
U256::from(1312_534484739100u128),
|
||||
precision,
|
||||
); // 1312.5344847391
|
||||
check_pow_fraction(one * 2 / 7, one * 49 / 4, U256::from(2163), precision);
|
||||
check_pow_fraction(
|
||||
one * 5000 / 5100,
|
||||
one / 9,
|
||||
U256::from(997802126900u128),
|
||||
precision,
|
||||
); // 0.99780212695
|
||||
// results get less accurate as the base gets further from 1, so allow
|
||||
// for a greater margin of error
|
||||
check_pow_fraction(
|
||||
one * 2,
|
||||
one * 27 / 5,
|
||||
U256::from(42_224253144700u128),
|
||||
less_precision,
|
||||
); // 42.2242531447
|
||||
check_pow_fraction(
|
||||
one * 18 / 10,
|
||||
one * 11 / 3,
|
||||
U256::from(8_629769290500u128),
|
||||
less_precision,
|
||||
); // 8.629769290
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newtonian_approximation() {
|
||||
// square root
|
||||
let test = PreciseNumber::new(9).unwrap();
|
||||
let nth_root = PreciseNumber::new(2).unwrap();
|
||||
let guess = test.checked_div(&nth_root).unwrap();
|
||||
let root = test
|
||||
.newtonian_root_approximation(
|
||||
&nth_root,
|
||||
guess,
|
||||
PreciseNumber::MAX_APPROXIMATION_ITERATIONS,
|
||||
)
|
||||
.unwrap()
|
||||
.to_imprecise()
|
||||
.unwrap();
|
||||
assert_eq!(root, 3); // actually 3
|
||||
|
||||
let test = PreciseNumber::new(101).unwrap();
|
||||
let nth_root = PreciseNumber::new(2).unwrap();
|
||||
let guess = test.checked_div(&nth_root).unwrap();
|
||||
let root = test
|
||||
.newtonian_root_approximation(
|
||||
&nth_root,
|
||||
guess,
|
||||
PreciseNumber::MAX_APPROXIMATION_ITERATIONS,
|
||||
)
|
||||
.unwrap()
|
||||
.to_imprecise()
|
||||
.unwrap();
|
||||
assert_eq!(root, 10); // actually 10.049875
|
||||
|
||||
let test = PreciseNumber::new(1_000_000_000).unwrap();
|
||||
let nth_root = PreciseNumber::new(2).unwrap();
|
||||
let guess = test.checked_div(&nth_root).unwrap();
|
||||
let root = test
|
||||
.newtonian_root_approximation(
|
||||
&nth_root,
|
||||
guess,
|
||||
PreciseNumber::MAX_APPROXIMATION_ITERATIONS,
|
||||
)
|
||||
.unwrap()
|
||||
.to_imprecise()
|
||||
.unwrap();
|
||||
assert_eq!(root, 31_623); // actually 31622.7766
|
||||
|
||||
// 5th root
|
||||
let test = PreciseNumber::new(500).unwrap();
|
||||
let nth_root = PreciseNumber::new(5).unwrap();
|
||||
let guess = test.checked_div(&nth_root).unwrap();
|
||||
let root = test
|
||||
.newtonian_root_approximation(
|
||||
&nth_root,
|
||||
guess,
|
||||
PreciseNumber::MAX_APPROXIMATION_ITERATIONS,
|
||||
)
|
||||
.unwrap()
|
||||
.to_imprecise()
|
||||
.unwrap();
|
||||
assert_eq!(root, 3); // actually 3.46572422
|
||||
}
|
||||
|
||||
fn check_square_root(check: &PreciseNumber) {
|
||||
let epsilon = PreciseNumber {
|
||||
value: U256::from(10),
|
||||
}; // correct within 11 digits
|
||||
let one = PreciseNumber::one();
|
||||
let one_plus_epsilon = one.checked_add(&epsilon).unwrap();
|
||||
let one_minus_epsilon = one.checked_sub(&epsilon).unwrap();
|
||||
let approximate_root = check.sqrt().unwrap();
|
||||
let lower_bound = approximate_root
|
||||
.checked_mul(&one_minus_epsilon)
|
||||
.unwrap()
|
||||
.checked_pow(2)
|
||||
.unwrap();
|
||||
let upper_bound = approximate_root
|
||||
.checked_mul(&one_plus_epsilon)
|
||||
.unwrap()
|
||||
.checked_pow(2)
|
||||
.unwrap();
|
||||
assert!(check.less_than_or_equal(&upper_bound));
|
||||
assert!(check.greater_than_or_equal(&lower_bound));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_square_root_min_max() {
|
||||
let test_roots = [
|
||||
PreciseNumber::minimum_sqrt_base(),
|
||||
PreciseNumber::maximum_sqrt_base(),
|
||||
];
|
||||
for i in test_roots.iter() {
|
||||
check_square_root(i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_floor() {
|
||||
let whole_number = PreciseNumber::new(2).unwrap();
|
||||
let mut decimal_number = PreciseNumber::new(2).unwrap();
|
||||
decimal_number.value += U256::from(1);
|
||||
let floor = decimal_number.floor().unwrap();
|
||||
let floor_again = floor.floor().unwrap();
|
||||
assert_eq!(whole_number.value, floor.value);
|
||||
assert_eq!(whole_number.value, floor_again.value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ceiling() {
|
||||
let whole_number = PreciseNumber::new(2).unwrap();
|
||||
let mut decimal_number = PreciseNumber::new(2).unwrap();
|
||||
decimal_number.value -= U256::from(1);
|
||||
let ceiling = decimal_number.ceiling().unwrap();
|
||||
let ceiling_again = ceiling.ceiling().unwrap();
|
||||
assert_eq!(whole_number.value, ceiling.value);
|
||||
assert_eq!(whole_number.value, ceiling_again.value);
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_square_root(a in 0..u128::MAX) {
|
||||
let a = PreciseNumber { value: U256::from(a) };
|
||||
check_square_root(&a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
//! The Uniswap invariant calculator with an extra offset
|
||||
|
||||
use crate::{
|
||||
curve::{
|
||||
calculator::{
|
||||
CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult, TradeDirection,
|
||||
TradingTokenResult,
|
||||
use {
|
||||
crate::{
|
||||
curve::{
|
||||
calculator::{
|
||||
CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult, TradeDirection,
|
||||
TradingTokenResult,
|
||||
},
|
||||
constant_product::{
|
||||
normalized_value, pool_tokens_to_trading_tokens, swap,
|
||||
trading_tokens_to_pool_tokens,
|
||||
},
|
||||
},
|
||||
constant_product::{
|
||||
normalized_value, pool_tokens_to_trading_tokens, swap, trading_tokens_to_pool_tokens,
|
||||
},
|
||||
math::PreciseNumber,
|
||||
error::SwapError,
|
||||
},
|
||||
error::SwapError,
|
||||
};
|
||||
use arrayref::{array_mut_ref, array_ref};
|
||||
use solana_program::{
|
||||
program_error::ProgramError,
|
||||
program_pack::{IsInitialized, Pack, Sealed},
|
||||
arrayref::{array_mut_ref, array_ref},
|
||||
solana_program::{
|
||||
program_error::ProgramError,
|
||||
program_pack::{IsInitialized, Pack, Sealed},
|
||||
},
|
||||
spl_math::precise_number::PreciseNumber,
|
||||
};
|
||||
|
||||
/// Offset curve, uses ConstantProduct under the hood, but adds an offset to
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! The curve.fi invariant calculator.
|
||||
|
||||
use crate::{curve::math::U256, error::SwapError};
|
||||
use crate::error::SwapError;
|
||||
use solana_program::{
|
||||
program_error::ProgramError,
|
||||
program_pack::{IsInitialized, Pack, Sealed},
|
||||
|
@ -14,14 +14,41 @@ use crate::curve::{
|
|||
constant_product::{
|
||||
normalized_value, pool_tokens_to_trading_tokens, trading_tokens_to_pool_tokens,
|
||||
},
|
||||
math::PreciseNumber,
|
||||
};
|
||||
use arrayref::{array_mut_ref, array_ref};
|
||||
use spl_math::{precise_number::PreciseNumber, uint::U256};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
const N_COINS: u8 = 2;
|
||||
const N_COINS_SQUARED: u8 = 4;
|
||||
|
||||
/// Returns self to the power of b
|
||||
fn checked_u8_power(a: &U256, b: u8) -> Option<U256> {
|
||||
let mut result = *a;
|
||||
for _ in 1..b {
|
||||
result = result.checked_mul(*a)?;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Returns self multiplied by b
|
||||
fn checked_u8_mul(a: &U256, b: u8) -> Option<U256> {
|
||||
let mut result = *a;
|
||||
for _ in 1..b {
|
||||
result = result.checked_add(*a)?;
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Returns true of values differ not more than by 1
|
||||
fn almost_equal(a: &U256, b: &U256) -> Option<bool> {
|
||||
if a > b {
|
||||
Some(a.checked_sub(*b)? <= U256::one())
|
||||
} else {
|
||||
Some(b.checked_sub(*a)? <= U256::one())
|
||||
}
|
||||
}
|
||||
|
||||
/// StableCurve struct implementing CurveCalculator
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct StableCurve {
|
||||
|
@ -32,12 +59,12 @@ pub struct StableCurve {
|
|||
/// d = (leverage * sum_x + d_product * n_coins) * initial_d / ((leverage - 1) * initial_d + (n_coins + 1) * d_product)
|
||||
fn calculate_step(initial_d: &U256, leverage: u64, sum_x: u128, d_product: &U256) -> Option<U256> {
|
||||
let leverage_mul = U256::from(leverage).checked_mul(sum_x.into())?;
|
||||
let d_p_mul = d_product.checked_u8_mul(N_COINS)?;
|
||||
let d_p_mul = checked_u8_mul(&d_product, N_COINS)?;
|
||||
|
||||
let l_val = leverage_mul.checked_add(d_p_mul)?.checked_mul(*initial_d)?;
|
||||
|
||||
let leverage_sub = initial_d.checked_mul((leverage.checked_sub(1)?).into())?;
|
||||
let n_coins_sum = d_product.checked_u8_mul(N_COINS.checked_add(1)?)?;
|
||||
let n_coins_sum = checked_u8_mul(&d_product, N_COINS.checked_add(1)?)?;
|
||||
|
||||
let r_val = leverage_sub.checked_add(n_coins_sum)?;
|
||||
|
||||
|
@ -48,8 +75,8 @@ fn calculate_step(initial_d: &U256, leverage: u64, sum_x: u128, d_product: &U256
|
|||
/// Equation:
|
||||
/// A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
|
||||
fn compute_d(leverage: u64, amount_a: u128, amount_b: u128) -> Option<u128> {
|
||||
let amount_a_times_coins = U256::from(amount_a).checked_u8_mul(N_COINS)?;
|
||||
let amount_b_times_coins = U256::from(amount_b).checked_u8_mul(N_COINS)?;
|
||||
let amount_a_times_coins = checked_u8_mul(&U256::from(amount_a), N_COINS)?;
|
||||
let amount_b_times_coins = checked_u8_mul(&U256::from(amount_b), N_COINS)?;
|
||||
let sum_x = amount_a.checked_add(amount_b)?; // sum(x_i), a.k.a S
|
||||
if sum_x == 0 {
|
||||
Some(0)
|
||||
|
@ -70,7 +97,7 @@ fn compute_d(leverage: u64, amount_a: u128, amount_b: u128) -> Option<u128> {
|
|||
//d = (leverage * sum_x + d_p * n_coins) * d / ((leverage - 1) * d + (n_coins + 1) * d_p);
|
||||
d = calculate_step(&d, leverage, sum_x, &d_product)?;
|
||||
// Equality with the precision of 1
|
||||
if d.almost_equal(&d_previous)? {
|
||||
if almost_equal(&d, &d_previous)? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -94,13 +121,8 @@ fn compute_new_destination_amount(
|
|||
|
||||
// sum' = prod' = x
|
||||
// c = D ** (n + 1) / (n ** (2 * n) * prod' * A)
|
||||
let c = d_val
|
||||
.checked_u8_power(N_COINS.checked_add(1)?)?
|
||||
.checked_div(
|
||||
new_source_amount
|
||||
.checked_u8_mul(N_COINS_SQUARED)?
|
||||
.checked_mul(leverage)?,
|
||||
)?;
|
||||
let c = checked_u8_power(&d_val, N_COINS.checked_add(1)?)?
|
||||
.checked_div(checked_u8_mul(&new_source_amount, N_COINS_SQUARED)?.checked_mul(leverage)?)?;
|
||||
|
||||
// b = sum' - (A*n**n - 1) * D / (A * n**n)
|
||||
let b = new_source_amount.checked_add(d_val.checked_div(leverage)?)?;
|
||||
|
@ -110,9 +132,9 @@ fn compute_new_destination_amount(
|
|||
let mut y = d_val;
|
||||
for _ in 0..32 {
|
||||
y_prev = y;
|
||||
y = (y.checked_u8_power(2)?.checked_add(c)?)
|
||||
.checked_div(y.checked_u8_mul(2)?.checked_add(b)?.checked_sub(d_val)?)?;
|
||||
if y.almost_equal(&y_prev)? {
|
||||
y = (checked_u8_power(&y, 2)?.checked_add(c)?)
|
||||
.checked_div(checked_u8_mul(&y, 2)?.checked_add(b)?.checked_sub(d_val)?)?;
|
||||
if almost_equal(&y, &y_prev)? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue