I am doing the exercise from the Solana lending protocol video from YouTube, and would like to calculate compound interest, ensuring:
- Good floating point accuracy
- My program works within CU limits
Using E
versus iterating
E
is an irrational number and a float and will therefore be less precise. The results below show slightly different results for E versus iterating using Decimals.- Iterating seems to be more CU intensive - if I try say 500 periods instead of 120 I'll get a CU error.
Using Decimal
versus f64
While the example uses f64, I believe Decimal to be more accurate. But does Decimal math even work on Solana? An LLM (I know, it may have made this up) says:
While Rust is the primary language for Solana programs, rust_decimal isn't directly compatible with Solana's environment [1]. Solana programs typically use the standard Rust types for numerical operations, and integrating a library like rust_decimal would require significant adaptation.
However at least on localhost I was able to get the Decimal crate running.
What's the best way to calculate compound interest on Solana?
I have a working program showing the differences in each approach. It's possible there may be other approaches I should consider.
interest_rates.rs
use std::f64::consts::E;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::{Decimal, RoundingStrategy};
pub fn get_final_balance_using_decimal(
initial_balance: u64,
elapsed_periods: u64,
interest_rate_basis_points: u64,
) -> u64 {
let initial_balance_decimal: Decimal = Decimal::from(initial_balance);
let interest_rate_decimal: Decimal =
Decimal::from(interest_rate_basis_points) / Decimal::from(10_000);
let mut final_balance = initial_balance_decimal;
for _ in 0..elapsed_periods {
final_balance = final_balance
.checked_add(
final_balance
.checked_mul(interest_rate_decimal)
.unwrap()
.round_dp_with_strategy(2, RoundingStrategy::MidpointNearestEven),
)
.unwrap();
}
final_balance.to_u64().unwrap()
}
pub fn get_final_balance_using_e(
initial_balance: u64,
elapsed_periods: u64,
interest_rate_basis_points: u64,
) -> u64 {
let multiplier =
E.powf(interest_rate_basis_points as f64 * elapsed_periods as f64 / 10_000 as f64);
(initial_balance as f64 * multiplier) as u64
}
lib.rs
mod interest_rates;
use interest_rates::{get_final_balance_using_decimal, get_final_balance_using_e};
use anchor_lang::prelude::*;
declare_id!("5S8qdNZWGD2wpcg4fEmgutHgvnCjYZezamirN5TdJh9g");
#[program]
pub mod test_decimals {
use super::*;
pub fn compare_interest_techniques(_ctx: Context<CompareInterestTechniques>) -> Result<()> {
const INITIAL_BALANCE: u64 = 100_000;
// 120 OK, 500 will kill the Decimal technique
const PERIODS: u64 = 120;
const INTEREST_RATE_BASIS_POINTS: u64 = 2;
let final_balance_using_decimal =
get_final_balance_using_decimal(INITIAL_BALANCE, PERIODS, INTEREST_RATE_BASIS_POINTS);
let final_balance_using_e =
get_final_balance_using_e(INITIAL_BALANCE, PERIODS, INTEREST_RATE_BASIS_POINTS);
msg!(
"Final balances using decimal: {:?} and using E: {:?}",
final_balance_using_decimal,
final_balance_using_e
);
Ok(())
}
}
#[derive(Accounts)]
pub struct CompareInterestTechniques {}
test-decimals.ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { TestDecimals } from "../target/types/test_decimals";
import { getLogs } from "@solana-developers/helpers";
import { expect } from "chai";
describe("test_decimals", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const connection = anchor.getProvider().connection;
const program = anchor.workspace.TestDecimals as Program<TestDecimals>;
it("Logs the different interest calculations!", async () => {
const tx = await program.methods.compareInterestTechniques().rpc();
const logs = await getLogs(connection, tx);
console.log("Your transaction signature", tx);
console.log("Your logs", logs);
});
});
Solana logs
test_decimals
Your transaction signature dqZMFHam3YxcmYBGh8xcnyBXaWXhuKo9nkjQYa4tuAm4ey89mNEucFHkW9aG6rQCvKyThk1rW9icaiRay3ir5VZ
Your logs [
'Program 5S8qdNZWGD2wpcg4fEmgutHgvnCjYZezamirN5TdJh9g invoke [1]',
'Program log: Instruction: CompareInterestTechniques',
'Program log: Final balances using decimal: 102428 and using E: 102429',
'Program 5S8qdNZWGD2wpcg4fEmgutHgvnCjYZezamirN5TdJh9g consumed 63666 of 200000 compute units',
'Program 5S8qdNZWGD2wpcg4fEmgutHgvnCjYZezamirN5TdJh9g success'
]