Ethers.js: Feature request: option to change the big number library

Created on 28 Nov 2019  路  5Comments  路  Source: ethers-io/ethers.js

When interacting with any contract fields with ethers, I get a result that looks like this:

tokenBalance: v
  _hex: "0x033b2b2062ddfc98de7fffff"
  _ethersType: "BigNumber

After enquiring on StackExchange, I now understand the ethers is piggybacking off the bn.js library, but in my codebase I use bignumber.js, so I have to extract the _hex property from the ethers value and make a conversion.

It would be nice to have a global config where one can select what kind of big number the ethers library should return. Afaik, the most popular libraries are bn.js and bignumber.js, and they would probably be sufficient for many users.

discussion

Most helpful comment

Heya!

First off, that is a BigNumber object; you never need to (nor should) access any _property. They should be considered internal, and in v6 will likely be wrapped in a Weak Map and completely inaccessible. You should be able to just use v.toString() for base-10 string or v.toHexString() for a hex-prefixed hex string; the former can easily be passed into your Big Number library of choice. You should not rely on _hex always existing, which brings me to point 1. :)

1) A main reason to abstract the BN.js number is in case in the future I wish to remove it as a dependency, swap it out with something else, or provide an alternate underlying implementation. In the case that no ECC library is needed, the Big Number library can be greatly reduced in size.

2) Keep in mind that BigNumber is used extensively inside the ethers.js library. The reason to provide this one is that it provides the necessary features required internally. If it could be swapped out, I would have to do checks everywhere to determine if I should call .mul or .times, or whatever other possible options exist on various libraries. This doesn't even begin to scratch the surface of features other Big Number libraries do not have (for example, .nmask, .toTwos and .fromTwos). When the Signer calls estimateGas and getGasPrice, it knows it can .mul them together to get the fee.

3) This would break the ability for multiple libraries to use ethers safely within the same project. If you pull in ethers, and Takoyaki (which uses ethers) and your own code, and change the global Big Number library, then the Takoyaki library will fail all over the place internally, since it cannot call functions it expects to exist but have slightly different names or slightly different behaviour.

4) Immutability; the main reason for this implementation, which cannot be enforced against arbitrary (or even BN.js and BigNumber.js) Big Number libraries. This greatly simplifies a lot of code, but more importantly creates substantially safer and less error-prone code. Imagine passing a mutable value into a Contract; it would be quite easy a mistake to not create a deep copy of the value, and if an outside reference changes the value between various stages (like gas estimation and signing) could lead to unexpected results.

Generally allowing these "global" type of configuration makes incredibly brittle code that can no longer be used by frameworks, or by frameworks of frameworks, or even multiple libraries depending on the same library.

The BigNumber class is exposed though, so you could easily add ethers.BigNumber.prototype.toMyBigNumber = function() { return new BigNumberLib(this.toString()); }.

All 5 comments

Heya!

First off, that is a BigNumber object; you never need to (nor should) access any _property. They should be considered internal, and in v6 will likely be wrapped in a Weak Map and completely inaccessible. You should be able to just use v.toString() for base-10 string or v.toHexString() for a hex-prefixed hex string; the former can easily be passed into your Big Number library of choice. You should not rely on _hex always existing, which brings me to point 1. :)

1) A main reason to abstract the BN.js number is in case in the future I wish to remove it as a dependency, swap it out with something else, or provide an alternate underlying implementation. In the case that no ECC library is needed, the Big Number library can be greatly reduced in size.

2) Keep in mind that BigNumber is used extensively inside the ethers.js library. The reason to provide this one is that it provides the necessary features required internally. If it could be swapped out, I would have to do checks everywhere to determine if I should call .mul or .times, or whatever other possible options exist on various libraries. This doesn't even begin to scratch the surface of features other Big Number libraries do not have (for example, .nmask, .toTwos and .fromTwos). When the Signer calls estimateGas and getGasPrice, it knows it can .mul them together to get the fee.

3) This would break the ability for multiple libraries to use ethers safely within the same project. If you pull in ethers, and Takoyaki (which uses ethers) and your own code, and change the global Big Number library, then the Takoyaki library will fail all over the place internally, since it cannot call functions it expects to exist but have slightly different names or slightly different behaviour.

4) Immutability; the main reason for this implementation, which cannot be enforced against arbitrary (or even BN.js and BigNumber.js) Big Number libraries. This greatly simplifies a lot of code, but more importantly creates substantially safer and less error-prone code. Imagine passing a mutable value into a Contract; it would be quite easy a mistake to not create a deep copy of the value, and if an outside reference changes the value between various stages (like gas estimation and signing) could lead to unexpected results.

Generally allowing these "global" type of configuration makes incredibly brittle code that can no longer be used by frameworks, or by frameworks of frameworks, or even multiple libraries depending on the same library.

The BigNumber class is exposed though, so you could easily add ethers.BigNumber.prototype.toMyBigNumber = function() { return new BigNumberLib(this.toString()); }.

The last point is a neat trick. Although ethers.js BigNumber is exposed under utils so it'd be

ethers.utils.BigNumber.prototype.toMyBigNumber = function() { return new BigNumberLib(this.toString()); }.

@naddison36 Oh yes, sorry. I鈥檓 so used to using v5 these days, I forget. Yes, in v4 it is ethers.utils.BigNumber, in v5 ethers.BigNumber. :)

Thanks @ricmoo for the in-depth, well-crafted answer. I now understand the rationale behind your design choice of using a bespoke big number type - I think this is something worthy to be mentioned (as a warning?) in the docs.

The toMyBigNumber/ toString workaround works great for me and I'll change my code to not use _hex anymore.

I'm in the process of updating the docs now and will add more info to the new "Why Big Numbers?" section.

I'll close this, but please feel free to re-open if you have any further questions. Or continue commenting, I monitor closed issues.

Thanks! :)

Was this page helpful?
0 / 5 - 0 ratings