# Solidity - The Strings Library

By [Fatih Furkan](https://paragraph.com/@fatih-furkan) · 2022-06-07

---

In this article, we are going to inspect [OpenZeppelin's String library](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Strings.sol). Most of the articles are about core things like oracles, launchpads, tokens, etc. I wanted to write an article about this kind of useful but not too much-used thing. Let’s start!

State Variables
---------------

At the beginning of our contract, we have two state variables:

    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;
    

We will use these variables in our `toHexString` functions.

toString() function
-------------------

This function `Converts a uint256 to its ASCII string decimal representation.`

First of all, let’s look at the function signature:

    function toString(uint256 value) internal pure returns (string memory)
    

*   It takes an uint256 parameter.
    
*   It’s an internal function. So, it can only be accessible in the String contract itself, or a contract inherited from the String contract.
    
*   It’s a pure function. That means it doesn’t read or write from the state.
    
*   And lastly, it returns a string.
    

The method we used in this function does not work with the number 0. So, if the input number is zero, return ”0”:

    if (value == 0) {
      return "0";
    }
    

Next, we are saving our input variable to another variable. We’ll divide it by 10 continuously. Because we want to get how many digits is the input variable and we don’t want to lose the value.

    uint256 temp = value;
    uint256 digits;
    
    while (temp != 0) {
      digits++;
      temp /= 10;
    }
    

Let’s assume that we sent `100,042` to this function. Now, `temp` is equal to `0` , and `digits` is equal to `6`.

Now, we are going to create a `bytes` variable and insert the bytes one by one to it:

    bytes memory buffer = new bytes(digits);
    while (value != 0) {
      digits -= 1;
      buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
      value /= 10;
    }
    return string(buffer);
    

At the beginning `buffer` is equal to `0x000000000000`. And in every loop it changes these:

*   0x000000000032
    
*   0x000000003432
    
*   0x000000303432
    
*   0x000030303432
    
*   0x003030303432
    
*   0x313030303432
    

If you convert every byte to ASCII, you can get `0` for `0x30`, `1` for `31`, `2` for `32`, etc. 30 to 40 are the numbers 0 to 9.

Let’s break down the confusing line:

*   `value % 10` is for getting the least important number.
    
*   `uint8(48 + uint256(value % 10))` nothing much different. Just type conversion to be able to use `bytes1` without losing data.
    
*   `bytes1(uint8(48 + uint256(value % 10)))` now we can add the resulting number to our `buffer` .
    

Let’s run the first loop to understand what is going on in this line:

*   `value % 10` gives us `2`. I am not going to explain to you “what is mod” here. If you don’t know how we are getting `2` from `100,042 % 10` , then just google “what is mod in programming”.
    
*   `uint8(48 + uint256(value % 10))` gives us `50`. You can think: “what the hell is this number?”. And you’ll be right. They used this, because, we are going to use hexadecimal numbers. If you convert `50` to hexadecimal, you’ll get `32`. And `32` is equal to `2` in the ASCII table. Wow! What a conversion, hah?
    
*   `bytes1(uint8(48 + uint256(value % 10)))` conversion is required for our `buffer` , which is a bytes variable.
    

Lastly, we are converting our bytes to string. I love this part. Because if you want to get to result in `uint`, the result is will be a totally different thing. But, we are telling “convert it to string, I want to see its corresponding value in the ASCII table”.

toHexString() function
----------------------

Before we go deep dive into this function, you have to know that: there are 3 different `toHexString` functions in this library. If you don’t know [function overloading](https://docs.soliditylang.org/en/latest/contracts.html#function-overloading), you might be thinking “how can they use 3 functions with the same name?”. So, it’ll be better for you to check out function overloading before continuing this article.

Let’s look at our three functions:

    function toHexString(uint256 value, uint256 length) internal pure returns (string memory)
    

    function toHexString(uint256 value) internal pure returns (string memory)
    

    function toHexString(address addr) internal pure returns (string memory)
    

As you can see, they have the same name, but, they all have a different kinds of parameters. Because of that, their [function selectors](https://docs.soliditylang.org/en/v0.8.12/abi-spec.html#function-selector) are different. So, EVM is good with that.

Please think the functions in order. For example, the number 1, the first function, is the function that takes two `uint256` parameters.

But, you should know that: the second and third functions are calling the first function. It means if you call the second or the third function, your input values are going to the first function at the end of the day.

Enough for talking. Let’s look at the third function’s code:

    return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    

It has only one line of code. And it is converting our `address` input variable to `uint256`. And, send the result with `_ADDRESS_LENGTH`, which is `20`, to our first function.

Let’s convert my address,`0x000000000042bAA586DD7161dC0EB8f0CB4a9fBE` , to `uint256`: `346477235235092042596196240046333886` we’ll get this huge number.

We’ll look at the other steps in a minute when we are talking about the first function.

Okay, now time for our second function. If the input value is `0`, then we are returning `”0x00”`.

    if (value == 0) {
      return "0x00";
    }
    

If it is not zero, then we are going to calculate it’s length:

    uint256 temp = value;
    uint256 length = 0;
    while (temp != 0) {
      length++;
      temp >>= 8;
    }
    

I think only the `temp >>= 8;` line is confusing. Let’s again send to this function our `100,042` number and see what happens in our loop:

*   In our first loop `length` will be 1 and the temp is `390`. What? Ok. Now we have to go to the dark side of coding: the binary world. `100,042` in binary is equal to `11000011011001010` and 390 is equal to `110000110` . Did you notice the similarity between to binaries? Their first (start from left) 8 digits are equal. `>>` means shift bits to right. If you used `<<` this one, the result will be: `25610752` and its binary is `1100001101100101000000000` . Okay, I believe now things are more clear. `>>` deletes bits, `<<` adds zero to binary. I think we can think like that. So, our loop basically calculates the length of our value in bytes (8 bits are equal to 1 byte). We will use the length of this byte in our first function. Let’s go on to other steps.
    
*   `temp’s` value was `110000110` . So, we can delete the least significant 8 bits by our hand (last 8 bits). If we delete them here we have binary `1`, it is equal to decimal `1`. And `length` is equal to `2` now.
    
*   This is the last step for our value. Because in this step our value is going to equal to `0`. And while loop not going to run anymore. In this step `temp` is equal `0` and `length` is equal to `3`. Now, we are sending `100,042` and `length` to the first function.
    

So far so good! Only one function is left, the first function. It starts with defining a bytes variable named `buffer` and its length is equal to `2 * length + 2`. Why is that? It is because every 2 hexadecimal characters are equal to 1 byte. For example, if you want to convert byte `0` to hexadecimal, you'll have `0x00`. Since we are returning a string, we don’t actually have `0x` at the beginning. So, we have to add it manually. I think you got why we are adding `2` to our length, it is because of `0x`.

    bytes memory buffer = new bytes(2 * length + 2);
    buffer[0] = "0";
    buffer[1] = "x";
    

After than, we are going to determine all of our hexadecimal values one by one.

    for (uint256 i = 2 * length + 1; i > 1; --i) {
      buffer[i] = _HEX_SYMBOLS[value & 0xf];
      value >>= 4;
    }
    

We have sent to this function to value, do you remember? One is `(100042, 3)` and the other one is `(0x000000000042bAA586DD7161dC0EB8f0CB4a9fBE, 20)` . Let’s run them step-by-step. First `(100042, 3)` :

*   In this step, we are going to determine `value & 0xf` first. It is a bitwise AND operation. We want to get the last 4 bits We sent `100042` , in binary `11000011011001010`. So, `value & 0xf` gives us `1010`, which is equal to decimal `10`. 10th item in the `_HEX_SYMBOLS` list is equal to `a`. After then we are deleting the least significant 4 bits from `value`. And now we have `1100001101100` , which is equal to decimal `6252`. `buffer` is equal to `0x3078000000000061`. The first 2 bytes `3078` are coming from `0x` that we add before the loop. Do you remember `30` is equal to `0` in the ASCII table, right? The same goes with the `x` and `a`. `78` is equal to `x` in the ASCII table. If you convert the `buffer` to string, you’ll get `0xa`.
    
*   Now we are operating `1100001101100 & 0xf` which gives us decimal `12` and hexadecimal `c`. Buffer is equal to `0x3078000000006361`, in string `0xca`.
    
*   Our number is `110000110` . Again, delete the least significant 4 bits and run the operation bitwise AND. It gives us `0110`. Buffer is equal to `0x3078000000366361` , in string `0x6ca`.
    
*   Our number is `11000` . In this step, we are getting `8` . Now `temp` is equal to `1`. Buffer is `0x3078000038366361`, in string `0x86ca` .
    
*   We get `1` in binary in this step. And it gives us hexadecimal `1`. `temp` is `0`. Buffer is `0x3078003138366361`, in string `0x186ca` .
    
*   `temp` was equal to. Because of that, we are getting a `0` in hexadecimal. Buffer is equal to `0x3078303138366361` , in string `0x0186ca` . And this was the last step.
    

We gave `(100042, 3)` to this function and it returned `0x0186ca`. You can convert these numbers using an online converter and you’ll see the result is true.

Conclusion
----------

I said I also inspect the `346477235235092042596196240046333886` and `20` . But I am not a computer, I am bored doing the same thing over and over. So, you can do it by yourself. Here is the full code of the library that I inspect. I am sharing it because, in the future, it can be changed:

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
        uint8 private constant _ADDRESS_LENGTH = 20;
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            // Inspired by OraclizeAPI's implementation - MIT licence
            // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
    
            if (value == 0) {
                return "0";
            }
            uint256 temp = value;
            uint256 digits;
            while (temp != 0) {
                digits++;
                temp /= 10;
            }
            bytes memory buffer = new bytes(digits);
            while (value != 0) {
                digits -= 1;
                buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                value /= 10;
            }
            return string(buffer);
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            if (value == 0) {
                return "0x00";
            }
            uint256 temp = value;
            uint256 length = 0;
            while (temp != 0) {
                length++;
                temp >>= 8;
            }
            return toHexString(value, length);
        }
    
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _HEX_SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
    
        /**
         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
         */
        function toHexString(address addr) internal pure returns (string memory) {
            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
        }
    }

---

*Originally published on [Fatih Furkan](https://paragraph.com/@fatih-furkan/solidity-the-strings-library)*
