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 mapping
s 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;
}
}