diff --git a/src/contracts/core/AVSDirectory.sol b/src/contracts/core/AVSDirectory.sol index 4d3645bd5..1f471e50c 100644 --- a/src/contracts/core/AVSDirectory.sol +++ b/src/contracts/core/AVSDirectory.sol @@ -17,7 +17,7 @@ contract AVSDirectory is ReentrancyGuardUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; - using MagnitudeCheckpoints for MagnitudeCheckpoints.History; + using Checkpoints for Checkpoints.History; /// @dev Index for flag that pauses operator register/deregister to avs when set. uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0; @@ -364,7 +364,7 @@ contract AVSDirectory is slashedMagnitude = uint64(uint256(bipsToSlash) * uint256(currentMagnitude) / BIPS_FACTOR); _magnitudeUpdate[operator][strategies[i]][msg.sender][operatorSetId].decrementAtAndFutureCheckpoints({ - timestamp: uint32(block.timestamp), + key: uint32(block.timestamp), decrementValue: slashedMagnitude }); } @@ -392,7 +392,7 @@ contract AVSDirectory is // 3. update totalMagnitude, get total magnitude and subtract slashedMagnitude and slashedFromDeallocation _totalMagnitudeUpdate[operator][strategies[i]].push({ - timestamp: uint32(block.timestamp), + key: uint32(block.timestamp), value: _totalMagnitudeUpdate[operator][strategies[i]].latest() - slashedMagnitude - slashedFromDeallocation }); } @@ -522,7 +522,7 @@ contract AVSDirectory is // 3. allocate magnitude which will take effect in the future 21 days from now _magnitudeUpdate[operator][strategy][operatorSets[i].avs][operatorSets[i].operatorSetId].push({ - timestamp: effectTimestamp, + key: effectTimestamp, value: value + uint224(allocation.magnitudeDiffs[i]) }); // 4. keep track of available freeMagnitude to update later @@ -566,7 +566,7 @@ contract AVSDirectory is // 2. update and decrement current and future queued amounts in case any pending allocations exist _magnitudeUpdate[operator][strategy][operatorSets[i].avs][operatorSets[i].operatorSetId] .decrementAtAndFutureCheckpoints({ - timestamp: uint32(block.timestamp), + key: uint32(block.timestamp), decrementValue: deallocation.magnitudeDiffs[i] }); @@ -685,6 +685,26 @@ contract AVSDirectory is || registrationStatus.lastDeregisteredTimestamp + ALLOCATION_DELAY >= block.timestamp; } + // /** + // * @notice fetches the minimum slashable shares for a certain operator and operatorSet for a list of strategies + // * from the current timestamp until the given timestamp + // * + // * @param operator the operator to get the minimum slashable shares for + // * @param operatorSet the operatorSet to get the minimum slashable shares for + // * @param strategies the strategies to get the minimum slashable shares for + // * @param timestamp the timestamp to the minimum slashable shares before + // * + // * @dev used to get the slashable stakes of operators to weigh over a given slashability window + // * + // * @return the list of share amounts for each strategy + // */ + // function getMinimumSlashableSharesBefore( + // address operator, + // OperatorSet calldata operatorSet, + // IStrategy[] calldata strategies, + // uint32 timestamp + // ) external view returns (uint256[] calldata) {} + /** * @notice Calculates the digest hash to be signed by an operator to register with an AVS. * diff --git a/src/contracts/core/AVSDirectoryStorage.sol b/src/contracts/core/AVSDirectoryStorage.sol index 66f0d2243..0c54c28bd 100644 --- a/src/contracts/core/AVSDirectoryStorage.sol +++ b/src/contracts/core/AVSDirectoryStorage.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.12; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "../interfaces/IAVSDirectory.sol"; -import {MagnitudeCheckpoints} from "../libraries/MagnitudeCheckpoints.sol"; +import {Checkpoints} from "../libraries/Checkpoints.sol"; abstract contract AVSDirectoryStorage is IAVSDirectory { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -59,14 +59,14 @@ abstract contract AVSDirectoryStorage is IAVSDirectory { /// @notice Mapping: operator => strategy => checkpointed totalMagnitude /// Note that totalMagnitude is monotonically decreasing and only gets updated upon slashing - mapping(address => mapping(IStrategy => MagnitudeCheckpoints.History)) internal _totalMagnitudeUpdate; + mapping(address => mapping(IStrategy => Checkpoints.History)) internal _totalMagnitudeUpdate; /// @notice Mapping: operator => strategy => free available magnitude that can be allocated to operatorSets /// Decrements whenever allocations take place and increments when deallocations are completed mapping(address => mapping(IStrategy => uint64)) public freeMagnitude; /// @notice Mapping: operator => strategy => avs => operatorSetId => checkpointed magnitude - mapping(address => mapping(IStrategy => mapping(address => mapping(uint32 => MagnitudeCheckpoints.History)))) + mapping(address => mapping(IStrategy => mapping(address => mapping(uint32 => Checkpoints.History)))) internal _magnitudeUpdate; /// @notice Mapping: operator => strategy => avs => operatorSetId => queuedDeallocations diff --git a/src/contracts/libraries/MagnitudeCheckpoints.sol b/src/contracts/libraries/Checkpoints.sol similarity index 55% rename from src/contracts/libraries/MagnitudeCheckpoints.sol rename to src/contracts/libraries/Checkpoints.sol index f0f3a7a45..539290f4c 100644 --- a/src/contracts/libraries/MagnitudeCheckpoints.sol +++ b/src/contracts/libraries/Checkpoints.sol @@ -6,10 +6,10 @@ import "@openzeppelin-upgrades-v4.9.0/contracts/utils/math/MathUpgradeable.sol"; import "@openzeppelin-upgrades-v4.9.0/contracts/utils/math/SafeCastUpgradeable.sol"; /** - * @title Library for handling checkpointed magnitudes as part of allocating and slashing. + * @title Library for handling checkpoints as part of allocating and slashing. * @notice This library is using OpenZeppelin's CheckpointsUpgradeable library (v4.9.0) * and removes structs and functions that are unessential. - * Interfaces and structs are renamed for clarity and usage (magnitudes, timestamps, etc). + * Interfaces and structs are renamed for clarity and usage (timestamps, etc). * Some additional functions have also been added for convenience. * @dev This library defines the `History` struct, for checkpointing values as they change at different points in * time, and later looking up past values by block number. See {Votes} as an example. @@ -19,87 +19,87 @@ import "@openzeppelin-upgrades-v4.9.0/contracts/utils/math/SafeCastUpgradeable.s * * _Available since v4.5._ */ -library MagnitudeCheckpoints { +library Checkpoints { struct History { - MagnitudeCheckpoint[] _magnitudes; + Checkpoint[] _checkpoints; } - struct MagnitudeCheckpoint { - uint32 _timestamp; + struct Checkpoint { + uint32 _key; uint224 _value; } /** - * @dev Pushes a (`timestamp`, `value`) pair into a History so that it is stored as the checkpoint. + * @dev Pushes a (`key`, `value`) pair into a History so that it is stored as the checkpoint. * * Returns previous value and new value. */ - function push(History storage self, uint32 timestamp, uint224 value) internal returns (uint224, uint224) { - return _insert(self._magnitudes, timestamp, value); + function push(History storage self, uint32 key, uint224 value) internal returns (uint224, uint224) { + return _insert(self._checkpoints, key, value); } /** - * @dev Returns the value in the first (oldest) checkpoint with timestamp greater or equal than the search timestamp, or zero if there is none. + * @dev Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if there is none. */ - function lowerLookup(History storage self, uint32 timestamp) internal view returns (uint224) { - uint256 len = self._magnitudes.length; - uint256 pos = _lowerBinaryLookup(self._magnitudes, timestamp, 0, len); - return pos == len ? 0 : _unsafeAccess(self._magnitudes, pos)._value; + function lowerLookup(History storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + uint256 pos = _lowerBinaryLookup(self._checkpoints, key, 0, len); + return pos == len ? 0 : _unsafeAccess(self._checkpoints, pos)._value; } /** - * @dev Returns the value in the last (most recent) checkpoint with timestamp lower or equal than the search timestamp, or zero if there is none. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. */ - function upperLookup(History storage self, uint32 timestamp) internal view returns (uint224) { - uint256 len = self._magnitudes.length; - uint256 pos = _upperBinaryLookup(self._magnitudes, timestamp, 0, len); - return pos == 0 ? 0 : _unsafeAccess(self._magnitudes, pos - 1)._value; + function upperLookup(History storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; + uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len); + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; } /** - * @dev Returns the value in the last (most recent) checkpoint with timestamp lower or equal than the search timestamp, or zero if there is none. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. * - * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high timestamps). + * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys). */ - function upperLookupRecent(History storage self, uint32 timestamp) internal view returns (uint224) { - uint256 len = self._magnitudes.length; + function upperLookupRecent(History storage self, uint32 key) internal view returns (uint224) { + uint256 len = self._checkpoints.length; uint256 low = 0; uint256 high = len; if (len > 5) { uint256 mid = len - MathUpgradeable.sqrt(len); - if (timestamp < _unsafeAccess(self._magnitudes, mid)._timestamp) { + if (key < _unsafeAccess(self._checkpoints, mid)._key) { high = mid; } else { low = mid + 1; } } - uint256 pos = _upperBinaryLookup(self._magnitudes, timestamp, low, high); + uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high); - return pos == 0 ? 0 : _unsafeAccess(self._magnitudes, pos - 1)._value; + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; } /** * @dev Returns the value in the most recent checkpoint, or zero if there are no checkpoints. */ function latest(History storage self) internal view returns (uint224) { - uint256 pos = self._magnitudes.length; - return pos == 0 ? 0 : _unsafeAccess(self._magnitudes, pos - 1)._value; + uint256 pos = self._checkpoints.length; + return pos == 0 ? 0 : _unsafeAccess(self._checkpoints, pos - 1)._value; } /** - * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the timestamp and value + * @dev Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value * in the most recent checkpoint. */ - function latestCheckpoint(History storage self) internal view returns (bool exists, uint32 _timestamp, uint224 _value) { - uint256 pos = self._magnitudes.length; + function latestCheckpoint(History storage self) internal view returns (bool exists, uint32 _key, uint224 _value) { + uint256 pos = self._checkpoints.length; if (pos == 0) { return (false, 0, 0); } else { - MagnitudeCheckpoint memory ckpt = _unsafeAccess(self._magnitudes, pos - 1); - return (true, ckpt._timestamp, ckpt._value); + Checkpoint memory ckpt = _unsafeAccess(self._checkpoints, pos - 1); + return (true, ckpt._key, ckpt._value); } } @@ -107,51 +107,51 @@ library MagnitudeCheckpoints { * @dev Returns the number of checkpoint. */ function length(History storage self) internal view returns (uint256) { - return self._magnitudes.length; + return self._checkpoints.length; } /** - * @dev Pushes a (`timestamp`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, + * @dev Pushes a (`key`, `value`) pair into an ordered list of checkpoints, either by inserting a new checkpoint, * or by updating the last one. */ - function _insert(MagnitudeCheckpoint[] storage self, uint32 timestamp, uint224 value) private returns (uint224, uint224) { + function _insert(Checkpoint[] storage self, uint32 key, uint224 value) private returns (uint224, uint224) { uint256 pos = self.length; if (pos > 0) { // Copying to memory is important here. - MagnitudeCheckpoint memory last = _unsafeAccess(self, pos - 1); + Checkpoint memory last = _unsafeAccess(self, pos - 1); - // Checkpoint timestamps must be non-decreasing. - require(last._timestamp <= timestamp, "Checkpoint: decreasing timestamps"); + // Checkpoint keys must be non-decreasing. + require(last._key <= key, "Checkpoint: decreasing keys"); // Update or push new checkpoint - if (last._timestamp == timestamp) { + if (last._key == key) { _unsafeAccess(self, pos - 1)._value = value; } else { - self.push(MagnitudeCheckpoint({_timestamp: timestamp, _value: value})); + self.push(Checkpoint({_key: key, _value: value})); } return (last._value, value); } else { - self.push(MagnitudeCheckpoint({_timestamp: timestamp, _value: value})); + self.push(Checkpoint({_key: key, _value: value})); return (0, value); } } /** - * @dev Return the index of the last (most recent) checkpoint with timestamp lower or equal than the search timestamp, or `high` if there is none. + * @dev Return the index of the last (most recent) checkpoint with key lower or equal than the search key, or `high` if there is none. * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. * * WARNING: `high` should not be greater than the array's length. */ function _upperBinaryLookup( - MagnitudeCheckpoint[] storage self, - uint32 timestamp, + Checkpoint[] storage self, + uint32 key, uint256 low, uint256 high ) private view returns (uint256) { while (low < high) { uint256 mid = MathUpgradeable.average(low, high); - if (_unsafeAccess(self, mid)._timestamp > timestamp) { + if (_unsafeAccess(self, mid)._key > key) { high = mid; } else { low = mid + 1; @@ -161,20 +161,20 @@ library MagnitudeCheckpoints { } /** - * @dev Return the index of the first (oldest) checkpoint with timestamp is greater or equal than the search timestamp, or `high` if there is none. + * @dev Return the index of the first (oldest) checkpoint with key is greater or equal than the search key, or `high` if there is none. * `low` and `high` define a section where to do the search, with inclusive `low` and exclusive `high`. * * WARNING: `high` should not be greater than the array's length. */ function _lowerBinaryLookup( - MagnitudeCheckpoint[] storage self, - uint32 timestamp, + Checkpoint[] storage self, + uint32 key, uint256 low, uint256 high ) private view returns (uint256) { while (low < high) { uint256 mid = MathUpgradeable.average(low, high); - if (_unsafeAccess(self, mid)._timestamp < timestamp) { + if (_unsafeAccess(self, mid)._key < key) { low = mid + 1; } else { high = mid; @@ -187,9 +187,9 @@ library MagnitudeCheckpoints { * @dev Access an element of the array without performing bounds check. The position is assumed to be within bounds. */ function _unsafeAccess( - MagnitudeCheckpoint[] storage self, + Checkpoint[] storage self, uint256 pos - ) private pure returns (MagnitudeCheckpoint storage result) { + ) private pure returns (Checkpoint storage result) { assembly { mstore(0, self.slot) result.slot := add(keccak256(0, 0x20), pos) @@ -203,64 +203,64 @@ library MagnitudeCheckpoints { */ /** - * @dev Returns the value in the last (most recent) checkpoint with timestamp lower or equal than the search timestamp, or zero if there is none. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. * In addition, returns the position of the checkpoint in the array. * * NOTE: That if value != 0 && pos == 0, then that means the value is the first checkpoint and actually exists * a checkpoint DNE iff value == 0 && pos == 0 */ - function upperLookupWithPos(History storage self, uint32 timestamp) internal view returns (uint224, uint256) { - uint256 len = self._magnitudes.length; - uint256 pos = _upperBinaryLookup(self._magnitudes, timestamp, 0, len); - return pos == 0 ? (0, 0) : (_unsafeAccess(self._magnitudes, pos - 1)._value, pos - 1); + function upperLookupWithPos(History storage self, uint32 key) internal view returns (uint224, uint256) { + uint256 len = self._checkpoints.length; + uint256 pos = _upperBinaryLookup(self._checkpoints, key, 0, len); + return pos == 0 ? (0, 0) : (_unsafeAccess(self._checkpoints, pos - 1)._value, pos - 1); } /** - * @dev Returns the value in the last (most recent) checkpoint with timestamp lower or equal than the search timestamp, or zero if there is none. + * @dev Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. * In addition, returns the position of the checkpoint in the array. * - * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high timestamps). + * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" checkpoint (checkpoints with high keys). * NOTE: That if value != 0 && pos == 0, then that means the value is the first checkpoint and actually exists * a checkpoint DNE iff value == 0 && pos == 0 => value == 0 */ - function upperLookupRecentWithPos(History storage self, uint32 timestamp) internal view returns (uint224, uint256) { - uint256 len = self._magnitudes.length; + function upperLookupRecentWithPos(History storage self, uint32 key) internal view returns (uint224, uint256) { + uint256 len = self._checkpoints.length; uint256 low = 0; uint256 high = len; if (len > 5) { uint256 mid = len - MathUpgradeable.sqrt(len); - if (timestamp < _unsafeAccess(self._magnitudes, mid)._timestamp) { + if (key < _unsafeAccess(self._checkpoints, mid)._key) { high = mid; } else { low = mid + 1; } } - uint256 pos = _upperBinaryLookup(self._magnitudes, timestamp, low, high); + uint256 pos = _upperBinaryLookup(self._checkpoints, key, low, high); - return pos == 0 ? (0, 0) : (_unsafeAccess(self._magnitudes, pos - 1)._value, pos - 1); + return pos == 0 ? (0, 0) : (_unsafeAccess(self._checkpoints, pos - 1)._value, pos - 1); } /// @notice WARNING: this function is only used because of the invariant property - /// that from the current timestamp, all future checkpointed magnitude values are strictly > current value. + /// that from the current key, all future checkpointed magnitude values are strictly > current value. /// Use function with extreme care for other situations. function decrementAtAndFutureCheckpoints( History storage self, - uint32 timestamp, + uint32 key, uint224 decrementValue ) internal { - (uint224 value, uint256 pos) = upperLookupRecentWithPos(self, timestamp); + (uint224 value, uint256 pos) = upperLookupRecentWithPos(self, key); // if there is no checkpoint, return if (value == 0 && pos == 0) { pos = type(uint256).max; } - uint256 len = self._magnitudes.length; + uint256 len = self._checkpoints.length; while (pos < len) { - MagnitudeCheckpoint storage current = _unsafeAccess(self._magnitudes, pos); + Checkpoint storage current = _unsafeAccess(self._checkpoints, pos); // reverts from underflow. Expected to never happen in our usage current._value -= decrementValue;