Feat/shield create subscription crypto#6456
Conversation
…aciton in controller
| // conversion rate is a float string e.g: "1.0" | ||
| // We need to handle float conversion rates with integer math for BigInt. | ||
| // We'll scale the conversion rate to an integer by multiplying by 10^4. | ||
| // conversionRate is in usd decimal. In most currencies, we only care about 2 decimals (cents) | ||
| // So, scale must be max of 10 ** 4 (most exchanges trade with max 4 decimals of usd) | ||
| // This allows us to avoid floating point math and keep precision. | ||
| const CONVERSION_RATE_SCALE = 10n ** 4n; | ||
| const conversionRateScaled = BigInt( | ||
| Number(conversionRate) * Number(CONVERSION_RATE_SCALE), | ||
| ); | ||
| const priceAmount = this.#getSubscriptionPriceAmount(price); | ||
| const priceAmountScaled = BigInt( | ||
| priceAmount * Number(CONVERSION_RATE_SCALE), | ||
| ); | ||
|
|
||
| const amount = | ||
| ((priceAmountScaled / CONVERSION_RATE_SCALE) * | ||
| BigInt(10) ** BigInt(tokenPaymentInfo.decimals) * | ||
| CONVERSION_RATE_SCALE) / | ||
| conversionRateScaled; |
There was a problem hiding this comment.
I think we should be able to do that computation without relying on floating point arithmetic.
Does the following work?
tokenApproveAmount = priceInUSD * tokenPerUSD
= (priceAmount / priceDecimals) * (tokenAmount / tokenDecimals)
= (priceAmount * tokenAmount) / (priceDecimals * tokenDecimals)
Co-authored-by: matthiasgeihs <62935430+matthiasgeihs@users.noreply.github.com>
| BigInt(10) ** BigInt(tokenPaymentInfo.decimals) * | ||
| CONVERSION_RATE_SCALE) / | ||
| conversionRateScaled; | ||
| return amount; |
There was a problem hiding this comment.
Bug: Token Approval Bug Causes Precision Loss
The token approve amount calculation in #getTokenApproveAmount has a precision loss bug. The BigInt integer division (priceAmountScaled / CONVERSION_RATE_SCALE) truncates the result, especially for small or fractional subscription prices. This can lead to an incorrect or zero token approval amount, causing transaction failures.
| @@ -1,5 +1,4 @@ | |||
| import { handleFetch } from '@metamask/controller-utils'; | |||
| import nock, { cleanAll, isDone } from 'nock'; | |||
There was a problem hiding this comment.
Why did you remove the usage of nock here?
Remember that we should keep changes minimal within the context of the PR, if possible.
| @@ -1,3 +1,5 @@ | |||
| import { handleFetch } from '@metamask/controller-utils'; | |||
There was a problem hiding this comment.
need to move @metamask/controller-utils to main dependencies now
…eld-create-subscription-crypto
| const tokenAmount = | ||
| (priceAmountScaled * tokenDecimal) / conversionRateScaled; | ||
| return tokenAmount; | ||
| } |
There was a problem hiding this comment.
Bug: BigInt Scaling Logic Fails
The BigInt scaling logic in #getTokenApproveAmount loses precision. Values are scaled up by SCALE and then immediately divided by SCALE using integer division, which truncates decimal values. This defeats the purpose of scaling, resulting in incorrect conversionRateScaled and priceAmountScaled values, and ultimately wrong token approval amounts for crypto payments.
as far as i can see comments have been addressed.
dismissing so we can move forward with other features.
(if issues persist, we still have some time to address them before we release the first version of the controller.)
|
|
||
| const tokenAmount = | ||
| (priceAmountScaled * tokenDecimal) / conversionRateScaled; | ||
| return tokenAmount; |
There was a problem hiding this comment.
Bug: Token Approval Precision Loss
The scaling logic in #getTokenApproveAmount is flawed. conversionRateScaled and priceAmountScaled are scaled up by SCALE then immediately divided by SCALE using BigInt integer division. This truncates fractional parts, causing precision loss and incorrect token approval amounts. It also risks division by zero if conversionRateScaled becomes 0n.
Explanation
getCryptoApproveTransactionParamswhich handle validating and prepare params to create crypto approve transaction for user to sign then submit to subscription servicestartSubscriptionWithCryptowhich register submitted crypto transaction with subscription service to start subscriptionReferences
Checklist