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:
Jon Cinque 2021-02-10 17:40:54 +01:00 committed by GitHub
parent b823ca88a4
commit beb4aa7e7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1038 additions and 658 deletions

20
Cargo.lock generated
View File

@ -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",
]

View File

@ -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",

33
libraries/math/Cargo.toml Normal file
View File

@ -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"]

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -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);
}
}
}

View File

@ -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)
}

View File

@ -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"
}
}

View File

@ -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(),
}
}

14
libraries/math/src/lib.rs Normal file
View File

@ -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");

View File

@ -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(&current_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);
}
}
}

View File

@ -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(())
}
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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]

View File

@ -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"] }

View File

@ -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;

View File

@ -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.

View File

@ -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)]

View File

@ -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

View File

@ -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(&current_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);
}
}
}

View File

@ -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

View File

@ -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;
}
}