ShortStrings.sol 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v4.9.0) (utils/ShortStrings.sol)
  3. pragma solidity ^0.8.20;
  4. import {StorageSlot} from "./StorageSlot.sol";
  5. // | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
  6. // | length | 0x BB |
  7. type ShortString is bytes32;
  8. /**
  9. * @dev This library provides functions to convert short memory strings
  10. * into a `ShortString` type that can be used as an immutable variable.
  11. *
  12. * Strings of arbitrary length can be optimized using this library if
  13. * they are short enough (up to 31 bytes) by packing them with their
  14. * length (1 byte) in a single EVM word (32 bytes). Additionally, a
  15. * fallback mechanism can be used for every other case.
  16. *
  17. * Usage example:
  18. *
  19. * ```solidity
  20. * contract Named {
  21. * using ShortStrings for *;
  22. *
  23. * ShortString private immutable _name;
  24. * string private _nameFallback;
  25. *
  26. * constructor(string memory contractName) {
  27. * _name = contractName.toShortStringWithFallback(_nameFallback);
  28. * }
  29. *
  30. * function name() external view returns (string memory) {
  31. * return _name.toStringWithFallback(_nameFallback);
  32. * }
  33. * }
  34. * ```
  35. */
  36. library ShortStrings {
  37. // Used as an identifier for strings longer than 31 bytes.
  38. bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;
  39. error StringTooLong(string str);
  40. error InvalidShortString();
  41. /**
  42. * @dev Encode a string of at most 31 chars into a `ShortString`.
  43. *
  44. * This will trigger a `StringTooLong` error is the input string is too long.
  45. */
  46. function toShortString(string memory str) internal pure returns (ShortString) {
  47. bytes memory bstr = bytes(str);
  48. if (bstr.length > 31) {
  49. revert StringTooLong(str);
  50. }
  51. return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));
  52. }
  53. /**
  54. * @dev Decode a `ShortString` back to a "normal" string.
  55. */
  56. function toString(ShortString sstr) internal pure returns (string memory) {
  57. uint256 len = byteLength(sstr);
  58. // using `new string(len)` would work locally but is not memory safe.
  59. string memory str = new string(32);
  60. /// @solidity memory-safe-assembly
  61. assembly {
  62. mstore(str, len)
  63. mstore(add(str, 0x20), sstr)
  64. }
  65. return str;
  66. }
  67. /**
  68. * @dev Return the length of a `ShortString`.
  69. */
  70. function byteLength(ShortString sstr) internal pure returns (uint256) {
  71. uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;
  72. if (result > 31) {
  73. revert InvalidShortString();
  74. }
  75. return result;
  76. }
  77. /**
  78. * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.
  79. */
  80. function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {
  81. if (bytes(value).length < 32) {
  82. return toShortString(value);
  83. } else {
  84. StorageSlot.getStringSlot(store).value = value;
  85. return ShortString.wrap(FALLBACK_SENTINEL);
  86. }
  87. }
  88. /**
  89. * @dev Decode a string that was encoded to `ShortString` or written to storage using {setWithFallback}.
  90. */
  91. function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {
  92. if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
  93. return toString(value);
  94. } else {
  95. return store;
  96. }
  97. }
  98. /**
  99. * @dev Return the length of a string that was encoded to `ShortString` or written to storage using
  100. * {setWithFallback}.
  101. *
  102. * WARNING: This will return the "byte length" of the string. This may not reflect the actual length in terms of
  103. * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.
  104. */
  105. function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {
  106. if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {
  107. return byteLength(value);
  108. } else {
  109. return bytes(store).length;
  110. }
  111. }
  112. }