SlotDerivation.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. const format = require('../format-lines');
  2. const sanitize = require('../helpers/sanitize');
  3. const { TYPES } = require('./Slot.opts');
  4. const header = `\
  5. pragma solidity ^0.8.20;
  6. /**
  7. * @dev Library for computing storage (and transient storage) locations from namespaces and deriving slots
  8. * corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by
  9. * the solidity language / compiler.
  10. *
  11. * See https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays[Solidity docs for mappings and dynamic arrays.].
  12. *
  13. * Example usage:
  14. * \`\`\`solidity
  15. * contract Example {
  16. * // Add the library methods
  17. * using StorageSlot for bytes32;
  18. * using SlotDerivation for bytes32;
  19. *
  20. * // Declare a namespace
  21. * string private constant _NAMESPACE = "<namespace>"; // eg. OpenZeppelin.Slot
  22. *
  23. * function setValueInNamespace(uint256 key, address newValue) internal {
  24. * _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue;
  25. * }
  26. *
  27. * function getValueInNamespace(uint256 key) internal view returns (address) {
  28. * return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value;
  29. * }
  30. * }
  31. * \`\`\`
  32. *
  33. * TIP: Consider using this library along with {StorageSlot}.
  34. *
  35. * NOTE: This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking
  36. * upgrade safety will ignore the slots accessed through this library.
  37. *
  38. * _Available since v5.1._
  39. */
  40. `;
  41. const namespace = `\
  42. /**
  43. * @dev Derive an ERC-7201 slot from a string (namespace).
  44. */
  45. function erc7201Slot(string memory namespace) internal pure returns (bytes32 slot) {
  46. assembly ("memory-safe") {
  47. mstore(0x00, sub(keccak256(add(namespace, 0x20), mload(namespace)), 1))
  48. slot := and(keccak256(0x00, 0x20), not(0xff))
  49. }
  50. }
  51. `;
  52. const array = `\
  53. /**
  54. * @dev Add an offset to a slot to get the n-th element of a structure or an array.
  55. */
  56. function offset(bytes32 slot, uint256 pos) internal pure returns (bytes32 result) {
  57. unchecked {
  58. return bytes32(uint256(slot) + pos);
  59. }
  60. }
  61. /**
  62. * @dev Derive the location of the first element in an array from the slot where the length is stored.
  63. */
  64. function deriveArray(bytes32 slot) internal pure returns (bytes32 result) {
  65. assembly ("memory-safe") {
  66. mstore(0x00, slot)
  67. result := keccak256(0x00, 0x20)
  68. }
  69. }
  70. `;
  71. const mapping = ({ type }) => `\
  72. /**
  73. * @dev Derive the location of a mapping element from the key.
  74. */
  75. function deriveMapping(bytes32 slot, ${type} key) internal pure returns (bytes32 result) {
  76. assembly ("memory-safe") {
  77. mstore(0x00, ${(sanitize[type] ?? (x => x))('key')})
  78. mstore(0x20, slot)
  79. result := keccak256(0x00, 0x40)
  80. }
  81. }
  82. `;
  83. const mapping2 = ({ type }) => `\
  84. /**
  85. * @dev Derive the location of a mapping element from the key.
  86. */
  87. function deriveMapping(bytes32 slot, ${type} memory key) internal pure returns (bytes32 result) {
  88. assembly ("memory-safe") {
  89. let length := mload(key)
  90. let begin := add(key, 0x20)
  91. let end := add(begin, length)
  92. let cache := mload(end)
  93. mstore(end, slot)
  94. result := keccak256(begin, add(length, 0x20))
  95. mstore(end, cache)
  96. }
  97. }
  98. `;
  99. // GENERATE
  100. module.exports = format(
  101. header.trimEnd(),
  102. 'library SlotDerivation {',
  103. format(
  104. [].concat(
  105. namespace,
  106. array,
  107. TYPES.map(type => (type.isValueType ? mapping(type) : mapping2(type))),
  108. ),
  109. ).trimEnd(),
  110. '}',
  111. );