0

For example, in the code below, would it be cheaper to store the string in a mapping? Or to convert it to bytes32 and store it as bytes32 then convert it back to a string whenever I call it with JavaScript?

mapping(string => bool) _tokenExists;

function test(string memory _str) public {
    _tokenExists[_str] = true;
}

Or

mapping(bytes32 => bool) _tokenExists;

function test(string memory _str) public {
    _tokenExists[abi.encodePacked(_str)] = true;
}

Which one of these would be cheaper & better to use and why?

PS: Is there a way I can find out on my own for the future which would be cheaper by doing my own gas estimates?

2

First of all, abi.encodePacked(_str) will convert the string _str to its UTF-8 bytes representation, and not to bytes32. bytes is a dynamically-sized byte array, as is string. Since Solidity 0.8.5 it’s possible to cast bytes into bytes32, but you need to pay attention because anything beyond 32 bytes will be chopped off.

So if your string’s UTF-8 bytes representation is 32 bytes or shorter, you can place it in a bytes32 via bytes32(abi.encodePacked(_str)), whereas if your string is longer than 32 bytes, you can compress it into a bytes32 via keccak256(abi.encodePacked(_str)).


From the Solidity docs:

The value corresponding to a mapping key k is located at keccak256(h(k) . p) where . is concatenation and h is a function that is applied to the key depending on its type:

  • for value types, h pads the value to 32 bytes in the same way as when storing the value in memory.
  • for strings and byte arrays, h computes the keccak256 hash of the unpadded data.

p above is the “slot position” for the mapping variable (your _tokenExists), so within a contract it is constant. For this example, suppose that p = 0. So we have:

Type of key k Value slot position
bytes32 keccak256( k . 0 )
bytes or string keccak256( keccak256(abi.encodePacked(k)) . 0 )

So for mappings with bytes or string key-type, accessing a mapping value invokes an extra keccak256. But invoking keccak256 is cheap enough to not let it influence your design decisions. Rather you should use string or bytes32 depending on what is semantically more suitable for your contract.


A simple way to test the theory above “on your own, for the future” is by loading in Remix IDE a test-contract, deploying it to the JavaScript VM Environment, and invoking the different functions with the same input strings, trying both short and long strings, and comparing the different functions’ execution cost, e.g.:

pragma solidity ^0.8.5;

contract Test {
    mapping(string => bool) _tokenExists1;
    function test1(string memory _str) external {
        _tokenExists1[_str] = true;
    }

    mapping(bytes => bool) _tokenExists2;
    function test2(string memory _str) external {
        _tokenExists2[abi.encodePacked(_str)] = true;
    }

    mapping(bytes32 => bool) _tokenExists3;
    function test3(string memory _str) external {
        _tokenExists3[keccak256(abi.encodePacked(_str))] = true;
    }

    mapping(bytes32 => bool) _tokenExists4;
    function test4(string memory _str) external {
        _tokenExists4[bytes32(abi.encodePacked(_str))] = true;
    }
}
3
  • Thank you so much! So you're saying that instead of using a string as the key for a mapping, I should instead do keccak256(abi.encodePacked(string)) to store that string as a bytes32 & save on gas if it's longer than 32 bytes? & do bytes32(abi.encodePacked(string)) to store the string as a key if it's less than or equal to 32 bytes?
    – Adrian D.
    Jun 17 at 4:43
  • Not letting my other comment for some reason. I just ran the functions you gave me and it seems just using a string as a key is the cheapest choice. However, would the best choice be using the front end to pass in a bytes32 value and store it as a bytes32 and not convert it in Solidity?
    – Adrian D.
    Jun 17 at 4:58
  • 1
    When you use string as a mapping key, internally Solidity will still calculate keccak256(abi.encodePacked(key)) every time you access a value (i.e. test1, test2 and test3 are effectively the same), so it’s better to let the Solidity compiler do it for you itself (test1) and keep your code clean. I forgot to include test5 for comparison, where you pass a ready-hashed bytes32 directly yourself… but even if there are any gas differences, they will be small, so if for your application it makes sense to use string keys, then use string keys and keep your Solidity/JS code simple.
    – dwardu
    Jun 17 at 7:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.