ShortStrings.sol 4.1 KB

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