-
-
Notifications
You must be signed in to change notification settings - Fork 188
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
feat(controller-utils): support bn.js v4 input to BN functions (re-introduces #4844) #4886
base: main
Are you sure you want to change the base?
Conversation
New dependencies detected. Learn more about Socket for GitHub ↗︎
|
5dfb95f
to
3ac1af4
Compare
it('fractionBN', () => { | ||
expect(util.fractionBN(new BN('1337'), 9, 10).toNumber()).toBe(1203); | ||
expect(util.fractionBN(new BN4('1337'), 9, 10).toNumber()).toBe(1203); | ||
// Ensure return values use the same bn.js implementation as input by detection using non-typed API | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
expect( | ||
(util.fractionBN(new BN4('1337'), 9, 10) as any)._strip, | ||
).toBeUndefined(); | ||
expect( | ||
(util.fractionBN(new BN4('1337'), 9, 10) as any).strip, | ||
).toBeDefined(); | ||
expect( | ||
(util.fractionBN(new BN('1337'), 9, 10) as any)._strip, | ||
).toBeDefined(); | ||
expect( | ||
(util.fractionBN(new BN('1337'), 9, 10) as any).strip, | ||
).toBeUndefined(); | ||
}); |
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.
Should we split all of these tests out into separate tests so we can give them proper names? Also, can we just test that the right BN instance is returned by using the toBeInstanceOf
matcher?
it('fractionBN', () => { | |
expect(util.fractionBN(new BN('1337'), 9, 10).toNumber()).toBe(1203); | |
expect(util.fractionBN(new BN4('1337'), 9, 10).toNumber()).toBe(1203); | |
// Ensure return values use the same bn.js implementation as input by detection using non-typed API | |
/* eslint-disable @typescript-eslint/no-explicit-any */ | |
expect( | |
(util.fractionBN(new BN4('1337'), 9, 10) as any)._strip, | |
).toBeUndefined(); | |
expect( | |
(util.fractionBN(new BN4('1337'), 9, 10) as any).strip, | |
).toBeDefined(); | |
expect( | |
(util.fractionBN(new BN('1337'), 9, 10) as any)._strip, | |
).toBeDefined(); | |
expect( | |
(util.fractionBN(new BN('1337'), 9, 10) as any).strip, | |
).toBeUndefined(); | |
}); | |
describe('fractionBN', () => { | |
describe('when the first argument is an instance of BN from modern bn.js', () => { | |
it('computes floor(A * B / C), where B and C are numbers', () => { | |
const result = util.fractionBN(new BN('1337'), 9, 10); | |
expect(result.toNumber()).toBe(1203); | |
}); | |
it('computes floor(A * B / C), where B is a string and C is a number', () => { | |
const result = util.fractionBN(new BN('1337'), '9', 10); | |
expect(result.toNumber()).toBe(1203); | |
}); | |
it('computes floor(A * B / C), where B is a number and C is a string', () => { | |
const result = util.fractionBN(new BN('1337'), 9, '10'); | |
expect(result.toNumber()).toBe(1203); | |
}); | |
it('returns the same version of BN as the input', () => { | |
const result = util.fractionBN(new BN('1337'), 9, 10); | |
expect(result).toBeInstanceOf(BN); | |
}); | |
}); | |
describe('when the first argument is an instance of BN from bn.js 4', () => { | |
it('computes floor(A * B / C), where B and C are numbers', () => { | |
const result = util.fractionBN(new BN4('1337'), 9, 10); | |
expect(result.toNumber()).toBe(1203); | |
}); | |
it('computes floor(A * B / C), where B is a string and C is a number', () => { | |
const result = util.fractionBN(new BN4('1337'), '9', 10); | |
expect(result.toNumber()).toBe(1203); | |
}); | |
it('computes floor(A * B / C), where B is a number and C is a string', () => { | |
const result = util.fractionBN(new BN4('1337'), 9, '10'); | |
expect(result.toNumber()).toBe(1203); | |
}); | |
it('returns the same version of BN as the input', () => { | |
const result = util.fractionBN(new BN4('1337'), 9, 10); | |
expect(result).toBeInstanceOf(BN4); | |
}); | |
}); | |
}); |
function getBNImplementation(targetBN: BN4): typeof BN4; | ||
function getBNImplementation(targetBN: BN): typeof BN; | ||
/** | ||
* Return the bn.js library responsible for the BN in question | ||
* @param targetBN - A BN instance | ||
* @returns A bn.js instance | ||
*/ | ||
function getBNImplementation(targetBN: BN | BN4): typeof BN4 | typeof BN { | ||
return Object.keys(targetBN).includes('_strip') ? BN4 : BN; | ||
} |
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.
The JSDoc is in the wrong place here. It should go on each overload, not the implementation:
function getBNImplementation(targetBN: BN4): typeof BN4; | |
function getBNImplementation(targetBN: BN): typeof BN; | |
/** | |
* Return the bn.js library responsible for the BN in question | |
* @param targetBN - A BN instance | |
* @returns A bn.js instance | |
*/ | |
function getBNImplementation(targetBN: BN | BN4): typeof BN4 | typeof BN { | |
return Object.keys(targetBN).includes('_strip') ? BN4 : BN; | |
} | |
/** | |
* Return the constructor of the given BN instance, which in this case is the BN | |
* constructor from bn.js 4. | |
* @param targetBN - A instance of BN from bn.js 4. | |
* @returns The BN constructor from bn.js 4. | |
*/ | |
function getBNImplementation(targetBN: BN4): typeof BN4; | |
/** | |
* Return the constructor of the given BN instance, which in this case is the BN | |
* constructor from bn.js 5+. | |
* @param targetBN - A instance of BN from bn.js 5+. | |
* @returns The BN constructor from bn.js 5+. | |
*/ | |
function getBNImplementation(targetBN: BN): typeof BN; | |
function getBNImplementation(targetBN: BN | BN4): typeof BN4 | typeof BN { | |
return Object.keys(targetBN).includes('_strip') ? BN4 : BN; | |
} |
export function fractionBN( | ||
targetBN: BN, | ||
numerator: number | string, | ||
denominator: number | string, | ||
): BN; | ||
export function fractionBN( | ||
targetBN: BN4, | ||
numerator: number | string, | ||
denominator: number | string, | ||
): BN4; |
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.
Similar as above. The JSDoc should go above each overload.
export function fractionBN( | |
targetBN: BN, | |
numerator: number | string, | |
denominator: number | string, | |
): BN; | |
export function fractionBN( | |
targetBN: BN4, | |
numerator: number | string, | |
denominator: number | string, | |
): BN4; | |
/** | |
* Multiply a BN by a fraction and get a BN back. | |
* | |
* This overload takes an instance of BN from bn.js 5+ and returns another | |
* instance of the same class. | |
* | |
* @param targetBN - Number to multiply by a fraction. | |
* @param numerator - Numerator of the fraction multiplier. | |
* @param denominator - Denominator of the fraction multiplier. | |
* @returns Product of the multiplication. | |
*/ | |
export function fractionBN( | |
targetBN: BN, | |
numerator: number | string, | |
denominator: number | string, | |
): BN; | |
/** | |
* Multiply a BN by a fraction and get a BN back. | |
* | |
* This overload takes an instance of BN from bn.js 4 and returns another | |
* instance of the same class. | |
* | |
* @param targetBN - Number to multiply by a fraction. | |
* @param numerator - Numerator of the fraction multiplier. | |
* @param denominator - Denominator of the fraction multiplier. | |
* @returns Product of the multiplication. | |
*/ | |
export function fractionBN( | |
targetBN: BN4, | |
numerator: number | string, | |
denominator: number | string, | |
): BN4; |
targetBN: BN4, | ||
numerator: number | string, | ||
denominator: number | string, | ||
): BN4; | ||
/** |
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 JSDoc is not necessary (but I can't suggest removing it because the diff crosses over into unchanged lines).
// @ts-expect-error - Signature overload confusion | ||
const BNImplementation = getBNImplementation(targetBN); | ||
|
||
const numBN = new BNImplementation(numerator); | ||
const denomBN = new BNImplementation(denominator); | ||
// @ts-expect-error - BNImplementation gets unexpected typed | ||
return targetBN.mul(numBN).div(denomBN); |
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'm not sure we need the ts-expect-error
s. There are two code paths here. We need to tell TypeScript which one to go down, it cannot figure that out itself.
Also, looking at this more closely, I am unsure why we need the getBNImplementation
function. Why can't we just use instanceof
? It seems like this would work just as well:
// @ts-expect-error - Signature overload confusion | |
const BNImplementation = getBNImplementation(targetBN); | |
const numBN = new BNImplementation(numerator); | |
const denomBN = new BNImplementation(denominator); | |
// @ts-expect-error - BNImplementation gets unexpected typed | |
return targetBN.mul(numBN).div(denomBN); | |
if (targetBN instanceof BN4) { | |
const numBN = new BN4(numerator); | |
const denomBN = new BN4(denominator); | |
return targetBN.mul(numBN).div(denomBN); | |
} | |
const numBN = new BN(numerator); | |
const denomBN = new BN(denominator); | |
return targetBN.mul(numBN).div(denomBN); |
export function fromHex(value: string | BN): BN; | ||
export function fromHex(value: BN4): BN4; | ||
/** | ||
* Parses a hex string and converts it into a number that can be operated on in a bignum-safe, | ||
* base-10 way. | ||
* | ||
* @param value - A base-16 number encoded as a string. | ||
* @returns The number as a BN object in base-16 mode. | ||
*/ | ||
export function fromHex(value: string | BN): BN { | ||
export function fromHex(value: string | BN | BN4): BN | BN4 { |
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.
Same story here with the JSDoc. How about:
export function fromHex(value: string | BN): BN; | |
export function fromHex(value: BN4): BN4; | |
/** | |
* Parses a hex string and converts it into a number that can be operated on in a bignum-safe, | |
* base-10 way. | |
* | |
* @param value - A base-16 number encoded as a string. | |
* @returns The number as a BN object in base-16 mode. | |
*/ | |
export function fromHex(value: string | BN): BN { | |
export function fromHex(value: string | BN | BN4): BN | BN4 { | |
/** | |
* Converts a number into a representation that can be safely operated on in a | |
* bignum-safe way. | |
* | |
* In this case the number is already in that representation. | |
* | |
* @param value - A BN instance from modern bn.js. | |
* @returns The same BN instance. | |
*/ | |
export function fromHex(value: BN): BN; | |
/** | |
* Converts a base-16 number encoded as a hex string into a base-10 | |
* representation that can be safely operated on in a bignum-safe way. | |
* | |
* @param value - A hex string, or a BN instance from bn.js. | |
* @returns The number as a BN instance from modern bn.js in base-10 mode. | |
*/ | |
export function fromHex(value: string): BN; | |
/** | |
* Converts a number into a representation that can be safely operated on in a | |
* bignum-safe way. | |
* | |
* In this case the number is already in that representation. | |
* | |
* @param value - A BN instance from bn.js 4. | |
* @returns The same BN instance. | |
*/ | |
export function fromHex(value: BN4): BN4; |
if (BN.isBN(value)) { | ||
return value; | ||
} | ||
return new BN(hexToBN(value).toString(10)); | ||
return new BN(hexToBN(value as string).toString(10), 10); |
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.
The type-cast here doesn't seem right. TypeScript thinks that value
is a string | BN4
.
BN.isBN
is a type guard that coerces value
to BN
. It seems like we need a comparable check for BN4
:
if (BN.isBN(value)) { | |
return value; | |
} | |
return new BN(hexToBN(value).toString(10)); | |
return new BN(hexToBN(value as string).toString(10), 10); | |
if (BN.isBN(value)) { | |
return value; | |
} | |
if (BN4.isBN(value)) { | |
return value; | |
} | |
return new BN(hexToBN(value).toString(10)); |
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 doesn't seem the changes to fromHex
or toHex
are being tested. Can we test those? (Happy to add them to this branch.)
This PR re-introduces the changes that were originally merged as part of #4844. They were reverted due to an incompatibility with
@metamask/create-release-branch
v3, which was resolved with the update to v3.1.References:
@metamask/create-release-branch
support fornpm:
aliases: feat: allownpm:name@version
dependency redirections in manifest create-release-branch#158@metamask/create-release-branch
v3.1 release with backported alias support: 3.1.0 create-release-branch#160@metamask/create-release-branch
bump to v3.1: chore: Bump@metamask/create-release-branch
from v3.0 to v3.1 #4884Original description of #4844:
Explanation
BNToHex
fractionBN
fromHex
toHex
fractionBN
now uses the original BN implementation when passed a v4 BN instanceReferences
bn.js
is imported downstream. Making this support explicit facilitates resolving the type issue without requiring a prior full migration off bn.js v4 or resorting to peppering ts-expect-errors all over.Changelog
@metamask/controller-utils
bn.js
v4 library:BNToHex
fractionBN
fromHex
toHex
fractionToBN
now returns output using the samebn.js
library version that created the inputChecklist