Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit beb4aa7

Browse files
authored
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
1 parent b823ca8 commit beb4aa7

22 files changed

+1038
-658
lines changed

Cargo.lock

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ members = [
88
"examples/rust/transfer-lamports",
99
"feature-proposal/program",
1010
"feature-proposal/cli",
11+
"libraries/math",
1112
"memo/program",
1213
"shared-memory/program",
1314
"stake-pool/cli",

libraries/math/Cargo.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[package]
2+
name = "spl-math"
3+
version = "0.1.0"
4+
description = "Solana Program Library Math"
5+
authors = ["Solana Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2018"
9+
10+
[features]
11+
no-entrypoint = []
12+
test-bpf = []
13+
14+
[dependencies]
15+
borsh = "0.7.1"
16+
borsh-derive = "0.7.1"
17+
num-derive = "0.3"
18+
num-traits = "0.2"
19+
solana-program = "1.5.4"
20+
thiserror = "1.0"
21+
uint = "0.8"
22+
23+
[dev-dependencies]
24+
proptest = "0.10"
25+
solana-program-test = "1.5.4"
26+
solana-sdk = "1.5.4"
27+
tokio = { version = "0.3", features = ["macros"]}
28+
29+
[lib]
30+
crate-type = ["cdylib", "lib"]
31+
32+
[package.metadata.docs.rs]
33+
targets = ["x86_64-unknown-linux-gnu"]

libraries/math/Xargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []

libraries/math/src/approximations.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//! Approximation calculations
2+
3+
use {
4+
num_traits::{CheckedAdd, CheckedDiv, One, Zero},
5+
std::cmp::Eq,
6+
};
7+
8+
const SQRT_ITERATIONS: u8 = 50;
9+
10+
/// Perform square root
11+
pub fn sqrt<T: CheckedAdd + CheckedDiv + One + Zero + Eq + Copy>(radicand: T) -> Option<T> {
12+
if radicand == T::zero() {
13+
return Some(T::zero());
14+
}
15+
// A good initial guess is the average of the interval that contains the
16+
// input number. For all numbers, that will be between 1 and the given number.
17+
let one = T::one();
18+
let two = one.checked_add(&one)?;
19+
let mut guess = radicand.checked_div(&two)?.checked_add(&one)?;
20+
let mut last_guess = guess;
21+
for _ in 0..SQRT_ITERATIONS {
22+
// x_k+1 = (x_k + radicand / x_k) / 2
23+
guess = last_guess
24+
.checked_add(&radicand.checked_div(&last_guess)?)?
25+
.checked_div(&two)?;
26+
if last_guess == guess {
27+
break;
28+
} else {
29+
last_guess = guess;
30+
}
31+
}
32+
Some(guess)
33+
}
34+
35+
#[cfg(test)]
36+
mod tests {
37+
use {super::*, proptest::prelude::*};
38+
39+
fn check_square_root(radicand: u128) {
40+
let root = sqrt(radicand).unwrap();
41+
let lower_bound = root.saturating_sub(1).checked_pow(2).unwrap();
42+
let upper_bound = root.checked_add(1).unwrap().checked_pow(2).unwrap();
43+
assert!(radicand as u128 <= upper_bound);
44+
assert!(radicand as u128 >= lower_bound);
45+
}
46+
47+
#[test]
48+
fn test_square_root_min_max() {
49+
let test_roots = [0, u64::MAX];
50+
for i in test_roots.iter() {
51+
check_square_root(*i as u128);
52+
}
53+
}
54+
55+
proptest! {
56+
#[test]
57+
fn test_square_root(a in 0..u64::MAX) {
58+
check_square_root(a as u128);
59+
}
60+
}
61+
}

libraries/math/src/entrypoint.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! Program entrypoint
2+
3+
#![cfg(not(feature = "no-entrypoint"))]
4+
5+
use solana_program::{
6+
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
7+
};
8+
9+
entrypoint!(process_instruction);
10+
fn process_instruction(
11+
program_id: &Pubkey,
12+
accounts: &[AccountInfo],
13+
instruction_data: &[u8],
14+
) -> ProgramResult {
15+
crate::processor::process_instruction(program_id, accounts, instruction_data)
16+
}

libraries/math/src/error.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//! Error types
2+
3+
use {
4+
num_derive::FromPrimitive,
5+
solana_program::{decode_error::DecodeError, program_error::ProgramError},
6+
thiserror::Error,
7+
};
8+
9+
/// Errors that may be returned by the Math program.
10+
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
11+
pub enum MathError {
12+
/// Calculation overflowed the destination number
13+
#[error("Calculation overflowed the destination number")]
14+
Overflow,
15+
/// Calculation underflowed the destination number
16+
#[error("Calculation underflowed the destination number")]
17+
Underflow,
18+
}
19+
impl From<MathError> for ProgramError {
20+
fn from(e: MathError) -> Self {
21+
ProgramError::Custom(e as u32)
22+
}
23+
}
24+
impl<T> DecodeError<T> for MathError {
25+
fn type_of() -> &'static str {
26+
"Math Error"
27+
}
28+
}

libraries/math/src/instruction.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Program instructions, used for end-to-end testing and instruction counts
2+
3+
use {
4+
crate::id,
5+
borsh::{BorshDeserialize, BorshSerialize},
6+
solana_program::instruction::Instruction,
7+
};
8+
9+
/// Instructions supported by the math program, used for testing instruction
10+
/// counts
11+
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
12+
pub enum MathInstruction {
13+
/// Calculate the square root of the given u64 with decimals
14+
///
15+
/// No accounts required for this instruction
16+
PreciseSquareRoot {
17+
/// Number underneath the square root sign, whose square root will be
18+
/// calculated
19+
radicand: u64,
20+
},
21+
/// Calculate the integer square root of the given u64
22+
///
23+
/// No accounts required for this instruction
24+
SquareRootU64 {
25+
/// Number underneath the square root sign, whose square root will be
26+
/// calculated
27+
radicand: u64,
28+
},
29+
/// Calculate the integer square root of the given u128
30+
///
31+
/// No accounts required for this instruction
32+
SquareRootU128 {
33+
/// Number underneath the square root sign, whose square root will be
34+
/// calculated
35+
radicand: u128,
36+
},
37+
}
38+
39+
/// Create PreciseSquareRoot instruction
40+
pub fn precise_sqrt(radicand: u64) -> Instruction {
41+
Instruction {
42+
program_id: id(),
43+
accounts: vec![],
44+
data: MathInstruction::PreciseSquareRoot { radicand }
45+
.try_to_vec()
46+
.unwrap(),
47+
}
48+
}
49+
50+
/// Create SquareRoot instruction
51+
pub fn sqrt_u64(radicand: u64) -> Instruction {
52+
Instruction {
53+
program_id: id(),
54+
accounts: vec![],
55+
data: MathInstruction::SquareRootU64 { radicand }
56+
.try_to_vec()
57+
.unwrap(),
58+
}
59+
}
60+
61+
/// Create SquareRoot instruction
62+
pub fn sqrt_u128(radicand: u128) -> Instruction {
63+
Instruction {
64+
program_id: id(),
65+
accounts: vec![],
66+
data: MathInstruction::SquareRootU128 { radicand }
67+
.try_to_vec()
68+
.unwrap(),
69+
}
70+
}

libraries/math/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! Math operations using unsigned integers
2+
3+
#![deny(missing_docs)]
4+
#![forbid(unsafe_code)]
5+
6+
pub mod approximations;
7+
mod entrypoint;
8+
pub mod error;
9+
pub mod instruction;
10+
pub mod precise_number;
11+
pub mod processor;
12+
pub mod uint;
13+
14+
solana_program::declare_id!("Math111111111111111111111111111111111111111");

0 commit comments

Comments
 (0)