ethers.js icon indicating copy to clipboard operation
ethers.js copied to clipboard

option to keep padding for quantities

Open passabilities opened this issue 1 year ago • 5 comments

Allows to optionally keep the leading 0s when converting a value to a Quantity.

passabilities avatar Oct 31 '23 19:10 passabilities

That’s what hexlify does; it doesn’t remove leading 0’s. A “quantity” type forbids leading 0’s, which is the purpose of that function, but if you use hexlify it will retain the zeros.

What do you need this functionality for?

ricmoo avatar Oct 31 '23 19:10 ricmoo

for instance when a number needs to be represented by all 32 bytes but there are leading 0s in the hex number take this example:

> num = BigInt('0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d')
1937035142596246788172577232054709726386880441279550832067530347910661804397n
> ethers.hexlify(num)
Uncaught:
TypeError: invalid BytesLike value (argument="value", value=1937035142596246788172577232054709726386880441279550832067530347910661804397, code=INVALID_ARGUMENT, version=6.8.0)
    at makeError (./node_modules/ethers/lib.commonjs/utils/errors.js:122:21)
    at assert (./node_modules/ethers/lib.commonjs/utils/errors.js:149:15)
    at assertArgument (./node_modules/ethers/lib.commonjs/utils/errors.js:161:5)
    at _getBytes (./node_modules/ethers/lib.commonjs/utils/data.js:27:36)
    at getBytes (./node_modules/ethers/lib.commonjs/utils/data.js:37:12)
    at Object.hexlify (./node_modules/ethers/lib.commonjs/utils/data.js:84:19) {
  code: 'INVALID_ARGUMENT',
  argument: 'value',
  value: 1937035142596246788172577232054709726386880441279550832067530347910661804397n,
  shortMessage: 'invalid BytesLike value'
}
> ethers.toQuantity(num)
'0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d'
  • hexlify throws an error because its an odd number of bytes (leading 0)
  • toQuantity strips the 0 making the number an odd number of bytes

I need all 32 bytes to be populated when converting an ENS token ID from a decimal number into a hex which represents the token's labelhash

this is not trivial to ensure I have the correct value but becomes annoying when you need to remember this 1 edge case and reproduce the following code to work:

    const q = ethers.toQuantity(opts.tokenId)
    const labelhash = ethers.zeroPadValue(q.length % 2 ? `0x0${q.substring(2)}` : q, 32)
    const url = 'https://api.thegraph.com/subgraphs/name/ensdomains/ens'
    const res: { registrations: [ { domain: { name: string } } | undefined ] } = await request(url, gql`
        query {
            registrations(
              where: { domain_: { labelhash: "${labelhash}" } }
            ) { domain { name } }
        }`,
    )

passabilities avatar Nov 03 '23 21:11 passabilities

iirc in v5 hexlify did not throw an error and did exactly want I wanted

passabilities avatar Nov 03 '23 21:11 passabilities

In v5, hexlify accepted a numeric type; the equivalent in v6 is toBeArray, to be more explicit about how input types should be processed.

It also isn’t safe to use v5 hexlify to get a 32-byte value from a bigint; it might usually work out (because of how hashing works, often having non-zero values in high-order bits), but if for example the hex string were to begin with 0x000 the final string would be only 31 bytes wide.

For now, I think you would want zeroPadValue(toBeArray(value), 32) to ensure you get a 32 byte (64 nibble) hex string.

It may make sense for zeroPadValue to accept a BigInt or number as well, though.

ricmoo avatar Nov 03 '23 22:11 ricmoo

You can also use abiCoder.encode([ "uint" ], [ value ]). Whatever best conveys the intention of the code, is best. :)

ricmoo avatar Nov 03 '23 22:11 ricmoo