Apparently, Javascript has 64-bit numbers, and 1 ether has 10^18 wei. So when doing computations in wei, I ran into this limit. 1000 ether became "1e+21" instead of 1 with 21 zeroes, and so web3.utils were not able to process it any longer.
> web3.utils.fromWei(""+(web3.utils.toWei('1', 'ether')*'990'), 'ether')
'990'
> web3.utils.fromWei(""+(web3.utils.toWei('1', 'ether')*'999'), 'ether')
'999'
> web3.utils.fromWei(""+(web3.utils.toWei('1', 'ether')*'1000'), 'ether')
Error: [number-to-bn] while converting number "1e+21" to BN.js instance, error: invalid number value. Value must be an integer, hex string, BN or BigNumber instance. Note, decimals are not supported.
at numberToBN (/Users/fracoon/projects/eth/sco/node_modules/number-to-bn/src/index.js:37:9)
at Object.fromWei (/Users/fracoon/projects/eth/sco/node_modules/ethjs-unit/lib/index.js:78:13)
at Object.fromWei (/Users/fracoon/projects/eth/sco/node_modules/web3-utils/src/index.js:199:77)
> web3.version
'1.0.0-beta.26'
Workaround: just write 1e-18*amount or 1e18*amount instead. Works great.
The error you see is expected behavior. The way you are using web3.utils.toWei is incorrect. it is relying on some convenient casting that it is working at all.
Rather than write
web3.utils.toWei('1', 'ether')*'990'
You should write
web3.utils.toWei('990', 'ether')
Which, you'll find works fine, even for large values, because it relies on BN.js underneath:
web3.utils.toWei('10000', 'ether')
What you have written is to convert 1 ether to wei, (which results in 1e18, stored as a BN.js instance), then multiply it by the string "990". BN times a string is not defined, so one of those values has to be cast -- in fact, what happens is they both get cast to different types: numbers.
When that happens, you lose the benefit of the BN, which is to store numbers that are too large to reliably represent in Javascript numbers. This is where this goes bad; that value becomes 1e21, which is too large and casts back to a string for your fromWei call as the string "1e+21", which is not a number.
If you're going to work in BN land, you should do everything you can to stay there.
Now I understand, as to why things work this way!
I don't fully appreciate this expected behavior.
According to the documentation, both fromWei and toWei can take a number/string/BN and return either a string, or a BN. The very code you suggested,
web3.utils.toWei('990', 'ether')
is not "staying in the BN land", because '990' is a string.
What is my usecase? My usecase is for the user to purchase 990 tokens, each at the price of 1 ether per token. I was following the idea that it's better to multiply integers than to multiply floats, since it's more robust. I also thought that using library functions, such as fromWei and toWei, is more robust than doing manual multiplication.
Apparently, I was mistaken: at a very realistic amount of 990 ether (for an ICO, anyway), I've got an error that I found unintuitive and hard to anticipate: I'd have to carefully watch out the type of every single variable I am using. It seems simpler to me to just avoid fromWei/toWei completely, as I am not saving that much effort anyway.
Perhaps, fixing toWei/fromWei to make them understand strings like "1e+21" is not the answer, or perhaps it is. I do think that there's a problem, that it's hard to write bug-free code doing ether/wei computations in JavaScript. The existence of the functions called "fromWei" and "toWei" suggested to me that the issue has been fully understood, but now I see that it's not the case. That is the point of my ticket.
Perhaps the ticket can be renamed, "tricky to code reliable ether/wei computations at amounts approaching 1000 ether".
You could do something like this. I think it is intuitive? For clarity, you may wish to move the new BN call somewhere else, but ensure you have done it _before_ you try to do math with anything expressed in wei.
const numberOfTokens = 990; // from user input.
const pricePerToken = web3.utils.toWei('1', 'ether'); // from configuration?
// Note: toWei does not promise it will return a BN, so we forcefully convert it.
const tokenPrice = new web3.utils.BN(pricePerToken).mul(numberOfTokens);
fellowing @gburtini's comment, I got another error:
RangeError: Invalid array length
at BN.mul (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3-utils/~/bn.js/lib/bn.js:1875:1)
at module.exports (/Users/fur/Projects/Block42/cubik-contracts/migrations/2_deploy_contracts.js:16:53)
at Require.file (/usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-migrate/migration.js:61:1)
at /usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-require/require.js:101:1
at FSReqWrap.readFileAfterClose [as oncomplete] (internal/fs/read_file_context.js:53:3)
.mul() should has BN as input too, so the third line should be:
const tokenPrice = new web3.utils.BN(pricePerToken).mul(new web3.utils.BN(numberOfTokens));
Thanks for the comments. So, how about, converting 1e+21 (amount in wei) back to 1000?
web3.utils.fromWei(), it says the same error. So, any good way to do that?
Thanks again.
Okey, I got it working after converting it into BN() as following:
web3.utils.fromWei(web3.utils.toBN(updatedBalance).toString() )
//which results as: 1000
Most helpful comment
You could do something like this. I think it is intuitive? For clarity, you may wish to move the
new BNcall somewhere else, but ensure you have done it _before_ you try to do math with anything expressed inwei.