openzeppelin-contracts icon indicating copy to clipboard operation
openzeppelin-contracts copied to clipboard

Optimize `Strings.toString(uint)` implementation

Open 3sGgpQ8H opened this issue 3 years ago • 5 comments

🧐 Motivation The current Strings.toString(uint) implementation is quite inefficient on large inputs.

📝 Details Consider optimizing it as suggested here: https://stackoverflow.com/a/71095692/2038768

3sGgpQ8H avatar Feb 12 '22 20:02 3sGgpQ8H

Thank you @3sGgpQ8H. Can you elaborate on what exactly makes the current implementation inefficient and what is the reason we would see an improvement with the link you shared?

frangio avatar Feb 17 '22 22:02 frangio

Sure.

  1. The current implementation uses linear search to find out the number of digits in the result, while binary search would be much more efficient: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Strings.sol#L22-L27
  2. In the current implementation, the main loop evaluate loop condition after every digit, which is a significant overhead. Unrolling the loop could save much gas.

3sGgpQ8H avatar Feb 19 '22 18:02 3sGgpQ8H

Got it. Agree with the first point. Perhaps we should have a general purpose Math.log10 and use that function here?

For the second point we need to consider that toString returns a string of arbitrary length so I'm not sure how that plays together with the loop unrolling.

As a side note, do you see this function being used on-chain as opposed to off-chain queries?

frangio avatar Feb 19 '22 23:02 frangio

I'm not sure how that plays together with the loop unrolling.

Look at the StackOverflow answer referred in the original post.

As a side note, do you see this function being used on-chain as opposed to off-chain queries?

Surely, its its primary purpose is to be used on-chain. For off-chain use it would be more efficient to return a number and convert to a string off-chain (i.e. in UI). Two quite common on-chain usages are:

  1. Include a number into a signed message whose signature is verified by a smart contract (a message has to be human readable, thus numbers are included in decimal format)
  2. Append a token ID to a ERC-721 NFT URI

3sGgpQ8H avatar Feb 22 '22 10:02 3sGgpQ8H

I hadn't realized the StackOverflow answer was Solidity. :slightly_smiling_face:

Signatures are a decent use case, though a standard like EIP712 doesn't convert them to decimal format. NFT URIs are not normally computed on chain as far as I know.

In any case I think it makes sense to optimize this.

frangio avatar Feb 22 '22 19:02 frangio

Fixed by https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3573 where toString was changed to binary search (see also the new log functions from #3670).

We haven't unrolled the loop. We can consider that if someone wants to open a PR and it shows significant savings.

frangio avatar Sep 16 '22 20:09 frangio