-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Complete level 36 migration of TypeScript by converting conversion.utils.js and deprecate old conversion util #17262
Conversation
9160788
to
8260271
Compare
a66c304
to
461d2cf
Compare
9db6610
to
8efb871
Compare
shared/modules/Numeric.ts
Outdated
if (parts.length === 1) { | ||
return false; | ||
} | ||
return parts.every((part) => isHexStringOrNegatedHexString(part)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this implementation will pattern match -<hex num>.-<hex num>
as a decimal hex. Flagging to ensure it's intentional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a safety net. I don't think its actually valid hex to do hex
.hex
but BigNumber uses that notation internally for hexadecimal containing a decimal point when stringified.... it seems... There should be no other cases where we get 'hex'.'hex' other than as a result of toString on a BigNumber which will always be re-interpretable by BigNumber during instantiation. We should add tests for this soon but as of right now I am of the mind that this is okay.
shared/modules/Numeric.ts
Outdated
@@ -0,0 +1,621 @@ | |||
import { BigNumber } from 'bignumber.js'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to break this PR up into stages, to make it easier to review?
e.g.
- Add this new
Numeric
module - Migrating code from
conversion.utils.js
toNumeric
in chunks - Delete
conversion.utils.js
That might make this less intimidating to review. And we can more effectively assign each chunk to the people most familiar with that code (e.g. we could ask the swaps team to just review the swaps changes).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Gudahtt I will break this down some but I don't know that I can break it down to the level of granularity that would be the easiest to review. I say this for the sake of expediency with hitting our goals for Q1 within the allotted time we have for TS as well because there are some order of operations issues. @DDDDDanica also asked for this, so cc'ing her for acknowledgement and thanking her for pushing back as well.
Add this new Numeric module
This would be easy to split out, considering it wouldn't be used anywhere yet.
Migrating code from conversion.utils.js to Numeric in chunks
So to do this I would need to redo some things because I converted conversion.utils.js to typescript first to get type feedback. To be able to merge this in chunks I would need to migrate to Numeric in chunks in JavaScript because if i attempt to do this in TypeScript it will not be mergeable due to lint / compile errors. So this would involve more rework than I think if sensible with the time constraints mentioned. However...
There's the fact that I relocated a bunch of methods that use conversionUtil from other parts of the codebase to the conversion.utils.js file. That would be a more meaningful break-up strategy and would mitigate a lot of LOC from this PR. I already did this PR breakdown here: #17319
I will see what else can be shaved off into separate PRs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry, I explained that poorly. I meant to say:
Migrating code that uses
conversion.utils.js
to useNumeric
instead, in chunks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Gudahtt I think i've got it fairly granular now. Some things have already merged, namely Numeric module and the relocation of various helper methods to the conversion.utils.js shared file. I will rename this PR and move from draft when it comes up but it now only contains the changes from the conversion.utils.js -> conversion.utils.ts conversion.
Builds ready [f5ec0cc]
Page Load Metrics (1280 ± 91 ms)
Bundle size diffs [🚀 Bundle size reduced!]
highlights:storybook
|
Codecov Report
@@ Coverage Diff @@
## develop #17262 +/- ##
===========================================
- Coverage 59.79% 59.69% -0.10%
===========================================
Files 936 937 +1
Lines 36142 35982 -160
Branches 9286 9232 -54
===========================================
- Hits 21611 21478 -133
+ Misses 14531 14504 -27
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
Converting to draft for now. Thanks @pedronfigueiredo and @DDDDDanica for initial reviews |
f5ec0cc
to
c9cadaf
Compare
Builds ready [c9cadaf]
Page Load Metrics (2853 ± 527 ms)
Bundle size diffs [🚀 Bundle size reduced!]
highlights:storybook
|
f937859
to
78a2bf9
Compare
Builds ready [78a2bf9]
Page Load Metrics (1482 ± 102 ms)
Bundle size diffs [🚨 Warning! Bundle size has increased!]
highlights:storybook
|
78a2bf9
to
a77cd99
Compare
a77cd99
to
731d4b0
Compare
Builds ready [731d4b0]
Page Load Metrics (1467 ± 115 ms)
Bundle size diffs [🚨 Warning! Bundle size has increased!]
highlights:storybook
|
20c29cd
to
d22468e
Compare
731d4b0
to
287650f
Compare
Builds ready [287650f]
Page Load Metrics (1309 ± 128 ms)
Bundle size diffs [🚀 Bundle size reduced!]
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work 👍
21b2ba4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that we plan on adding more tests in a future PR? Either way, big kudos for tackling this. I really like the improvements here, and the Numeric class seems like a much better design than before.
Builds ready [1959a6d]
Page Load Metrics (1259 ± 132 ms)
Bundle size diffs [🚀 Bundle size reduced!]
|
Explanation
This Pull request aims to complete the level 36 block of the ts-migration dashboard https://metamask.github.io/metamask-extension-ts-migration-dashboard/
This one was a large undertaking because conversions.util.js contained the dreaded 'conversionUtil' that had prolific use throughout the codebase. Usage was in many different places and forms:
Problems with the converter method.
Instantiation of the value is not guaranteed to yield a BigNumber
If you do not supply
fromNumericBase
to this function, which has no default value, the base value that you provided will be used. A few lines below this the 'convertedValue' is used as if it is guaranteed to be a BigNumber.The value returned from this method is not guaranteed to be any specific type
If you do not supply 'toNumericBase' you will get a BigNumber value returned. This isn't bad, except that your return value type is dependent on a prop passed to the method, and if you attempt to use a non BigNumber as a BigNumber we get the "Not a BigNumber" error.
fromCurrency and toCurrency have no impact on the operation except to check if they are the same
In many parts of our codebase I have seen conversionUtil called like this:
Because I was never confident in my understanding of conversions of ETH to Fiat I assumed there was some magic here. NOPE!
On the first line of the snippet at the beginning of this section, we check if fromCurrency and toCurrency are the same, if they are it does nothing, otherwise it checks that conversionRate was supplied and throws an error if not. It then applies the conversion rate. The currency markers do nothing else at all. Instead we could have conditionally passed conversionRate if it made sense and use the existence of the conversionRate to determine if it should be applied.
There's also another fun tidbit....
fromDenomination and toDenomination make sense, but what happens if you omit one or the other?
There's nothing majorly wrong with these:
Here, however,
This method converts the currency value to ETH by default if you supply fromDenomination. SO:
v is going to be the value in base 10 (decimal) of ETH. The problem with this is that the contract of it switching to ETH is baked into the code itself and not the props, which would be fine if the name of the method implied this. We do have methods that end in the name 'ToDecETH' which are more widely used for this operation, but under the hood they use conversionUtil. What makes worse is the existence of toDenomination. The existence of toDenomination should imply that not including it would not convert anything. That isn't the case. Furthermore if you use toDenomination without fromDenomination its assumed that the value you were using was in ETH already. How handy :)
Two different rounding operations occur
I do not know why these options are different, but I assume there is a reason. I'm hoping someone will be able to point that out to me in code review.
Problems with the helper methods that use converter/conversionUtil:
They are unnecessarily complex to use/understand
Usage:
So the first thing this method does is check that you supplied a valid 10 or 16 value for aBase and bBase, which uses the same values as the parseInt, toString, etc methods (10, 16) to determine if the string is decimal or hexadecimal. I like that, it makes sense and encourages the understanding of these parameters of those methods. 'hex' and 'dec' I think were used for onboarding/understanding purposes but they just map to 10 and 16 respectfully and you can't use the knowledge of them anywhere else (you'd need to use 10/16 bases for the other methods).
Then it does something peculiar, it uses a helper method 'getBigNumber' and calls it to get the BigNumber representation of the a value then calls '.add' with the BigNumber representation of the B value.
Then it supplies the value to the converter method with all the params you want to pass it that the converter method operates on. So compound all of the issues I wrote about the converter method with the following:
addCurrencies
can you tell what the order of operations is? Does it convert each value then add it together, or does it add it together then convert it. If I supply a conversionRate and fromCurrency/toCurrency what should I expect?Here's another fun example:
And here is the most complex usage I can find in real code:
Here we are attempting to compare a decimal value representing an amount in GWEI to a hex value representing an amount in WEI. The usage here is correct, but we can pass literally any propertiers of the converter to either A or B operand and it just becomes extremely confusing.
TO BE CONTINUED
Solution
This Pull Request implements a new class in TypeScript called 'Numeric' which has a lot of similarities with BigNumber except that it has denomination manipulation built in as well as a helper method for applyConverionRate. Each chaining operator of this new utility returns a new instance of Numeric. So for example:
In this previous example from above the way you'd write the same thing using Numeric:
And this one would be expressed like:
As you can see its much easier to understand because each chain in the operation expresses the order of operations instead of being baked into the application code.
Fixes #16211
Fixes #15463
Fixes #17211
Progresses #6290
Screenshots/Screencaps
Before
After
Manual Testing Steps
Pre-merge author checklist
Pre-merge reviewer checklist
If further QA is required (e.g. new feature, complex testing steps, large refactor), add the
Extension QA Board
label.In this case, a QA Engineer approval will be be required.