Repository: solana-labs/solana-program-library
Pull request #6713 (2024-05-09 19:20:20)

math: more type tests
Running the following command has been helpful determining which types to use for various math operations:
```sh
cargo test-sbf -- --nocapture --test-threads=1
```

In considering how to perform math in our programs (whether to create methods to replicate performing floating-point arithmetic or to use floats directly), we needed to benchmark some arithmetic with u128 vs f64.

This PR adds multiplication and division for each of these types.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 1a46eaf5f..fd30dff23 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2960,9 +2960,9 @@ dependencies = [
 
 [[package]]
 name = "libtest-mimic"
-version = "0.7.2"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fefdf21230d6143476a28adbee3d930e2b68a3d56443c777cae3fe9340eebff9"
+checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58"
 dependencies = [
  "clap 4.4.8",
  "escape8259",
diff --git a/libraries/math/src/instruction.rs b/libraries/math/src/instruction.rs
index 5fd4dc6c4..c9e3b40c4 100644
--- a/libraries/math/src/instruction.rs
+++ b/libraries/math/src/instruction.rs
@@ -107,6 +107,43 @@ pub enum MathInstruction {
         exponent: f64,
     },
 
+    /// Multiply two u128 values
+    ///
+    /// No accounts required for this instruction
+    U128Multiply {
+        /// The multiplicand
+        multiplicand: u128,
+        /// The multipier
+        multiplier: u128,
+    },
+    /// Divide two u128 values
+    ///
+    /// No accounts required for this instruction
+    U128Divide {
+        /// The dividend
+        dividend: u128,
+        /// The divisor
+        divisor: u128,
+    },
+    /// Multiply two f64 values
+    ///
+    /// No accounts required for this instruction
+    F64Multiply {
+        /// The multiplicand
+        multiplicand: f64,
+        /// The multipier
+        multiplier: f64,
+    },
+    /// Divide two f64 values
+    ///
+    /// No accounts required for this instruction
+    F64Divide {
+        /// The dividend
+        dividend: f64,
+        /// The divisor
+        divisor: f64,
+    },
+
     /// Don't do anything for comparison
     ///
     /// No accounts required for this instruction
@@ -220,6 +257,50 @@ pub fn f64_pow(base: f64, exponent: f64) -> Instruction {
     }
 }
 
+/// Create U128 Multiplication instruction
+pub fn u128_multiply(multiplicand: u128, multiplier: u128) -> Instruction {
+    Instruction {
+        program_id: id(),
+        accounts: vec![],
+        data: borsh::to_vec(&MathInstruction::U128Multiply {
+            multiplicand,
+            multiplier,
+        })
+        .unwrap(),
+    }
+}
+
+/// Create U128 Division instruction
+pub fn u128_divide(dividend: u128, divisor: u128) -> Instruction {
+    Instruction {
+        program_id: id(),
+        accounts: vec![],
+        data: borsh::to_vec(&MathInstruction::U128Divide { dividend, divisor }).unwrap(),
+    }
+}
+
+/// Create F64 Multiplication instruction
+pub fn f64_multiply(multiplicand: f64, multiplier: f64) -> Instruction {
+    Instruction {
+        program_id: id(),
+        accounts: vec![],
+        data: borsh::to_vec(&MathInstruction::F64Multiply {
+            multiplicand,
+            multiplier,
+        })
+        .unwrap(),
+    }
+}
+
+/// Create F64 Division instruction
+pub fn f64_divide(dividend: f64, divisor: f64) -> Instruction {
+    Instruction {
+        program_id: id(),
+        accounts: vec![],
+        data: borsh::to_vec(&MathInstruction::F64Divide { dividend, divisor }).unwrap(),
+    }
+}
+
 /// Create Noop instruction
 pub fn noop() -> Instruction {
     Instruction {
diff --git a/libraries/math/src/processor.rs b/libraries/math/src/processor.rs
index fff69dd97..dfc2bb176 100644
--- a/libraries/math/src/processor.rs
+++ b/libraries/math/src/processor.rs
@@ -50,6 +50,30 @@ fn f32_natural_log(argument: f32) -> f32 {
     argument.ln()
 }
 
+/// u128_multiply
+#[inline(never)]
+fn u128_multiply(multiplicand: u128, multiplier: u128) -> u128 {
+    multiplicand * multiplier
+}
+
+/// u128_divide
+#[inline(never)]
+fn u128_divide(dividend: u128, divisor: u128) -> u128 {
+    dividend / divisor
+}
+
+/// f64_multiply
+#[inline(never)]
+fn f64_multiply(multiplicand: f64, multiplier: f64) -> f64 {
+    multiplicand * multiplier
+}
+
+/// f64_divide
+#[inline(never)]
+fn f64_divide(dividend: f64, divisor: f64) -> f64 {
+    dividend / divisor
+}
+
 /// Instruction processor
 pub fn process_instruction(
     _program_id: &Pubkey,
@@ -157,6 +181,44 @@ pub fn process_instruction(
             msg!("{}", result as u64);
             Ok(())
         }
+        MathInstruction::U128Multiply {
+            multiplicand,
+            multiplier,
+        } => {
+            msg!("Calculating u128 Multiply");
+            sol_log_compute_units();
+            let result = u128_multiply(multiplicand, multiplier);
+            sol_log_compute_units();
+            msg!("{}", result);
+            Ok(())
+        }
+        MathInstruction::U128Divide { dividend, divisor } => {
+            msg!("Calculating u128 Divide");
+            sol_log_compute_units();
+            let result = u128_divide(dividend, divisor);
+            sol_log_compute_units();
+            msg!("{}", result);
+            Ok(())
+        }
+        MathInstruction::F64Multiply {
+            multiplicand,
+            multiplier,
+        } => {
+            msg!("Calculating f64 Multiply");
+            sol_log_compute_units();
+            let result = f64_multiply(multiplicand, multiplier);
+            sol_log_compute_units();
+            msg!("{}", result as u64);
+            Ok(())
+        }
+        MathInstruction::F64Divide { dividend, divisor } => {
+            msg!("Calculating f64 Divide");
+            sol_log_compute_units();
+            let result = f64_divide(dividend, divisor);
+            sol_log_compute_units();
+            msg!("{}", result as u64);
+            Ok(())
+        }
         MathInstruction::Noop => {
             msg!("Do nothing");
             msg!("{}", 0_u64);
diff --git a/libraries/type-length-value/js/package.json b/libraries/type-length-value/js/package.json
index 38216706c..0bbc8cc27 100644
--- a/libraries/type-length-value/js/package.json
+++ b/libraries/type-length-value/js/package.json
@@ -52,7 +52,7 @@
         "@types/node": "^20.12.11",
         "@typescript-eslint/eslint-plugin": "^7.8.0",
         "@typescript-eslint/parser": "^7.8.0",
-        "chai": "^5.1.0",
+        "chai": "^5.1.1",
         "eslint": "^8.57.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-prettier": "^5.1.3",
diff --git a/memo/js/package.json b/memo/js/package.json
index 96cc7ab6c..a64c9f6fa 100644
--- a/memo/js/package.json
+++ b/memo/js/package.json
@@ -59,7 +59,7 @@
         "@types/node-fetch": "^2.6.11",
         "@typescript-eslint/eslint-plugin": "^7.8.0",
         "@typescript-eslint/parser": "^7.8.0",
-        "chai": "^5.1.0",
+        "chai": "^5.1.1",
         "eslint": "^8.57.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-prettier": "^5.1.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 31f4d038d..8bcca0dc8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -148,8 +148,8 @@ importers:
         specifier: ^7.8.0
         version: 7.8.0(eslint@8.57.0)(typescript@5.4.5)
       chai:
-        specifier: ^5.1.0
-        version: 5.1.0
+        specifier: ^5.1.1
+        version: 5.1.1
       eslint:
         specifier: ^8.57.0
         version: 8.57.0
@@ -212,8 +212,8 @@ importers:
         specifier: ^7.8.0
         version: 7.8.0(eslint@8.57.0)(typescript@5.4.5)
       chai:
-        specifier: ^5.1.0
-        version: 5.1.0
+        specifier: ^5.1.1
+        version: 5.1.1
       eslint:
         specifier: ^8.57.0
         version: 8.57.0
@@ -358,8 +358,8 @@ importers:
         specifier: ^0.2.0
         version: 0.2.0
       tsx:
-        specifier: ^4.9.3
-        version: 4.9.3
+        specifier: ^4.10.2
+        version: 4.10.2
       typescript:
         specifier: ^5.4.5
         version: 5.4.5
@@ -471,8 +471,8 @@ importers:
         specifier: ^3.2.5
         version: 3.2.5
       rimraf:
-        specifier: ^5.0.0
-        version: 5.0.5
+        specifier: ^5.0.7
+        version: 5.0.7
       rollup:
         specifier: ^4.17.2
         version: 4.17.2
@@ -514,8 +514,8 @@ importers:
         specifier: ^7.8.0
         version: 7.8.0(eslint@8.57.0)(typescript@5.4.5)
       chai:
-        specifier: ^5.1.0
-        version: 5.1.0
+        specifier: ^5.1.1
+        version: 5.1.1
       eslint:
         specifier: ^8.57.0
         version: 8.57.0
@@ -657,8 +657,8 @@ importers:
         specifier: ^7.8.0
         version: 7.8.0(eslint@8.57.0)(typescript@5.4.5)
       chai:
-        specifier: ^5.1.0
-        version: 5.1.0
+        specifier: ^5.1.1
+        version: 5.1.1
       eslint:
         specifier: ^8.57.0
         version: 8.57.0
@@ -809,11 +809,11 @@ importers:
         specifier: ^7.8.0
         version: 7.8.0(eslint@8.57.0)(typescript@5.4.5)
       chai:
-        specifier: ^5.1.0
-        version: 5.1.0
+        specifier: ^5.1.1
+        version: 5.1.1
       chai-as-promised:
-        specifier: ^7.1.1
-        version: 7.1.1(chai@5.1.0)
+        specifier: ^7.1.2
+        version: 7.1.2(chai@5.1.1)
       eslint:
         specifier: ^8.57.0
         version: 8.57.0
@@ -3966,21 +3966,21 @@ packages:
       nofilter: 3.1.0
     dev: true
 
-  /chai-as-promised@7.1.1(chai@5.1.0):
-    resolution: {integrity: sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==}
+  /chai-as-promised@7.1.2(chai@5.1.1):
+    resolution: {integrity: sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==}
     peerDependencies:
-      chai: '>= 2.1.2 < 5'
+      chai: '>= 2.1.2 < 6'
     dependencies:
-      chai: 5.1.0
+      chai: 5.1.1
       check-error: 1.0.3
     dev: true
 
-  /chai@5.1.0:
-    resolution: {integrity: sha512-kDZ7MZyM6Q1DhR9jy7dalKohXQ2yrlXkk59CR52aRKxJrobmlBNqnFQxX9xOX8w+4mz8SYlKJa/7D7ddltFXCw==}
+  /chai@5.1.1:
+    resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
     engines: {node: '>=12'}
     dependencies:
       assertion-error: 2.0.1
-      check-error: 2.0.0
+      check-error: 2.1.1
       deep-eql: 5.0.1
       loupe: 3.1.0
       pathval: 2.0.0
@@ -4018,8 +4018,8 @@ packages:
       get-func-name: 2.0.2
     dev: true
 
-  /check-error@2.0.0:
-    resolution: {integrity: sha512-tjLAOBHKVxtPoHe/SA7kNOMvhCRdCJ3vETdeY0RuAc9popf+hyaSV6ZEg9hr4cpWF7jmo/JSWEnLDrnijS9Tog==}
+  /check-error@2.1.1:
+    resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
     engines: {node: '>= 16'}
     dev: true
 
@@ -7440,9 +7440,9 @@ packages:
       glob: 7.2.3
     dev: true
 
-  /rimraf@5.0.5:
-    resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==}
-    engines: {node: '>=14'}
+  /rimraf@5.0.7:
+    resolution: {integrity: sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==}
+    engines: {node: '>=14.18'}
     hasBin: true
     dependencies:
       glob: 10.3.10
@@ -8195,8 +8195,8 @@ packages:
       typescript: 5.4.5
     dev: true
 
-  /tsx@4.9.3:
-    resolution: {integrity: sha512-czVbetlILiyJZI5zGlj2kw9vFiSeyra9liPD4nG+Thh4pKTi0AmMEQ8zdV/L2xbIVKrIqif4sUNrsMAOksx9Zg==}
+  /tsx@4.10.2:
+    resolution: {integrity: sha512-gOfACgv1ElsIjvt7Fp0rMJKGnMGjox0JfGOfX3kmZCV/yZumaNqtHGKBXt1KgaYS9KjDOmqGeI8gHk/W7kWVZg==}
     engines: {node: '>=18.0.0'}
     hasBin: true
     dependencies:
diff --git a/single-pool/js/packages/classic/package.json b/single-pool/js/packages/classic/package.json
index 80af16480..8ddb17fd3 100644
--- a/single-pool/js/packages/classic/package.json
+++ b/single-pool/js/packages/classic/package.json
@@ -26,7 +26,7 @@
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-prettier": "^5.1.3",
     "solana-bankrun": "^0.2.0",
-    "tsx": "^4.9.3",
+    "tsx": "^4.10.2",
     "typescript": "^5.4.5"
   },
   "dependencies": {
diff --git a/stake-pool/js/package.json b/stake-pool/js/package.json
index a16b9bd0c..d4cb7f032 100644
--- a/stake-pool/js/package.json
+++ b/stake-pool/js/package.json
@@ -71,7 +71,7 @@
     "eslint-plugin-prettier": "^5.1.3",
     "jest": "^29.0.0",
     "prettier": "^3.2.5",
-    "rimraf": "^5.0.0",
+    "rimraf": "^5.0.7",
     "rollup": "^4.17.2",
     "rollup-plugin-dts": "^6.1.0",
     "ts-jest": "^29.1.2",
diff --git a/token-group/js/package.json b/token-group/js/package.json
index d70afb7e0..6fa16eed9 100644
--- a/token-group/js/package.json
+++ b/token-group/js/package.json
@@ -57,7 +57,7 @@
         "@types/node": "^20.12.11",
         "@typescript-eslint/eslint-plugin": "^7.8.0",
         "@typescript-eslint/parser": "^7.8.0",
-        "chai": "^5.1.0",
+        "chai": "^5.1.1",
         "eslint": "^8.57.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-prettier": "^5.1.3",
diff --git a/token-metadata/js/package.json b/token-metadata/js/package.json
index b261a3894..e454938de 100644
--- a/token-metadata/js/package.json
+++ b/token-metadata/js/package.json
@@ -57,7 +57,7 @@
         "@types/node": "^20.12.11",
         "@typescript-eslint/eslint-plugin": "^7.8.0",
         "@typescript-eslint/parser": "^7.8.0",
-        "chai": "^5.1.0",
+        "chai": "^5.1.1",
         "eslint": "^8.57.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-prettier": "^5.1.3",
diff --git a/token/js/package.json b/token/js/package.json
index f6deb2029..75ef16b71 100644
--- a/token/js/package.json
+++ b/token/js/package.json
@@ -71,8 +71,8 @@
         "@types/node-fetch": "^2.6.11",
         "@typescript-eslint/eslint-plugin": "^7.8.0",
         "@typescript-eslint/parser": "^7.8.0",
-        "chai": "^5.1.0",
-        "chai-as-promised": "^7.1.1",
+        "chai": "^5.1.1",
+        "chai-as-promised": "^7.1.2",
         "eslint": "^8.57.0",
         "eslint-config-prettier": "^9.1.0",
         "eslint-plugin-prettier": "^5.1.3",


Test patch:
diff --git a/libraries/math/tests/instruction_count.rs b/libraries/math/tests/instruction_count.rs
index 4d414645c..3604325d4 100644
--- a/libraries/math/tests/instruction_count.rs
+++ b/libraries/math/tests/instruction_count.rs
@@ -214,6 +214,70 @@ async fn test_f64_pow() {
     banks_client.process_transaction(transaction).await.unwrap();
 }
 
+#[tokio::test]
+async fn test_u128_multiply() {
+    let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
+
+    pc.set_compute_max_units(10000);
+
+    let (mut banks_client, payer, recent_blockhash) = pc.start().await;
+
+    let mut transaction = Transaction::new_with_payer(
+        &[instruction::u128_multiply(u64::MAX.into(), u64::MAX.into())],
+        Some(&payer.pubkey()),
+    );
+    transaction.sign(&[&payer], recent_blockhash);
+    banks_client.process_transaction(transaction).await.unwrap();
+}
+
+#[tokio::test]
+async fn test_u128_divide() {
+    let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
+
+    pc.set_compute_max_units(10000);
+
+    let (mut banks_client, payer, recent_blockhash) = pc.start().await;
+
+    let mut transaction = Transaction::new_with_payer(
+        &[instruction::u128_divide(u128::MAX, u128::MAX / 69)],
+        Some(&payer.pubkey()),
+    );
+    transaction.sign(&[&payer], recent_blockhash);
+    banks_client.process_transaction(transaction).await.unwrap();
+}
+
+#[tokio::test]
+async fn test_f64_multiply() {
+    let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
+
+    pc.set_compute_max_units(10000);
+
+    let (mut banks_client, payer, recent_blockhash) = pc.start().await;
+
+    let mut transaction = Transaction::new_with_payer(
+        &[instruction::f64_multiply(f64::powf(2., 42.), 1e-4)],
+        Some(&payer.pubkey()),
+    );
+    transaction.sign(&[&payer], recent_blockhash);
+    banks_client.process_transaction(transaction).await.unwrap();
+}
+
+#[tokio::test]
+async fn test_f64_divide() {
+    let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));
+
+    pc.set_compute_max_units(10000);
+
+    let (mut banks_client, payer, recent_blockhash) = pc.start().await;
+
+    let mut transaction = Transaction::new_with_payer(
+        &[instruction::f64_divide(f64::powf(2., 42.), 420420.6969)],
+        Some(&payer.pubkey()),
+    );
+    transaction.sign(&[&payer], recent_blockhash);
+    banks_client.process_transaction(transaction).await.unwrap();
+}
+
 #[tokio::test]
 async fn test_noop() {
     let mut pc = ProgramTest::new("spl_math", id(), processor!(process_instruction));