Checkpoints.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. const format = require('../format-lines');
  2. const VALUE_SIZES = [ 224, 160 ];
  3. const header = `\
  4. pragma solidity ^0.8.0;
  5. import "./math/Math.sol";
  6. import "./math/SafeCast.sol";
  7. /**
  8. * @dev This library defines the \`History\` struct, for checkpointing values as they change at different points in
  9. * time, and later looking up past values by block number. See {Votes} as an example.
  10. *
  11. * To create a history of checkpoints define a variable type \`Checkpoints.History\` in your contract, and store a new
  12. * checkpoint for the current transaction block using the {push} function.
  13. *
  14. * _Available since v4.5._
  15. */
  16. `;
  17. const types = opts => `\
  18. struct ${opts.historyTypeName} {
  19. ${opts.checkpointTypeName}[] ${opts.checkpointFieldName};
  20. }
  21. struct ${opts.checkpointTypeName} {
  22. ${opts.keyTypeName} ${opts.keyFieldName};
  23. ${opts.valueTypeName} ${opts.valueFieldName};
  24. }
  25. `;
  26. /* eslint-disable max-len */
  27. const operations = opts => `\
  28. /**
  29. * @dev Pushes a (\`key\`, \`value\`) pair into a ${opts.historyTypeName} so that it is stored as the checkpoint.
  30. *
  31. * Returns previous value and new value.
  32. */
  33. function push(
  34. ${opts.historyTypeName} storage self,
  35. ${opts.keyTypeName} key,
  36. ${opts.valueTypeName} value
  37. ) internal returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
  38. return _insert(self.${opts.checkpointFieldName}, key, value);
  39. }
  40. /**
  41. * @dev Returns the value in the oldest checkpoint with key greater or equal than the search key, or zero if there is none.
  42. */
  43. function lowerLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) {
  44. uint256 len = self.${opts.checkpointFieldName}.length;
  45. uint256 pos = _lowerBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len);
  46. return pos == len ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos).${opts.valueFieldName};
  47. }
  48. /**
  49. * @dev Returns the value in the most recent checkpoint with key lower or equal than the search key.
  50. */
  51. function upperLookup(${opts.historyTypeName} storage self, ${opts.keyTypeName} key) internal view returns (${opts.valueTypeName}) {
  52. uint256 len = self.${opts.checkpointFieldName}.length;
  53. uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len);
  54. return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName};
  55. }
  56. `;
  57. const legacyOperations = opts => `\
  58. /**
  59. * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one
  60. * before it is returned, or zero otherwise.
  61. */
  62. function getAtBlock(${opts.historyTypeName} storage self, uint256 blockNumber) internal view returns (uint256) {
  63. require(blockNumber < block.number, "Checkpoints: block not yet mined");
  64. uint32 key = SafeCast.toUint32(blockNumber);
  65. uint256 len = self.${opts.checkpointFieldName}.length;
  66. uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, 0, len);
  67. return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName};
  68. }
  69. /**
  70. * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one
  71. * before it is returned, or zero otherwise. Similar to {upperLookup} but optimized for the case when the searched
  72. * checkpoint is probably "recent", defined as being among the last sqrt(N) checkpoints where N is the number of
  73. * checkpoints.
  74. */
  75. function getAtProbablyRecentBlock(${opts.historyTypeName} storage self, uint256 blockNumber) internal view returns (uint256) {
  76. require(blockNumber < block.number, "Checkpoints: block not yet mined");
  77. uint32 key = SafeCast.toUint32(blockNumber);
  78. uint256 len = self.${opts.checkpointFieldName}.length;
  79. uint256 low = 0;
  80. uint256 high = len;
  81. if (len > 5) {
  82. uint256 mid = len - Math.sqrt(len);
  83. if (key < _unsafeAccess(self.${opts.checkpointFieldName}, mid)._blockNumber) {
  84. high = mid;
  85. } else {
  86. low = mid + 1;
  87. }
  88. }
  89. uint256 pos = _upperBinaryLookup(self.${opts.checkpointFieldName}, key, low, high);
  90. return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName};
  91. }
  92. /**
  93. * @dev Returns checkpoint at given position.
  94. */
  95. function getAtPosition(History storage self, uint32 pos) internal view returns (Checkpoint memory) {
  96. return self._checkpoints[pos];
  97. }
  98. /**
  99. * @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block.
  100. *
  101. * Returns previous value and new value.
  102. */
  103. function push(${opts.historyTypeName} storage self, uint256 value) internal returns (uint256, uint256) {
  104. return _insert(self.${opts.checkpointFieldName}, SafeCast.toUint32(block.number), SafeCast.toUint224(value));
  105. }
  106. /**
  107. * @dev Pushes a value onto a History, by updating the latest value using binary operation \`op\`. The new value will
  108. * be set to \`op(latest, delta)\`.
  109. *
  110. * Returns previous value and new value.
  111. */
  112. function push(
  113. ${opts.historyTypeName} storage self,
  114. function(uint256, uint256) view returns (uint256) op,
  115. uint256 delta
  116. ) internal returns (uint256, uint256) {
  117. return push(self, op(latest(self), delta));
  118. }
  119. `;
  120. const common = opts => `\
  121. /**
  122. * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints.
  123. */
  124. function latest(${opts.historyTypeName} storage self) internal view returns (${opts.valueTypeName}) {
  125. uint256 pos = self.${opts.checkpointFieldName}.length;
  126. return pos == 0 ? 0 : _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1).${opts.valueFieldName};
  127. }
  128. /**
  129. * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value
  130. * in the most recent checkpoint.
  131. */
  132. function latestCheckpoint(${opts.historyTypeName} storage self)
  133. internal
  134. view
  135. returns (
  136. bool exists,
  137. ${opts.keyTypeName} ${opts.keyFieldName},
  138. ${opts.valueTypeName} ${opts.valueFieldName}
  139. )
  140. {
  141. uint256 pos = self.${opts.checkpointFieldName}.length;
  142. if (pos == 0) {
  143. return (false, 0, 0);
  144. } else {
  145. ${opts.checkpointTypeName} memory ckpt = _unsafeAccess(self.${opts.checkpointFieldName}, pos - 1);
  146. return (true, ckpt.${opts.keyFieldName}, ckpt.${opts.valueFieldName});
  147. }
  148. }
  149. /**
  150. * @dev Returns the number of checkpoint.
  151. */
  152. function length(${opts.historyTypeName} storage self) internal view returns (uint256) {
  153. return self.${opts.checkpointFieldName}.length;
  154. }
  155. /**
  156. * @dev Pushes a (\`key\`, \`value\`) pair into an ordered list of checkpoints, either by inserting a new checkpoint,
  157. * or by updating the last one.
  158. */
  159. function _insert(
  160. ${opts.checkpointTypeName}[] storage self,
  161. ${opts.keyTypeName} key,
  162. ${opts.valueTypeName} value
  163. ) private returns (${opts.valueTypeName}, ${opts.valueTypeName}) {
  164. uint256 pos = self.length;
  165. if (pos > 0) {
  166. // Copying to memory is important here.
  167. ${opts.checkpointTypeName} memory last = _unsafeAccess(self, pos - 1);
  168. // Checkpoints keys must be increasing.
  169. require(last.${opts.keyFieldName} <= key, "Checkpoint: invalid key");
  170. // Update or push new checkpoint
  171. if (last.${opts.keyFieldName} == key) {
  172. _unsafeAccess(self, pos - 1).${opts.valueFieldName} = value;
  173. } else {
  174. self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: value}));
  175. }
  176. return (last.${opts.valueFieldName}, value);
  177. } else {
  178. self.push(${opts.checkpointTypeName}({${opts.keyFieldName}: key, ${opts.valueFieldName}: value}));
  179. return (0, value);
  180. }
  181. }
  182. /**
  183. * @dev Return the index of the oldest checkpoint whose key is greater than the search key, or \`high\` if there is none.
  184. * \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and exclusive \`high\`.
  185. *
  186. * WARNING: \`high\` should not be greater than the array's length.
  187. */
  188. function _upperBinaryLookup(
  189. ${opts.checkpointTypeName}[] storage self,
  190. ${opts.keyTypeName} key,
  191. uint256 low,
  192. uint256 high
  193. ) private view returns (uint256) {
  194. while (low < high) {
  195. uint256 mid = Math.average(low, high);
  196. if (_unsafeAccess(self, mid).${opts.keyFieldName} > key) {
  197. high = mid;
  198. } else {
  199. low = mid + 1;
  200. }
  201. }
  202. return high;
  203. }
  204. /**
  205. * @dev Return the index of the oldest checkpoint whose key is greater or equal than the search key, or \`high\` if there is none.
  206. * \`low\` and \`high\` define a section where to do the search, with inclusive \`low\` and exclusive \`high\`.
  207. *
  208. * WARNING: \`high\` should not be greater than the array's length.
  209. */
  210. function _lowerBinaryLookup(
  211. ${opts.checkpointTypeName}[] storage self,
  212. ${opts.keyTypeName} key,
  213. uint256 low,
  214. uint256 high
  215. ) private view returns (uint256) {
  216. while (low < high) {
  217. uint256 mid = Math.average(low, high);
  218. if (_unsafeAccess(self, mid).${opts.keyFieldName} < key) {
  219. low = mid + 1;
  220. } else {
  221. high = mid;
  222. }
  223. }
  224. return high;
  225. }
  226. function _unsafeAccess(${opts.checkpointTypeName}[] storage self, uint256 pos)
  227. private
  228. pure
  229. returns (${opts.checkpointTypeName} storage result)
  230. {
  231. assembly {
  232. mstore(0, self.slot)
  233. result.slot := add(keccak256(0, 0x20), pos)
  234. }
  235. }
  236. `;
  237. /* eslint-enable max-len */
  238. // OPTIONS
  239. const defaultOpts = (size) => ({
  240. historyTypeName: `Trace${size}`,
  241. checkpointTypeName: `Checkpoint${size}`,
  242. checkpointFieldName: '_checkpoints',
  243. keyTypeName: `uint${256 - size}`,
  244. keyFieldName: '_key',
  245. valueTypeName: `uint${size}`,
  246. valueFieldName: '_value',
  247. });
  248. const OPTS = VALUE_SIZES.map(size => defaultOpts(size));
  249. const LEGACY_OPTS = {
  250. ...defaultOpts(224),
  251. historyTypeName: 'History',
  252. checkpointTypeName: 'Checkpoint',
  253. keyFieldName: '_blockNumber',
  254. };
  255. // GENERATE
  256. module.exports = format(
  257. header.trimEnd(),
  258. 'library Checkpoints {',
  259. [
  260. // Legacy types & functions
  261. types(LEGACY_OPTS),
  262. legacyOperations(LEGACY_OPTS),
  263. common(LEGACY_OPTS),
  264. // New flavors
  265. ...OPTS.flatMap(opts => [
  266. types(opts),
  267. operations(opts),
  268. common(opts),
  269. ]),
  270. ],
  271. '}',
  272. );