CircularBuffer.sol 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.20;
  3. import {Math} from "../math/Math.sol";
  4. import {Arrays} from "../Arrays.sol";
  5. import {Panic} from "../Panic.sol";
  6. /**
  7. * @dev A fixed-size buffer for keeping `bytes32` items in storage.
  8. *
  9. * This data structure allows for pushing elements to it, and when its length exceeds the specified fixed size,
  10. * new items take the place of the oldest element in the buffer, keeping at most `N` elements in the
  11. * structure.
  12. *
  13. * Elements can't be removed but the data structure can be cleared. See {clear}.
  14. *
  15. * Complexity:
  16. * - insertion ({push}): O(1)
  17. * - lookup ({last}): O(1)
  18. * - inclusion ({includes}): O(N) (worst case)
  19. * - reset ({clear}): O(1)
  20. *
  21. * * The struct is called `Bytes32CircularBuffer`. Other types can be cast to and from `bytes32`. This data structure
  22. * can only be used in storage, and not in memory.
  23. *
  24. * Example usage:
  25. *
  26. * ```solidity
  27. * contract Example {
  28. * // Add the library methods
  29. * using CircularBuffer for CircularBuffer.Bytes32CircularBuffer;
  30. *
  31. * // Declare a buffer storage variable
  32. * CircularBuffer.Bytes32CircularBuffer private myBuffer;
  33. * }
  34. * ```
  35. *
  36. * _Available since v5.1._
  37. */
  38. library CircularBuffer {
  39. /**
  40. * @dev Error emitted when trying to setup a buffer with a size of 0.
  41. */
  42. error InvalidBufferSize();
  43. /**
  44. * @dev Counts the number of items that have been pushed to the buffer. The residuo modulo _data.length indicates
  45. * where the next value should be stored.
  46. *
  47. * Struct members have an underscore prefix indicating that they are "private" and should not be read or written to
  48. * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and
  49. * lead to unexpected behavior.
  50. *
  51. * In a full buffer:
  52. * - The most recently pushed item (last) is at data[(index - 1) % data.length]
  53. * - The oldest item (first) is at data[index % data.length]
  54. */
  55. struct Bytes32CircularBuffer {
  56. uint256 _count;
  57. bytes32[] _data;
  58. }
  59. /**
  60. * @dev Initialize a new CircularBuffer of given size.
  61. *
  62. * If the CircularBuffer was already setup and used, calling that function again will reset it to a blank state.
  63. *
  64. * NOTE: The size of the buffer will affect the execution of {includes} function, as it has a complexity of O(N).
  65. * Consider a large buffer size may render the function unusable.
  66. */
  67. function setup(Bytes32CircularBuffer storage self, uint256 size) internal {
  68. if (size == 0) revert InvalidBufferSize();
  69. clear(self);
  70. Arrays.unsafeSetLength(self._data, size);
  71. }
  72. /**
  73. * @dev Clear all data in the buffer without resetting memory, keeping the existing size.
  74. */
  75. function clear(Bytes32CircularBuffer storage self) internal {
  76. self._count = 0;
  77. }
  78. /**
  79. * @dev Push a new value to the buffer. If the buffer is already full, the new value replaces the oldest value in
  80. * the buffer.
  81. */
  82. function push(Bytes32CircularBuffer storage self, bytes32 value) internal {
  83. uint256 index = self._count++;
  84. uint256 modulus = self._data.length;
  85. Arrays.unsafeAccess(self._data, index % modulus).value = value;
  86. }
  87. /**
  88. * @dev Number of values currently in the buffer. This value is 0 for an empty buffer, and cannot exceed the size of
  89. * the buffer.
  90. */
  91. function count(Bytes32CircularBuffer storage self) internal view returns (uint256) {
  92. return Math.min(self._count, self._data.length);
  93. }
  94. /**
  95. * @dev Length of the buffer. This is the maximum number of elements kept in the buffer.
  96. */
  97. function length(Bytes32CircularBuffer storage self) internal view returns (uint256) {
  98. return self._data.length;
  99. }
  100. /**
  101. * @dev Getter for the i-th value in the buffer, from the end.
  102. *
  103. * Reverts with {Panic-ARRAY_OUT_OF_BOUNDS} if trying to access an element that was not pushed, or that was
  104. * dropped to make room for newer elements.
  105. */
  106. function last(Bytes32CircularBuffer storage self, uint256 i) internal view returns (bytes32) {
  107. uint256 index = self._count;
  108. uint256 modulus = self._data.length;
  109. uint256 total = Math.min(index, modulus); // count(self)
  110. if (i >= total) {
  111. Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
  112. }
  113. return Arrays.unsafeAccess(self._data, (index - i - 1) % modulus).value;
  114. }
  115. /**
  116. * @dev Check if a given value is in the buffer.
  117. */
  118. function includes(Bytes32CircularBuffer storage self, bytes32 value) internal view returns (bool) {
  119. uint256 index = self._count;
  120. uint256 modulus = self._data.length;
  121. uint256 total = Math.min(index, modulus); // count(self)
  122. for (uint256 i = 0; i < total; ++i) {
  123. if (Arrays.unsafeAccess(self._data, (index - i - 1) % modulus).value == value) {
  124. return true;
  125. }
  126. }
  127. return false;
  128. }
  129. }