SlotDerivation.js 3.4 KB

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