-
-
Notifications
You must be signed in to change notification settings - Fork 226
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
Migrate from JavaScript to TypeScript #74
Conversation
I just found out from #68 that there used to be a typings declaration file for this project, but was removed in that PR. I think I'd be able to get some ideas from that file. |
@danfinlay I'm pinging you now because the I am not sure how to proceed from here -- arguably the test file should stringize the object type first (by way of hashing I suppose?), but I can see that it would also make sense for Secondly, I am not sure what the purpose of the I will push a commit on my current work on attempting to type |
Thanks for the quick investigation, @KiChjang. I think it's very likely that the typefile is just bad, and we may need to disregard a lot of it. I definitely think using an object as params is plainly superior, and we should refer to the testfile and the sig-examples repo as more realistic reference points instead when possible. As for TypedUtils.sign, it seems clear it does no signing, it is clearly actually a hashing method. I believe it was broken out so that the hashing functionality could be tested independently. As some bug fixes show up along the way, I am happy to add to the bounty, but it is tricky to price fixes along the way fairly, so feel free to suggest what would keep you feeling motivated, and if we agree to continue, we will pay out, and if not, we will simply pay you the bounty for identifying additional issues and complexity that need to be addressed before the conversion can be easily completed. |
@danfinlay I forgot to mention -- the type mismatches that I'm seeing right now is preventing the testfile to pass. I think I will change the behavior of certain functions a slight bit (e.g. asserting that With this PR, I believe behaviorally, nothing will change in this library (aside from the slight modifications I mentioned earlier, which I think is inconsequential because other functions would have thrown an exception if there is a type mismatch), so we could simply merge it and file follow-up bug issues and place a bounty on them accordingly. |
The testfile has an interesting testcase where it references a variable called |
Yes, removing broken tests like that is fine, I'll open an issue for adding a correct test for that case. |
@danfinlay Let me know whether the current state on this PR is good to go. |
Rebased to master. |
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.
@KiChjang this is a great start! I've left a few comments, I think we might need to refine a few of these types to avoid using never
and some of the casts in the test file.
package.json
Outdated
@@ -29,6 +31,8 @@ | |||
"tweetnacl-util": "^0.15.0" | |||
}, | |||
"devDependencies": { | |||
"tape": "^4.9.1" | |||
"tape": "^4.9.1", | |||
"ts-node": "^8.5.4", |
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 believe we can avoid this dependency by compiling the test file as well and executing the generated JS file directly. (Related to my comments on the tsconfig below.)
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.
We can add the test directory to .include
in the tscconfig file.
tsconfig.json
Outdated
@@ -0,0 +1,10 @@ | |||
{ | |||
"compilerOptions": { | |||
"outDir": "./build", |
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.
Let's leave this property out and output the JS files alongside the TS files—that produces the exact same package structure as before, allowing us to not have to modify the publish process.
package.json
Outdated
@@ -4,7 +4,8 @@ | |||
"description": "A few useful functions for signing ethereum data", | |||
"main": "index.js", | |||
"scripts": { | |||
"test": "node test/index.js" | |||
"build": "tsc -d", | |||
"test": "npm run build && ts-node test/index.ts" |
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.
"test": "npm run build && ts-node test/index.ts" | |
"test": "npm run build && node test/index.js" |
package.json
Outdated
"tape": "^4.9.1" | ||
"tape": "^4.9.1", | ||
"ts-node": "^8.5.4", | ||
"typescript": "3.4.3" |
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.
Can we use the latest version of TypeScript here?
package.json
Outdated
@@ -4,7 +4,8 @@ | |||
"description": "A few useful functions for signing ethereum data", | |||
"main": "index.js", | |||
"scripts": { | |||
"test": "node test/index.js" | |||
"build": "tsc -d", |
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.
"build": "tsc -d", | |
"build": "tsc --project .", |
Let's use the --project
flag here and keep all the compiler options in the tsconfig file. (We'll need to add "declaration": true
to .compilerOptions
there.)
test/index.ts
Outdated
@@ -41,11 +41,11 @@ test('personalSign and recover', function (t) { | |||
const privKeyHex = '4af1bceebf7f3634ec3cff8a2c38e51178d5d4ce585c52d6043e5e2cc3418bb0' | |||
const privKey = Buffer.from(privKeyHex, 'hex') | |||
const message = 'Hello, world!' | |||
const msgParams = { data: message } | |||
const msgParams: sigUtil.MsgParams<never> = { data: message } |
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.
Why are we using the never
type here? The never
type doesn't make sense to me, semantically. We might have some of these types wrong if we need this?
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.
Since TypedData<T>
is a union type of string | EIP712TypedData | EIP712TypedData[] | TypedMessage<T>
, the never
type here signifies that we know for sure that the data
field will never be of the TypedMessage<T>
type, so it's safe to use never
.
I don't particularly like this syntax either, so I'm open to suggestions on improving this.
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.
so it's safe to use
never
I don't contest that it's safe to use never
here, I guess I'm more wondering if we can avoid using it in favour of something more semantic? I'll spend a bit more time thinking of an alternative.
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 have an idea -- instead of trying to parameterize on the TypedData
type, we can instead parameterize on the API functions themselves, i.e. make TypedData
parameter-less, and then on the functions, require MsgParams<TypedData | TypedMessage<T>>
.
test/index.ts
Outdated
@@ -453,7 +447,7 @@ test('signedTypeData', (t) => { | |||
{ name: 'contents', type: 'string' } | |||
], | |||
}, | |||
primaryType: 'Mail', | |||
primaryType: 'Mail' as 'Mail', |
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.
Why do we need to cast this? primaryType
should be a string, no? There are a few instances of @param {string} primaryType
in the JSDoc comments and TYPED_MESSAGE_SCHEMA
defines it as a string?
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.
Similarly for all of the below casts?
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.
primaryType
should be a string, no?
Not exactly. This is the type definition of TypedMessage
:
export interface TypedMessage<T extends MessageTypes> {
types: T;
primaryType: keyof T;
// ...
}
In other words, the type of primaryType
should only consist of the keys that appear in types
. I could have made this string
instead, but I figured out that we'd probably want stronger typing guarantees, and what's the point of having a type system if we can't leverage it? ;)
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.
Yes, fair, I'm definitely in favour of stronger type guarantees. That said, this cast seems unnecessary—how can we best remove the need for it?
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.
As I recall, this is currently a limitation of TypeScript, which doesn't allow object fields to be narrowed down to their literal types; perhaps this is the tracking issue? microsoft/TypeScript#16896
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.
A marginally better way would be to use as const
instead of as 'Mail'
here, but it's purely cosmetic.
package.json
Outdated
@@ -21,6 +22,7 @@ | |||
}, | |||
"homepage": "https://github.com/MetaMask/eth-sig-util#readme", | |||
"dependencies": { | |||
"@types/node": "^13.1.2", |
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.
Can we downgrade this to ^10
(refs #77)
Hmm... should I EDIT: Nevermind, I think I will do just that. |
- Create necessary project configuration files to support TypeScript - Annotate every function parameter and return value with its proper type
@whymarrh Please have a look, I believe all your review comments are adequately addressed. |
@KiChjang thanks I'll have some time later today to look at this |
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 LGTM, the types definitely make a bit more sense now
@danfinlay Looks like we have an OK from a reviewer, is there anything else left in this PR that you'd like me to address, or is it good to go? |
@rekmarks want to double-check this as well |
@whymarrh I'll review over the course of the weekend. One initial thought is that we should get some linting up in here. I think there's an internal discussion to be had to determine our TypeScript lint rules (e.g. gaba is doing stuff I don't care for), so let's continue to not worry about style in this PR? |
Yeah let's ignore styling for this PR, once this is merged we can iterate on the project (linting, docs, etc.) |
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.
Thanks @KiChjang!
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.
Fixed the build 😅 thanks again
Fixes #59. Fixes #70.
As I feared initially, there are some difficulties when trying to type some parameters and values correctly.
msgParams.data
in particular is quite problematic -- I've come to understand it as a union between an object and a string according to usage, but the function body does not seem to indicate that it properly handles the cases where it is a string type, and instead assumes that it's always an object.I'm creating a draft pull request now just to have more eyes looking at what I've managed to convert. Currently it cannot be compiled by the TypeScript compiler due to the aforementioned problems I pointed out above.