// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Math} from "../math/Math.sol"; import {Arrays} from "../Arrays.sol"; import {Panic} from "../Panic.sol"; /** * @dev A fixed-size buffer for keeping `bytes32` items in storage. * * This data structure allows for pushing elements to it, and when its length exceeds the specified fixed size, * new items take the place of the oldest element in the buffer, keeping at most `N` elements in the * structure. * * Elements can't be removed but the data structure can be cleared. See {clear}. * * Complexity: * - insertion ({push}): O(1) * - lookup ({last}): O(1) * - inclusion ({includes}): O(N) (worst case) * - reset ({clear}): O(1) * * * The struct is called `Bytes32CircularBuffer`. Other types can be cast to and from `bytes32`. This data structure * can only be used in storage, and not in memory. * * Example usage: * * ```solidity * contract Example { * // Add the library methods * using CircularBuffer for CircularBuffer.Bytes32CircularBuffer; * * // Declare a buffer storage variable * CircularBuffer.Bytes32CircularBuffer private myBuffer; * } * ``` */ library CircularBuffer { /** * @dev Counts the number of items that have been pushed to the buffer. The residuo modulo _data.length indicates * where the next value should be stored. * * Struct members have an underscore prefix indicating that they are "private" and should not be read or written to * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and * lead to unexpected behavior. * * The last item is at data[(index - 1) % data.length] and the last item is at data[index % data.length]. This * range can wrap around. */ struct Bytes32CircularBuffer { uint256 _count; bytes32[] _data; } /** * @dev Initialize a new CircularBuffer of given size. * * If the CircularBuffer was already setup and used, calling that function again will reset it to a blank state. * * NOTE: The size of the buffer will affect the execution of {includes} function, as it has a complexity of O(N). * Consider a large buffer size may render the function unusable. */ function setup(Bytes32CircularBuffer storage self, uint256 size) internal { clear(self); Arrays.unsafeSetLength(self._data, size); } /** * @dev Clear all data in the buffer without resetting memory, keeping the existing size. */ function clear(Bytes32CircularBuffer storage self) internal { self._count = 0; } /** * @dev Push a new value to the buffer. If the buffer is already full, the new value replaces the oldest value in * the buffer. */ function push(Bytes32CircularBuffer storage self, bytes32 value) internal { uint256 index = self._count++; uint256 modulus = self._data.length; Arrays.unsafeAccess(self._data, index % modulus).value = value; } /** * @dev Number of values currently in the buffer. This value is 0 for an empty buffer, and cannot exceed the size of * the buffer. */ function count(Bytes32CircularBuffer storage self) internal view returns (uint256) { return Math.min(self._count, self._data.length); } /** * @dev Length of the buffer. This is the maximum number of elements kepts in the buffer. */ function length(Bytes32CircularBuffer storage self) internal view returns (uint256) { return self._data.length; } /** * @dev Getter for the i-th value in the buffer, from the end. * * Reverts with {Panic-ARRAY_OUT_OF_BOUNDS} if trying to access an element that was not pushed, or that was * dropped to make room for newer elements. */ function last(Bytes32CircularBuffer storage self, uint256 i) internal view returns (bytes32) { uint256 index = self._count; uint256 modulus = self._data.length; uint256 total = Math.min(index, modulus); // count(self) if (i >= total) { Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS); } return Arrays.unsafeAccess(self._data, (index - i - 1) % modulus).value; } /** * @dev Check if a given value is in the buffer. */ function includes(Bytes32CircularBuffer storage self, bytes32 value) internal view returns (bool) { uint256 index = self._count; uint256 modulus = self._data.length; uint256 total = Math.min(index, modulus); // count(self) for (uint256 i = 0; i < total; ++i) { if (Arrays.unsafeAccess(self._data, (index - i - 1) % modulus).value == value) { return true; } } return false; } }