Skip to content

Fix NaN conversionRate values#194

Merged
whymarrh merged 2 commits intoMetaMask:developfrom
whymarrh:fix-NaN-conversionRate
Jan 24, 2020
Merged

Fix NaN conversionRate values#194
whymarrh merged 2 commits intoMetaMask:developfrom
whymarrh:fix-NaN-conversionRate

Conversation

@whymarrh
Copy link
Copy Markdown
Contributor

@whymarrh whymarrh commented Jan 23, 2020

This PR fixes[1] an issue in the extension where the conversionRate in state is NaN.

How this manifested itself in the extension

The CurrencyRateController was introduced in v6.6.0[2][3] (v6.5.3...v6.6.0) and we saw MetaMask/metamask-extension#6725 shortly after that went out. The NaN in the background state ends up as null in the UI, because JSON, and is then used to format balances in the CurrencyDisplayContainer component:

@@ -108,6 +108,17 @@ describe('Confirm Transaction utils', () => {
 
       assert.equal(fiatTransactionAmount, '468.58')
     })
+
+    it('should handle a null conversionRate', () => {
+      const fiatTransactionAmount = utils.getValueFromWeiHex({
+        value: '0xde0b6b3a7640000',
+        toCurrency: 'usd',
+        conversionRate: null,
+        numberOfDecimals: 2,
+      })
+
+      assert.equal(fiatTransactionAmount, '468.58')
+    })
   })
 
   describe('getTransactionFee', () => {
$ yarn run test:unit
yarn run v1.21.1
$ cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive "test/unit/**/*.js" "ui/app/**/*.test.js"


  Confirm Transaction utils
    getValueFromWeiHex
      1) should handle a null conversionRate


  0 passing (271ms)
  1 failing

  1) Confirm Transaction utils
       getValueFromWeiHex
         should handle a null conversionRate:
     BigNumber Error: times() not a number: null
      at raise (node_modules/bignumber.js/bignumber.js:1190:25)
      at node_modules/bignumber.js/bignumber.js:1178:33
      at new BigNumber (node_modules/bignumber.js/bignumber.js:193:67)
      at BigNumber.P.times.P.mul (node_modules/bignumber.js/bignumber.js:2092:37)
      at node_modules/ramda/src/invoker.js:38:29
      at node_modules/ramda/src/internal/_curryN.js:37:27
      at node_modules/ramda/src/internal/_arity.js:5:45
      at node_modules/ramda/src/over.js:37:47
      at node_modules/ramda/src/lens.js:35:9
      at over (node_modules/ramda/src/over.js:37:56)
      at node_modules/ramda/src/internal/_curry3.js:21:50
      at f2 (node_modules/ramda/src/internal/_curry2.js:25:16)
      at node_modules/ramda/src/converge.js:40:18
      at f1 (node_modules/ramda/src/internal/_curry1.js:17:17)
      at when (node_modules/ramda/src/when.js:33:20)
      at node_modules/ramda/src/internal/_curry3.js:26:46
      at f1 (node_modules/ramda/src/internal/_curry1.js:17:17)
      at node_modules/ramda/src/internal/_pipe.js:3:14
      at node_modules/ramda/src/internal/_pipe.js:3:27
      at node_modules/ramda/src/internal/_pipe.js:3:27
      at node_modules/ramda/src/internal/_pipe.js:3:27
      at node_modules/ramda/src/internal/_pipe.js:3:27
      at node_modules/ramda/src/internal/_pipe.js:3:27
      at node_modules/ramda/src/internal/_arity.js:5:45
      at converter (ui/app/helpers/utils/conversion-util.js:128:7)
      at Object.getValueFromWeiHex (ui/app/helpers/utils/confirm-tx.util.js:64:10)
      at Context.getValueFromWeiHex (ui/app/helpers/utils/confirm-tx.util.test.js:148:43)

This has reared its head in a few GH issues:

Missing symbols in the API

The root cause here would be the exchange API response missing the symbol. This is at least possible if the selected symbol errors, as the API still returns a "successful" response. Note the 200 status code:

curl --silent \
  --dump-header - \
  'https://min-api.cryptocompare.com/data/price?fsym=eth&tsyms=usp'
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 23 Jan 2020 21:26:11 GMT
Content-Type: application/json; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Content-Security-Policy: frame-ancestors 'none'
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Cookie, Set-Cookie, Authorization
Access-Control-Allow-Credentials: true
Cache-Control: no-cache, no-store
CryptoCompare-Cache-HIT: false
CryptoCompare-Server-Id: ccc-api09

{"Response":"Error","Message":"There is no data for any of the toSymbols USP .","HasWarning":true,"Type":2,"RateLimit":{},"Data":{},"Warning":"There is no data for the toSymbol/s USP ","ParamWithError":"tsyms"}

[1] In part, we'll need to update the extension with this.

@codecov-io
Copy link
Copy Markdown

codecov-io commented Jan 23, 2020

Codecov Report

Merging #194 into develop will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@          Coverage Diff           @@
##           develop   #194   +/-   ##
======================================
  Coverage      100%   100%           
======================================
  Files           23     23           
  Lines         1518   1518           
  Branches       211    211           
======================================
  Hits          1518   1518

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5b7eff7...d209cc9. Read the comment docs.

@whymarrh whymarrh force-pushed the fix-NaN-conversionRate branch from ccb4f40 to 5b7eff7 Compare January 23, 2020 23:19
Copy link
Copy Markdown
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

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

LGTM!

const conversionRate = Number(json[currency.toUpperCase()]);

if (!Number.isFinite(conversionRate)) {
throw new Error(`Invalid response for ${currency.toUpperCase()}: ${json[currency.toUpperCase()]}`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see a potential bug throwing an error here. While ago we had an issue with concurrency in calls to the API happening asynchronously in a bad way. the solution was to add a semaphore to take care of the current state of the controller while doing calls. So, this method is used here
https://github.com/MetaMask/gaba/blob/2dc732ff1b13be27fc5f83a197f379fb8669be18/src/assets/CurrencyRateController.ts#L144
and is being called from other places with safelyExecute so throwing an error is fine. But throwing an error without releasing
https://github.com/MetaMask/gaba/blob/2dc732ff1b13be27fc5f83a197f379fb8669be18/src/assets/CurrencyRateController.ts#L148
could block this method.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good point, though this seems to be a pre-existing problem (one I introduced a while back 😬 ). handleFetch already throws an error in some cases, so it could already block.

I guess the solution would be to put a try block around the code between the mutex.aquire() and releaseLock(), and put releaseLock() in the finally clause.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You're right, that's why we have this method called safelyExecute to handle errors, all async methods are called with that if it throws it just keeps polling.
For the same reason having a try catch inside safelyExecute doesn't look good from my point of view. As you mentioned before, going with 0 as value would be good instead of throwing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would solve the problem you identified (the mutex never being released). It should be perfectly fine. 🤷‍♂ Whether it's being called from safelyExecute or not shouldn't make any difference.

Though it was try ... finally that I suggested, not try ... catch. try ... finally would let us release the lock in the finally block regardless whether an error was thrown or not, then it would let the error continue to bubble upwards if it was thrown, to be caught by safelyExecute or wherever else. It's a pretty standard way of dealing with cases like this where you need to ensure something is released.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's a pretty standard way of dealing with cases like this yes, I know.
I'm fine whatever handles the issue I brought 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have created a separate PR to address this problem: #195

Copy link
Copy Markdown
Contributor

@estebanmino estebanmino left a comment

Choose a reason for hiding this comment

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

all right let's merge this first?

@whymarrh whymarrh merged commit e68a040 into MetaMask:develop Jan 24, 2020
@whymarrh whymarrh deleted the fix-NaN-conversionRate branch January 24, 2020 20:43
whymarrh added a commit to whymarrh/gaba that referenced this pull request Jan 24, 2020
@whymarrh whymarrh mentioned this pull request Jan 24, 2020
Mrtenz pushed a commit that referenced this pull request Oct 16, 2025
The two provider construction functions have been removed. These
functions are instead availabile from the new package
`eth-json-rpc-provider`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants