diff --git a/src/contracts/core/AVSDirectoryStorage.sol b/src/contracts/core/AVSDirectoryStorage.sol index a835aa8dc..77e15be8f 100644 --- a/src/contracts/core/AVSDirectoryStorage.sol +++ b/src/contracts/core/AVSDirectoryStorage.sol @@ -28,11 +28,6 @@ abstract contract AVSDirectoryStorage is IAVSDirectory { bytes32 public constant OPERATOR_SET_FORCE_DEREGISTRATION_TYPEHASH = keccak256("OperatorSetForceDeregistration(address avs,uint32[] operatorSetIds,bytes32 salt,uint256 expiry)"); - /// @notice The EIP-712 typehash for the `MagnitudeAdjustments` struct used by the contract - bytes32 public constant MAGNITUDE_ADJUSTMENT_TYPEHASH = keccak256( - "MagnitudeAdjustments(address operator,MagnitudeAdjustment(address strategy, OperatorSet(address avs, uint32 operatorSetId)[], uint64[] magnitudeDiffs)[],bytes32 salt,uint256 expiry)" - ); - /// @dev Index for flag that pauses operator register/deregister to avs when set. uint8 internal constant PAUSED_OPERATOR_REGISTER_DEREGISTER_TO_AVS = 0; diff --git a/src/contracts/core/AllocationManager.sol b/src/contracts/core/AllocationManager.sol index d9aa34543..58a2a73f8 100644 --- a/src/contracts/core/AllocationManager.sol +++ b/src/contracts/core/AllocationManager.sol @@ -17,7 +17,7 @@ contract AllocationManager is AllocationManagerStorage, ReentrancyGuardUpgradeable { - using Snapshots for Snapshots.History; + using Snapshots for Snapshots.DefaultWadHistory; using DoubleEndedQueue for DoubleEndedQueue.Bytes32Deque; using SlashingLib for uint256; @@ -78,7 +78,7 @@ contract AllocationManager is /** * @notice This function takes a list of strategies and adds all completable modifications for each strategy, - * updating the freeMagnitudes of the operator as needed. + * updating the encumberedMagnitude of the operator as needed. * * @param operator address to complete modifications for * @param strategies a list of strategies to complete modifications for @@ -101,7 +101,7 @@ contract AllocationManager is /** * @notice Modifies the propotions of slashable stake allocated to a list of operatorSets for a set of strategies * @param allocations array of magnitude adjustments for multiple strategies and corresponding operator sets - * @dev Updates freeMagnitude for the updated strategies + * @dev Updates encumberedMagnitude for the updated strategies * @dev msg.sender is used as operator * @dev For each allocation, allocation.operatorSets MUST be ordered in ascending order according to the * encoding of the operatorSet. This is to prevent duplicate operatorSets being passed in. The easiest way to ensure @@ -118,37 +118,21 @@ contract AllocationManager is require(allocation.operatorSets.length == allocation.magnitudes.length, InputArrayLengthMismatch()); require(avsDirectory.isOperatorSetBatch(allocation.operatorSets), InvalidOperatorSet()); - // 1. For the given (operator,strategy) complete any pending deallocations to update free magnitude + // 1. For the given (operator,strategy) complete any pending modifications to free up encumberedMagnitude _completePendingModifications({ operator: msg.sender, strategy: allocation.strategy, numToComplete: type(uint16).max }); - - { - // 2. Check current totalMagnitude matches expected value. This is to check for slashing race conditions - // where an operator gets slashed from an operatorSet and as a result all the configured allocations have larger - // proprtional magnitudes relative to each other. - - // Load the operator's total magnitude for the strategy. - (bool exists,, uint224 currentTotalMagnitude) = - _totalMagnitudeUpdate[msg.sender][allocation.strategy].latestSnapshot(); - - // If the operator has no total magnitude snapshot, set it to WAD, which denotes an unslashed operator. - if (!exists) { - currentTotalMagnitude = WAD; - _totalMagnitudeUpdate[msg.sender][allocation.strategy].push({ - key: uint32(block.timestamp), - value: currentTotalMagnitude - }); - allocatableMagnitude[msg.sender][allocation.strategy] = uint64(currentTotalMagnitude); - } - - require( - uint64(currentTotalMagnitude) == allocation.expectedTotalMagnitude, InvalidExpectedTotalMagnitude() - ); - } - + + // 2. Check current totalMagnitude matches expected value. This is to check for slashing race conditions + // where an operator gets slashed from an operatorSet and as a result all the configured allocations have larger + // proprtional magnitudes relative to each other. + uint64 totalMagnitude = uint64(_totalMagnitudeUpdate[msg.sender][allocation.strategy].latest()); + require( + totalMagnitude == allocation.expectedTotalMagnitude, InvalidExpectedTotalMagnitude() + ); + for (uint256 j = 0; j < allocation.operatorSets.length; ++j) { bytes32 operatorSetKey = _encodeOperatorSet(allocation.operatorSets[j]); // Check that there are no pending allocations & deallocations for the operator, operatorSet, strategy @@ -157,23 +141,25 @@ contract AllocationManager is require(noPendingModification, ModificationAlreadyPending()); // Calculate the new pending diff with this modification - mInfo.pendingMagnitudeDiff = + mInfo.pendingMagnitudeDelta = int128(uint128(allocation.magnitudes[j])) - int128(uint128(mInfo.currentMagnitude)); - require(mInfo.pendingMagnitudeDiff != 0, SameMagnitude()); + require(mInfo.pendingMagnitudeDelta != 0, SameMagnitude()); // Handle deallocation/allocation and modification effect timestamp - if (mInfo.pendingMagnitudeDiff < 0) { + if (mInfo.pendingMagnitudeDelta < 0) { // This is a deallocation // 1. Update the effect timestamp for the deallocation mInfo.effectTimestamp = uint32(block.timestamp) + DEALLOCATION_DELAY; - } else if (mInfo.pendingMagnitudeDiff > 0) { + } else if (mInfo.pendingMagnitudeDelta > 0) { // This is an allocation - // 1. decrement free magnitude by incremented amount - uint64 magnitudeToAllocate = uint64(uint128(mInfo.pendingMagnitudeDiff)); - require(allocatableMagnitude[msg.sender][allocation.strategy] >= magnitudeToAllocate, InsufficientAllocatableMagnitude()); - allocatableMagnitude[msg.sender][allocation.strategy] -= magnitudeToAllocate; + // 1. add to the encumbered magnitude and make sure it doesn't exceed the total magnitude + uint64 newEncumberedMagnitude = + encumberedMagnitude[msg.sender][allocation.strategy] + + uint64(uint128(mInfo.pendingMagnitudeDelta)); + require(newEncumberedMagnitude <= totalMagnitude, InsufficientAllocatableMagnitude()); + encumberedMagnitude[msg.sender][allocation.strategy] = newEncumberedMagnitude; // 2. Update the effectTimestamp for the allocation mInfo.effectTimestamp = uint32(block.timestamp) + operatorAllocationDelay; @@ -217,15 +203,15 @@ contract AllocationManager is uint64 slashedMagnitude = uint64(uint256(mInfo.currentMagnitude).mulWad(wadToSlash)); mInfo.currentMagnitude -= slashedMagnitude; // if there is a pending deallocation, slash pending deallocation proportionally - if (mInfo.pendingMagnitudeDiff < 0) { - int128 slashedPending = int128(uint128(uint256(uint128(-mInfo.pendingMagnitudeDiff)).mulWad(wadToSlash))); - mInfo.pendingMagnitudeDiff += int128(slashedPending); + if (mInfo.pendingMagnitudeDelta < 0) { + int128 slashedPending = int128(uint128(uint256(uint128(-mInfo.pendingMagnitudeDelta)).mulWad(wadToSlash))); + mInfo.pendingMagnitudeDelta += int128(slashedPending); } // update operatorMagnitudeInfo _operatorMagnitudeInfo[operator][strategies[i]][operatorSetKey] = mInfo; // 3. update totalMagnitude, get total magnitude and subtract slashedMagnitude - Snapshots.History storage totalMagnitudes = _totalMagnitudeUpdate[operator][strategies[i]]; + Snapshots.DefaultWadHistory storage totalMagnitudes = _totalMagnitudeUpdate[operator][strategies[i]]; uint64 totalMagnitudeBeforeSlashing = uint64(totalMagnitudes.latest()); totalMagnitudes.push({key: uint32(block.timestamp), value: totalMagnitudeBeforeSlashing - slashedMagnitude}); @@ -259,9 +245,7 @@ contract AllocationManager is * @notice For a single strategy, complete pending modifications for an operator, strategy * @param operator address to update allocatableMagnitude for * @param strategy the strategy to update allocatableMagnitude for - * @param numToComplete the number of pending free magnitudes deallocations to complete - * @dev read through pending free magnitudes and add to allocatableMagnitude if completableTimestamp is >= block timestamp - * In addition to updating allocatableMagnitude, updates next starting index to read from for pending free magnitudes after completing + * @param numToComplete the number of pending modifications to complete */ function _completePendingModifications(address operator, IStrategy strategy, uint16 numToComplete) internal { uint256 numCompleted; @@ -291,14 +275,15 @@ contract AllocationManager is } // update stored magnitudes - if (mInfo.pendingMagnitudeDiff > 0) { - mInfo.currentMagnitude += uint64(uint128(mInfo.pendingMagnitudeDiff)); + if (mInfo.pendingMagnitudeDelta > 0) { + mInfo.currentMagnitude += uint64(uint128(mInfo.pendingMagnitudeDelta)); } else { - mInfo.currentMagnitude -= uint64(uint128(-mInfo.pendingMagnitudeDiff)); - // if this is a deallocation, add the magnitude back to freeMagnitude - allocatableMagnitude[operator][strategy] += uint64(uint128(-mInfo.pendingMagnitudeDiff)); + uint64 magnitudeDelta = uint64(uint128(-mInfo.pendingMagnitudeDelta)); + mInfo.currentMagnitude -= magnitudeDelta; + // if this is a deallocation, reduce the encumbered magnitude + encumberedMagnitude[operator][strategy] -= magnitudeDelta; } - mInfo.pendingMagnitudeDiff = 0; + mInfo.pendingMagnitudeDelta = 0; // update the magnitude info in storage _operatorMagnitudeInfo[operator][strategy][operatorSetKey] = mInfo; @@ -340,10 +325,10 @@ contract AllocationManager is MagnitudeInfo memory mInfo = _operatorMagnitudeInfo[operator][strategies[i]][operatorSetKey]; slashableMagnitudes[i][j] = mInfo.currentMagnitude; if (block.timestamp >= mInfo.effectTimestamp && mInfo.effectTimestamp != 0) { - if (mInfo.pendingMagnitudeDiff < 0) { - slashableMagnitudes[i][j] -= uint64(uint128(-mInfo.pendingMagnitudeDiff)); - } else if (mInfo.pendingMagnitudeDiff > 0) { - slashableMagnitudes[i][j] += uint64(uint128(mInfo.pendingMagnitudeDiff)); + if (mInfo.pendingMagnitudeDelta < 0) { + slashableMagnitudes[i][j] -= uint64(uint128(-mInfo.pendingMagnitudeDelta)); + } else if (mInfo.pendingMagnitudeDelta > 0) { + slashableMagnitudes[i][j] += uint64(uint128(mInfo.pendingMagnitudeDelta)); } } } @@ -352,8 +337,7 @@ contract AllocationManager is } /** - * @notice Get the allocatable magnitude for an operator and strategy based on number of pending deallocations - * that could be completed at the same time. This is the sum of freeMagnitude and the sum of all pending completable deallocations. + * @notice Get the allocatable magnitude for an operator and strategy * @param operator the operator to get the allocatable magnitude for * @param strategy the strategy to get the allocatable magnitude for */ @@ -362,13 +346,13 @@ contract AllocationManager is for (uint256 i = 0; i < modificationQueue[operator][strategy].length(); ++i) { bytes32 opsetKey = modificationQueue[operator][strategy].at(i); MagnitudeInfo memory mInfo = _operatorMagnitudeInfo[operator][strategy][opsetKey]; - if (block.timestamp >= mInfo.effectTimestamp && mInfo.effectTimestamp != 0 && mInfo.pendingMagnitudeDiff < 0) { - freeableMagnitude += uint64(uint128(-mInfo.pendingMagnitudeDiff)); + if (block.timestamp >= mInfo.effectTimestamp && mInfo.effectTimestamp != 0 && mInfo.pendingMagnitudeDelta < 0) { + freeableMagnitude += uint64(uint128(-mInfo.pendingMagnitudeDelta)); } else { break; } } - return allocatableMagnitude[operator][strategy] + freeableMagnitude; + return uint64(_totalMagnitudeUpdate[operator][strategy].latest()) - encumberedMagnitude[operator][strategy] + freeableMagnitude; } /** @@ -407,13 +391,7 @@ contract AllocationManager is ) external view returns (uint64[] memory) { uint64[] memory totalMagnitudes = new uint64[](strategies.length); for (uint256 i = 0; i < strategies.length; ++i) { - (uint224 value, uint256 pos) = _totalMagnitudeUpdate[operator][strategies[i]].upperLookupWithPos(timestamp); - // if there is no existing total magnitude snapshot - if (value == 0 && pos == 0) { - totalMagnitudes[i] = WAD; - } else { - totalMagnitudes[i] = uint64(value); - } + totalMagnitudes[i] = uint64(_totalMagnitudeUpdate[operator][strategies[i]].upperLookup(timestamp)); } return totalMagnitudes; } @@ -424,22 +402,22 @@ contract AllocationManager is * @param strategy the strategy to get the pending modification for * @param operatorSets the operatorSets to get the pending modification for * @return timestamps the timestamps for each pending dealloction - * @return pendingMagnitudeDiffs the pending modification diffs for each operatorSet + * @return pendingMagnitudeDeltas the pending modification diffs for each operatorSet */ function getPendingModifications( address operator, IStrategy strategy, OperatorSet[] calldata operatorSets - ) external view returns (uint32[] memory timestamps, int128[] memory pendingMagnitudeDiffs) { + ) external view returns (uint32[] memory timestamps, int128[] memory pendingMagnitudeDeltas) { timestamps = new uint32[](operatorSets.length); - pendingMagnitudeDiffs = new int128[](operatorSets.length); + pendingMagnitudeDeltas = new int128[](operatorSets.length); for (uint256 i = 0; i < operatorSets.length; ++i) { MagnitudeInfo memory opsetMagnitudeInfo = _operatorMagnitudeInfo[operator][strategy][_encodeOperatorSet(operatorSets[i])]; if (opsetMagnitudeInfo.effectTimestamp < block.timestamp && opsetMagnitudeInfo.effectTimestamp != 0) { - pendingMagnitudeDiffs[i] = opsetMagnitudeInfo.pendingMagnitudeDiff; + pendingMagnitudeDeltas[i] = opsetMagnitudeInfo.pendingMagnitudeDelta; timestamps[i] = opsetMagnitudeInfo.effectTimestamp; } } diff --git a/src/contracts/core/AllocationManagerStorage.sol b/src/contracts/core/AllocationManagerStorage.sol index 8e71500fe..30a8290d6 100644 --- a/src/contracts/core/AllocationManagerStorage.sol +++ b/src/contracts/core/AllocationManagerStorage.sol @@ -8,7 +8,7 @@ import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; import {Snapshots} from "../libraries/Snapshots.sol"; abstract contract AllocationManagerStorage is IAllocationManager { - using Snapshots for Snapshots.History; + using Snapshots for Snapshots.DefaultWadHistory; // Constants @@ -43,10 +43,10 @@ abstract contract AllocationManagerStorage is IAllocationManager { /// @notice Mapping: operator => strategy => snapshotted totalMagnitude /// Note that totalMagnitude is monotonically decreasing and only gets updated upon slashing - mapping(address => mapping(IStrategy => Snapshots.History)) internal _totalMagnitudeUpdate; + mapping(address => mapping(IStrategy => Snapshots.DefaultWadHistory)) internal _totalMagnitudeUpdate; - /// @notice Mapping: operator => strategy => the amount of magnitude that is allocatable - mapping(address => mapping(IStrategy => uint64)) public allocatableMagnitude; + /// @notice Mapping: operator => strategy => the amount of magnitude that is not available for allocation + mapping(address => mapping(IStrategy => uint64)) public encumberedMagnitude; /// @notice Mapping: operator => strategy => operatorSet (encoded) => MagnitudeInfo mapping(address => mapping(IStrategy => mapping(bytes32 => MagnitudeInfo))) internal _operatorMagnitudeInfo; diff --git a/src/contracts/interfaces/IAllocationManager.sol b/src/contracts/interfaces/IAllocationManager.sol index 95e0f765c..339c4ca0c 100644 --- a/src/contracts/interfaces/IAllocationManager.sol +++ b/src/contracts/interfaces/IAllocationManager.sol @@ -52,11 +52,11 @@ interface IAllocationManager is ISignatureUtils { /** * @notice struct used for pending free magnitude. Stored in (operator, strategy, operatorSet) mapping * to be used in completeDeallocations. - * @param magnitudeDiff the amount of magnitude to deallocate + * @param magnitudeDelta the amount of magnitude to deallocate * @param completableTimestamp the timestamp at which the deallocation can be completed, 21 days from when queued */ // struct PendingFreeMagnitude { - // uint64 magnitudeDiff; + // uint64 magnitudeDelta; // uint32 completableTimestamp; // } @@ -67,7 +67,7 @@ interface IAllocationManager is ISignatureUtils { * @param effectTimestamp the timestamp at which the pending magnitude will take effect */ struct MagnitudeInfo { - int128 pendingMagnitudeDiff; + int128 pendingMagnitudeDelta; uint64 currentMagnitude; uint32 effectTimestamp; } @@ -223,13 +223,13 @@ interface IAllocationManager is ISignatureUtils { * @param strategy the strategy to get the pending modification for * @param operatorSets the operatorSets to get the pending modification for * @return timestamps the timestamps for each pending dealloction - * @return pendingMagnitudeDiffs the pending modification diffs for each operatorSet + * @return pendingMagnitudeDeltas the pending modification diffs for each operatorSet */ function getPendingModifications( address operator, IStrategy strategy, OperatorSet[] calldata operatorSets - ) external view returns (uint32[] memory timestamps, int128[] memory pendingMagnitudeDiffs); + ) external view returns (uint32[] memory timestamps, int128[] memory pendingMagnitudeDeltas); /** * @param operator the operator to get the slashable magnitude for diff --git a/src/contracts/libraries/Snapshots.sol b/src/contracts/libraries/Snapshots.sol index c71dcfd3d..3ddd69c31 100644 --- a/src/contracts/libraries/Snapshots.sol +++ b/src/contracts/libraries/Snapshots.sol @@ -5,22 +5,24 @@ pragma solidity ^0.8.0; import "@openzeppelin-upgrades/contracts/utils/math/MathUpgradeable.sol"; import "@openzeppelin-upgrades/contracts/utils/math/SafeCastUpgradeable.sol"; +import "./SlashingLib.sol"; + /** * @title Library for handling snapshots 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 (timestamps, etc). * Some additional functions have also been added for convenience. - * @dev This library defines the `History` struct, for snapshotting values as they change at different points in + * @dev This library defines the `DefaultWadHistory` struct, for snapshotting values as they change at different points in * time, and later looking up past values by block number. See {Votes} as an example. * - * To create a history of snapshots define a variable type `Snapshots.History` in your contract, and store a new - * snapshot for the current transaction block using the {push} function. + * To create a history of snapshots define a variable type `Snapshots.DefaultWadHistory` in your contract, and store a new + * snapshot for the current transaction block using the {push} function. If there is no history yet, the value is WAD. * * _Available since v4.5._ */ library Snapshots { - struct History { + struct DefaultWadHistory { Snapshot[] _snapshots; } @@ -30,18 +32,18 @@ library Snapshots { } /** - * @dev Pushes a (`key`, `value`) pair into a History so that it is stored as the snapshot. + * @dev Pushes a (`key`, `value`) pair into a DefaultWadHistory so that it is stored as the snapshot. * * Returns previous value and new value. */ - function push(History storage self, uint32 key, uint224 value) internal returns (uint224, uint224) { + function push(DefaultWadHistory storage self, uint32 key, uint224 value) internal returns (uint224, uint224) { return _insert(self._snapshots, key, value); } /** * @dev Returns the value in the first (oldest) snapshot with key greater or equal than the search key, or zero if there is none. */ - function lowerLookup(History storage self, uint32 key) internal view returns (uint224) { + function lowerLookup(DefaultWadHistory storage self, uint32 key) internal view returns (uint224) { uint256 len = self._snapshots.length; uint256 pos = _lowerBinaryLookup(self._snapshots, key, 0, len); return pos == len ? 0 : _unsafeAccess(self._snapshots, pos)._value; @@ -50,10 +52,10 @@ library Snapshots { /** * @dev Returns the value in the last (most recent) snapshot with key lower or equal than the search key, or zero if there is none. */ - function upperLookup(History storage self, uint32 key) internal view returns (uint224) { + function upperLookup(DefaultWadHistory storage self, uint32 key) internal view returns (uint224) { uint256 len = self._snapshots.length; uint256 pos = _upperBinaryLookup(self._snapshots, key, 0, len); - return pos == 0 ? 0 : _unsafeAccess(self._snapshots, pos - 1)._value; + return pos == 0 ? WAD : _unsafeAccess(self._snapshots, pos - 1)._value; } /** @@ -61,7 +63,7 @@ library Snapshots { * * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" snapshot (snapshots with high keys). */ - function upperLookupRecent(History storage self, uint32 key) internal view returns (uint224) { + function upperLookupRecent(DefaultWadHistory storage self, uint32 key) internal view returns (uint224) { uint256 len = self._snapshots.length; uint256 low = 0; @@ -78,17 +80,17 @@ library Snapshots { uint256 pos = _upperBinaryLookup(self._snapshots, key, low, high); - return pos == 0 ? 0 : _unsafeAccess(self._snapshots, pos - 1)._value; + return pos == 0 ? WAD : _unsafeAccess(self._snapshots, pos - 1)._value; } /** * @dev Returns the value in the most recent snapshot, or zero if there are no snapshots. */ function latest( - History storage self + DefaultWadHistory storage self ) internal view returns (uint224) { uint256 pos = self._snapshots.length; - return pos == 0 ? 0 : _unsafeAccess(self._snapshots, pos - 1)._value; + return pos == 0 ? WAD : _unsafeAccess(self._snapshots, pos - 1)._value; } /** @@ -96,11 +98,11 @@ library Snapshots { * in the most recent snapshot. */ function latestSnapshot( - History storage self + DefaultWadHistory storage self ) internal view returns (bool exists, uint32 _key, uint224 _value) { uint256 pos = self._snapshots.length; if (pos == 0) { - return (false, 0, 0); + return (false, 0, WAD); } else { Snapshot memory ckpt = _unsafeAccess(self._snapshots, pos - 1); return (true, ckpt._key, ckpt._value); @@ -111,7 +113,7 @@ library Snapshots { * @dev Returns the number of snapshot. */ function length( - History storage self + DefaultWadHistory storage self ) internal view returns (uint256) { return self._snapshots.length; } @@ -209,7 +211,7 @@ library Snapshots { * @dev Returns the value in the last (most recent) snapshot with key lower or equal than the search key, or zero if there is none. * This function is a linear search for keys that are close to the end of the array. */ - function upperLookupLinear(History storage self, uint32 key) internal view returns (uint224) { + function upperLookupLinear(DefaultWadHistory storage self, uint32 key) internal view returns (uint224) { uint256 len = self._snapshots.length; for (uint256 i = len; i > 0; --i) { Snapshot storage current = _unsafeAccess(self._snapshots, i - 1); @@ -219,71 +221,4 @@ library Snapshots { } return 0; } - - /** - * @dev Returns the value in the last (most recent) snapshot with key lower or equal than the search key, or zero if there is none. - * In addition, returns the position of the snapshot in the array. - * - * NOTE: That if value != 0 && pos == 0, then that means the value is the first snapshot and actually exists - * a snapshot DNE iff value == 0 && pos == 0 - */ - function upperLookupWithPos(History storage self, uint32 key) internal view returns (uint224, uint256) { - uint256 len = self._snapshots.length; - uint256 pos = _upperBinaryLookup(self._snapshots, key, 0, len); - return pos == 0 ? (0, 0) : (_unsafeAccess(self._snapshots, pos - 1)._value, pos - 1); - } - - /** - * @dev Returns the value in the last (most recent) snapshot with key lower or equal than the search key, or zero if there is none. - * In addition, returns the position of the snapshot in the array. - * - * NOTE: This is a variant of {upperLookup} that is optimised to find "recent" snapshot (snapshots with high keys). - * NOTE: That if value != 0 && pos == 0, then that means the value is the first snapshot and actually exists - * a snapshot DNE iff value == 0 && pos == 0 => value == 0 - */ - function upperLookupRecentWithPos( - History storage self, - uint32 key - ) internal view returns (uint224, uint256, uint256) { - uint256 len = self._snapshots.length; - - uint256 low = 0; - uint256 high = len; - - if (len > 5) { - uint256 mid = len - MathUpgradeable.sqrt(len); - if (key < _unsafeAccess(self._snapshots, mid)._key) { - high = mid; - } else { - low = mid + 1; - } - } - - uint256 pos = _upperBinaryLookup(self._snapshots, key, low, high); - - return pos == 0 ? (0, 0, len) : (_unsafeAccess(self._snapshots, pos - 1)._value, pos - 1, len); - } - - /// @notice WARNING: this function is only used because of the invariant property - /// that from the current key, all future snapshotted magnitude values are strictly > current value. - /// Use function with extreme care for other situations. - function decrementAtAndFutureSnapshots(History storage self, uint32 key, uint224 decrementValue) internal { - (uint224 value, uint256 pos, uint256 len) = upperLookupRecentWithPos(self, key); - - // if there is no snapshot, return - if (value == 0 && pos == 0) { - pos = type(uint256).max; - } - - while (pos < len) { - Snapshot storage current = _unsafeAccess(self._snapshots, pos); - - // reverts from underflow. Expected to never happen in our usage - current._value -= decrementValue; - - unchecked { - ++pos; - } - } - } }