Repository navigation
fix(tokens): convert the buy amount out of the entry currency - #1523
Merged
Merged
Conversation
The buy gate compares the typed amount against a ceiling derived from token balances, which are USD-denominated. It read that amount through the `enteredAmount` getter, which stamps the raw number with the token balance's currency without converting it, so 500 naira was compared as $500 and blocked against a balance worth about $0.87. Any currency trading well below 1:1 is affected, which is why this reached us from Nigeria and, earlier, from India. USD is unaffected, and so is Add Money, which returns its limit in the entry currency already. Convert through `rateToUsd` before the comparison, the way `checkBalanceLimit` and every downstream amount already do. A missing rate blocks rather than passes: `Rate.ignore` carries `Double.MIN_VALUE`, so converting with it would collapse the amount to nearly zero and clear every ceiling.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A user in Nigeria with about $0.87 of USDF could not buy at any amount. Entering ₦500 against a balance the same screen displayed as "₦1,330.74 available" produced Insufficient Balance.
Cause
checkFundingAmountcompares the typed amount againsttransactionLimit(), which is built fromtokenCoordinator.tokenBalancesand is therefore USD-denominated. It read the typed amount through the privateenteredAmountgetter:That stamps the raw number with the token balance's currency rather than converting it. The user types 500 naira and the gate evaluates
500 > 0.87.Fiat.valueGreaterThancomparestoDouble()with no currency check, so nothing catches the mismatch.This is a single wrong accessor, not two implementations that drifted. Every other consumer of the amount builds it against the rate's currency — the submit path at lines 927, 938, 1005, 1043, 1256 and 1317, and the sibling validator
checkBalanceLimit, which converts viarateToUsdalready.checkFundingAmountwas the only caller of the mislabeled getter, which is now unused.Fix
Convert before comparing, matching
checkBalanceLimit. ₦500 at roughly ₦1,530/$ becomes $0.33 and clears the $0.87 ceiling.A missing rate now blocks rather than passes.
Rate.ignorecarriesDouble.MIN_VALUE, so converting with it collapses any amount to nearly zero and would clear every ceiling.Scope
Every non-USD user on the Get/Buy path, on 4503. Severity tracks the exchange rate: NGN and INR block almost any entry, while EUR and GBP sit close enough to 1.0 that the gate mostly behaved, which is likely how this survived. USD is arithmetically unaffected —
fxis 1.0 and the conversion is identity. Add Money is unaffected: that branch returnsmaxSendPerDayin the entry currency.The same class of bug was fixed for INR in #1173, in the display path. That change corrected
maxAmountFlow, which is why the screenshot shows a correct "₦1,330.74 available" above a gate that disagrees with it.Tests
checkFundingAmountandtransactionLimithad no coverage. Three tests added toSwapViewModelErrorTest:The first fails against the unfixed gate; I confirmed that by reverting the source and re-running. The third fails too. The second passes either way — for NGN the raw number always exceeds its own converted value, so no entry can distinguish the two code paths in that direction. It is a guard, not a regression test, and I have left it in as one.
Full module suite: 114 tests, no failures.
Follow-ups, not in this PR
The gate and the "available" hint compute the same ceiling from independent paths with different limit sources (
sendLimitForversusmaxToAdd), which is what let them disagree. Deriving one from the other would prevent a repeat, but it changes daily-limit behavior and deserves its own change.A currency guard on
Fiat.valueGreaterThanandminwould have turned this into a loud failure instead of a silent one.