refactor: remove `toDouble` method and reverted other changes (#3531)
What changed, and why it matters
This commit removes a shortcut method that converted cryptocurrency amounts directly into regular 'double' numbers and reverts some earlier helper-function changes. It also fixes one spot where Solana transactions were looking up the wrong asset type. The overall effect is to make amount formatting and parsing go through string-based conversions again, which reduces the risk of tiny rounding errors or precision loss when handling very small crypto balances.
Treat as a defensive correctness fix. Review whether any other callers still use toDouble() or direct double conversions for monetary amounts, verify unit tests cover large and fractional BigInt values in format_fixed/parse_fixed, and confirm the Solana asset lookup change does not break token price display for existing transactions.
Security signals we found
Removal of floating-point conversion shortcut (toDouble) in favor of string parsing
Reversion to string-based decimal multiplier handling in fixed-point math helpers
Correction of Solana asset lookup to use chain-specific resolver
Potential precision/rounding concerns from prior double conversion path
No explicit security language, CVE, or attacker scenario in commit message
Evidence from the diff
The patch removes Money.toDouble() and replaces its callers with double.parse(transaction.amount.toString()). It also reverts format_fixed.dart and parse_fixed.dart from a cached BigInt multiplier (multiplierOf) back to a string multiplier (getMultiplier) parsed into BigInt on use, and changes one Solana asset lookup from assetOfTransaction to solana!.assetOfTransaction(…). The stated intent is a refactor/revert, but the change path reduces floating-point conversion surface area and corrects an asset-resolution inconsistency for Solana.
Changed components
cw_core/lib/amount/money.dartcw_core/lib/format_fixed.dartcw_core/lib/parse_fixed.dartlib/view_model/dashboard/transaction_list_item.dartInspect captured patch +45 / −32
diff --git a/cw_core/lib/amount/money.dart b/cw_core/lib/amount/money.dart
index 20857e09..de5483e4 100644
--- a/cw_core/lib/amount/money.dart
+++ b/cw_core/lib/amount/money.dart
@@ -202,10 +202,6 @@ class Money implements Comparable<Money> {
@override
int get hashCode => amount.hashCode ^ currency.hashCode;
- // Added this to reduce the hops we do to convert Money to double
- // for fiat conversion and display
- double toDouble() => amount / multiplierOf(currency.decimals);
-
@override
String toString() => formatFixed(amount, currency.decimals);
diff --git a/cw_core/lib/format_fixed.dart b/cw_core/lib/format_fixed.dart
index aa016281..4e02c48d 100644
--- a/cw_core/lib/format_fixed.dart
+++ b/cw_core/lib/format_fixed.dart
@@ -4,31 +4,44 @@ String formatFixed(BigInt value, int? decimals, {int? fractionalDigits, bool tri
decimals ??= 0;
fractionalDigits ??= decimals;
- final multiplier = multiplierOf(decimals);
- var negative = value.isNegative;
- if (negative) value = -value;
+ final multiplier = getMultiplier(decimals);
+ // Make sure wei is a big number (convert as necessary)
+ final negative = value.isNegative;
+ if (negative) {
+ value = -value;
+ }
- var fraction = (value % multiplier).toString().padLeft(decimals, "0");
+ var fraction =
+ value.modPow(BigInt.one, BigInt.parse(multiplier)).toString().padLeft(decimals, "0");
- if (fractionalDigits < 0) fractionalDigits = 0;
- if (fractionalDigits > decimals) fractionalDigits = decimals;
+ if (fractionalDigits < 0) {
+ fractionalDigits = 0;
+ }
+ if (fractionalDigits > decimals) {
+ fractionalDigits = decimals;
+ }
fraction = fraction.substring(0, fractionalDigits);
if (trimZeros) {
fraction = removeTrailing("0", fraction);
}
- final whole = value ~/ multiplier;
+ final whole = value ~/ BigInt.parse(multiplier);
final valString = fraction.isEmpty ? "$whole" : "$whole.$fraction";
- if (negative) return "-$valString";
+ if (negative) {
+ return "-$valString";
+ }
return valString;
}
String removeTrailing(String pattern, String from) {
- if (pattern.isEmpty) return from;
+ if (pattern.isEmpty) {
+ return from;
+ }
+
var i = from.length;
while (i > 0 && from.startsWith(pattern, i - pattern.length)) {
i -= pattern.length;
diff --git a/cw_core/lib/parse_fixed.dart b/cw_core/lib/parse_fixed.dart
index dbbc24f7..a5a6d7b8 100644
--- a/cw_core/lib/parse_fixed.dart
+++ b/cw_core/lib/parse_fixed.dart
@@ -37,40 +37,44 @@ BigInt parseFixed(String value, int decimals) {
}
final negative = value.startsWith("-");
- if (negative) value = value.substring(1);
+ if (negative) {
+ value = value.substring(1);
+ }
- if (value == ".") throw FormatException("missing value, value, $value");
+ if (value == ".") {
+ throw FormatException("missing value, value, $value");
+ }
- if (value.startsWith(".")) value = "0$value";
+ if (value.startsWith(".")) {
+ value = "0$value";
+ }
final comps = value.split(".");
if (comps.length > 2) {
throw FormatException("too many decimal points, value, $value");
}
- var whole = comps.isNotEmpty ? comps[0] : "0";
- var fraction = (comps.length == 2 ? comps[1] : "0").padRight(decimals, "0");
+ final whole = comps.isNotEmpty ? comps[0] : "0";
+ final fraction = (comps.length == 2 ? comps[1] : "0").padRight(decimals, "0");
if (fraction.length > multiplier.length - 1) {
throw FormatException(
- "fractional component(${fraction.length}) exceeds decimals(${decimals}), underflow, parseFixed");
+ "fractional component(${fraction.length}) exceeds decimals(${decimals}), underflow, parseFixed",
+ );
}
final wholeValue = BigInt.parse(whole);
final fractionValue = BigInt.parse(fraction);
- final multiplierValue = multiplierOf(decimals);
+ final multiplierValue = BigInt.parse(multiplier);
var wei = (wholeValue * multiplierValue) + fractionValue;
- if (negative) wei *= BigInt.from(-1);
+ if (negative) {
+ wei *= BigInt.from(-1);
+ }
return wei;
}
// Returns a string "1" followed by decimal "0"s
String getMultiplier(int decimals) => "1".padRight(decimals + 1, "0");
-
-final _multipliers = <int, BigInt>{};
-
-// this is more direct and faster than having it as string then parsing everytime to get the number
-BigInt multiplierOf(int decimals) => _multipliers[decimals] ??= BigInt.from(10).pow(decimals);
diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart
index 9a7dfa65..620dcd1e 100644
--- a/lib/view_model/dashboard/transaction_list_item.dart
+++ b/lib/view_model/dashboard/transaction_list_item.dart
@@ -203,7 +203,7 @@ class TransactionListItem extends ActionListItem with Keyable {
case WalletType.decred:
case WalletType.zcash:
amount = calculateFiatAmountRaw(
- cryptoAmount: transaction.amount.toDouble(),
+ cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
case WalletType.ethereum:
@@ -214,15 +214,15 @@ class TransactionListItem extends ActionListItem with Keyable {
final asset = assetOfTransaction;
final price = balanceViewModel.fiatConversionStore.prices[asset];
amount = calculateFiatAmountRaw(
- cryptoAmount: transaction.amount.toDouble(),
+ cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
case WalletType.solana:
- final asset = assetOfTransaction;
+ final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConversionStore.prices[asset];
amount = calculateFiatAmountRaw(
- cryptoAmount: transaction.amount.toDouble(),
+ cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
@@ -230,7 +230,7 @@ class TransactionListItem extends ActionListItem with Keyable {
final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConversionStore.prices[asset];
amount = calculateFiatAmountRaw(
- cryptoAmount: transaction.amount.toDouble(),
+ cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
@@ -242,7 +242,7 @@ class TransactionListItem extends ActionListItem with Keyable {
}
final price = balanceViewModel.fiatConversionStore.prices[asset];
amount = calculateFiatAmountRaw(
- cryptoAmount: transaction.amount.toDouble(),
+ cryptoAmount: double.parse(transaction.amount.toString()),
price: price,
).withLocalSeperator(_appStore.settingsStore.languageCode);
break;
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.