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

Conversation

gitteri
Copy link
Contributor

@gitteri gitteri commented Nov 27, 2024

This adds four new helper functions to allow developers to better interact with interest bearing tokens.

  1. amountToUiAmountForMintWithoutSimulation - converts a number/bigint amount for a given mint at the current time to a UiAmount
  2. amountToUiAmountWithoutSimulation - converts a given number/bigint amount and detailed information about the mint's interest rates to a UiAmount
  3. uiAmountToAmountForMintWithoutSimulation- converts a given ui amount string for a given mint at the current time to amount
  4. uiAmountToAmountWithoutSimulation - converts a given ui amount string and detailed information about the mint's interest rates to amount

UiAmounts are provided with the token's decimals as its precision and truncated instead of rounded to avoid rounding issues.
Amounts are processed in the base unit of the token (if the token has 2 decimals 1.23 is represented as 123)

@mergify mergify bot added the community Community contribution label Nov 27, 2024
preUpdateAverageRate: number;
lastUpdateTimestamp: bigint;
lastUpdateTimestamp: number;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these were changed because the buffer encoder does not work with bigints. numbers should be fine for timestamps and this doesn't seem to impact any other current usage

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what do you mean about the buffer encoder not working? We use bigints in a lot of the extensions and they seem to work. I'd prefer to leave this as is

Copy link
Contributor Author

@gitteri gitteri Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically if you use a bigint it has an issue when trying to encode this state due to this function that's in the buffer encoding library:

function divmodInt64(src) {
    const hi32 = Math.floor(src / V2E32); // cannot mix bigint and numbers here
    const lo32 = src - (hi32 * V2E32);
    return { hi32, lo32 };
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe to fix this we'd have to fix the buffer encoding library. I took a look through all other state type definitions I could find and didn't see any other bigints

Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really great work! Mostly small things to fixup, but the logic is there and the tests look good

connection: Connection,
mint: PublicKey,
uiAmount: string,
programId = TOKEN_PROGRAM_ID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you, but since this function is taking a mint address, we can just figure out the program id from the mint owner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep good call. I'll update this

@@ -1,7 +1,10 @@
import Decimal from 'decimal.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm likely missing something about JS math, but JS's native number is a 64-bit float, just like Rust's f64. Why do we need another dependency for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

floating point math in js is notoriously bad. As an example: 0.1 + 0.2 !== 0.3 (it actually equals 0.30000000000000004). I wish I knew of a better way than importing a new dependency. Any suggestions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust has the same behavior, so maybe that's actually ok?

    println!("{}", 0.1 + 0.2);

Gives

0.30000000000000004

export async function amountToUiAmountForMintWithoutSimulation(
connection: Connection,
mint: PublicKey,
amount: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use bigint in most places in the code to represent a Rust u64 -- can we do the same thing here?

*/

export function amountToUiAmountWithoutSimulation(
amount: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a bigint instead like the rest of the package?

connection: Connection,
mint: PublicKey,
amount: string,
programId = TOKEN_PROGRAM_ID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you, but since this takes the mint's public key, we can use the account owner instead of forcing people to pass this

Comment on lines 199 to 201
if (programId.equals(TOKEN_PROGRAM_ID)) {
return BigInt(uiAmountScaled.trunc().toString());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as with the other function, I think we can remove this

): Promise<bigint> {
Decimal.set({ toExpPos: 24, toExpNeg: -24 })
const mintInfo = await getMint(connection, mint, 'confirmed', programId);
const uiAmountScaled = new Decimal(uiAmount).mul(new Decimal(10).pow(mintInfo.decimals));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: same as with the other function, this can get moved under if (!interestBearingMintConfigState)

return BigInt(uiAmountScaled.trunc().toString());
}

const currentTime = Math.floor(Date.now() / 1000); // Convert to seconds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

up to you: same here, can fetch the clock for the cluster time

preUpdateAverageRate: number;
lastUpdateTimestamp: bigint;
lastUpdateTimestamp: number;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what do you mean about the buffer encoder not working? We use bigints in a lot of the extensions and they seem to work. I'd prefer to leave this as is

pnpm-lock.yaml Outdated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you revert any changes that aren't necessary for the PR? It seems like we're just adding sinon and decimal.js currently (although I hope we don't need the second one)

Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking really close! Once the encoding bit is solved and these nits are fixed, it should be good to go

* @param r - The interest rate in basis points.
* @returns The calculated exponent.
*/
const calculateExponentForTimesAndRate = (t1: number, t2: number, r: number) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we don't typically use arrow functions in this package -- can this be updated to a function(...)?

* @returns A promise that resolves to the current timestamp in seconds.
* @throws An error if the sysvar clock cannot be fetched or parsed.
*/
const getSysvarClockTimestamp = async (connection: Connection): Promise<number> => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: same here, let's avoid the arrow function

* @param connection Connection to use
* @param mint Mint to use for calculations
* @param amount Amount of tokens to be converted to Ui Amount
* @param programId SPL Token program account (default: TOKEN_PROGRAM_ID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this param doesn't exist

Suggested change
* @param programId SPL Token program account (default: TOKEN_PROGRAM_ID)


// Calculate post-update exponent
// e^(currentRate * (currentTimestamp - lastUpdateTimestamp) / (SECONDS_PER_YEAR * ONE_IN_BASIS_POINTS))
const postUpdateExp = calculateExponentForTimesAndRate(lastUpdateTimestamp, Number(currentTimestamp), currentRate)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Number shouldn't be needed, unless I've missed something

Suggested change
const postUpdateExp = calculateExponentForTimesAndRate(lastUpdateTimestamp, Number(currentTimestamp), currentRate)
const postUpdateExp = calculateExponentForTimesAndRate(lastUpdateTimestamp, currentTimestamp, currentRate)

const totalScale = preUpdateExp * postUpdateExp;

// Calculate original principle by dividing the UI amount (principle + interest) by the total scale
const originalPrinciple = uiAmountScaled / totalScale;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: If we want to use the term, it's principal, not principle https://www.merriam-webster.com/dictionary/principal


// Calculate original principle by dividing the UI amount (principle + interest) by the total scale
const originalPrinciple = uiAmountScaled / totalScale;
return BigInt(Math.floor(originalPrinciple));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is there a reason to use floor over trunc like with the other one?

Comment on lines 231 to 232
* @param programId SPL Token program account (default: TOKEN_PROGRAM_ID)
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since the param was removed

Suggested change
* @param programId SPL Token program account (default: TOKEN_PROGRAM_ID)
*

throw new Error('Invalid program ID');
}

const mintInfo = await getMint(connection, mint, 'confirmed', programId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes a second network call to fetch the account again, so let's do unpackMint like with the other function

Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last one, then we can merge!

joncinque
joncinque previously approved these changes Dec 4, 2024
Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all your patience and your work! Once it passes CI, I'll merge it in

@mergify mergify bot dismissed joncinque’s stale review December 4, 2024 19:29

Pull request has been modified.

@joncinque joncinque merged commit 96a1575 into solana-labs:master Dec 4, 2024
24 checks passed
@joncinque joncinque changed the title add JS helpers for interest bearing tokens' UIAmounts token-js: add JS helpers for interest bearing tokens' UIAmounts Dec 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
community Community contribution
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants