Skip to content

Commit

Permalink
test: add tests for updateOperatorStake
Browse files Browse the repository at this point in the history
  • Loading branch information
wadealexc committed Nov 14, 2023
1 parent acc170a commit a6baa25
Show file tree
Hide file tree
Showing 6 changed files with 418 additions and 218 deletions.
48 changes: 27 additions & 21 deletions docs/StakeRegistry.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ This document organizes methods according to the following themes (click each to

#### Important State Variables

* `StakeUpdate[][256] internal _totalStakeHistory`: TODO - explain history update pattern
* `mapping(bytes32 => mapping(uint8 => StakeUpdate[])) internal operatorStakeHistory`: TODO - explain history update pattern
* `uint96[256] public minimumStakeForQuorum`: TODO
* `StakeUpdate[][256] internal _totalStakeHistory`: TODO
* `mapping(bytes32 => mapping(uint8 => StakeUpdate[])) internal operatorStakeHistory`: TODO

#### Helpful Definitions

Expand All @@ -49,16 +49,22 @@ struct StrategyParams {
}
```

* `_weightOfOperatorForQuorum(uint8 quorumNumber, address operator) -> (uint96 weight, bool hasMinimumStake)`: Uses the quorum's configured `StrategyParams` to calculate the weight of an `operator` across each strategy they have shares in.
* For each `strategy` and `multiplier` configured for the quorum, the `operator's` raw share count is queried from the core `DelegationManager` contract (see [`eigenlayer-contracts/docs`](https://github.com/Layr-Labs/eigenlayer-contracts/tree/master/docs)) and multiplied with the corresponding `multiplier`. These results are summed to determine the total weight of the `operator` for the quorum.
* If the sum is less than the `minimumStakeForQuorum`, `hasMinimumStake` will be false.

---

### Operator Lifecycle

(TODO) Brief description, here are the functions:
These methods are callable ONLY by the `BLSRegistryCoordinatorWithIndices`, and are used when operators register, deregister, or are updated:

* [`StakeRegistry.registerOperator`](#registeroperator)
* [`StakeRegistry.deregisterOperator`](#deregisteroperator)
* [`StakeRegistry.updateOperatorStake`](#updateoperatorstake)

See [`BLSRegistryCoordinatorWithIndices.md`](./BLSRegistryCoordinatorWithIndices.md) for more context on how these methods are used.

#### `registerOperator`

```solidity
Expand All @@ -70,35 +76,35 @@ function registerOperator(
public
virtual
onlyRegistryCoordinator
returns (uint96[] memory, uint96[] memory)
returns (uint96[] memory currentStakes, uint96[] memory totalStakes)
```

<!-- The RegistryCoordinator for the AVS makes a call to the StakeRegistry to register an operator for a certain set of quorums. For each of the quorums being registered for, the StakeRegistry calculates a linear combination of the operator's delegated shares of each `strategy` in the quorum and their corresponding `multiplier` to get a `stake`. The contract then stores the stake in the following struct:
```
/// @notice struct used to store the stakes of an individual operator or the sum of all operators' stakes, for storage
struct OperatorStakeUpdate {
// the block number at which the stake amounts were updated and stored
uint32 updateBlockNumber;
// the block number at which the *next update* occurred.
/// @notice This entry has the value **0** until another update takes place.
uint32 nextUpdateBlockNumber;
// stake weight for the quorum
uint96 stake;
}
```
For each quorum the operator is a part of. -->
The `BLSRegistryCoordinatorWithIndices` calls this method when an operator registers for one or more quorums.

(TODO) description
For each quorum, the `operator's` weight is calculating according to that quorum's `StrategyParams` (see `_weightOfOperatorForQuorum` in [Helpful Definitions](#helpful-definitions)). If the `operator's` weight is below the minimum stake for the quorum, the method fails.

Otherwise, the `operator's` stake history is updated with the new stake. See `operatorStakeHistory` in [Important State Variables](#important-state-variables) for specifics.

The quorum's total stake history is also updated, adding the `operator's` new stake to the quorum's current stake. See `_totalStakeHistory` in [Important State Variables](#important-state-variables) for specifics.

This method returns two things to the `BLSRegistryCoordinatorWithIndices`:
* `uint96[] memory currentStakes`: A list of the `operator's` current stake for each of the passed-in `quorumNumbers`
* `uint96[] memory totalStakes`: A list of the current total stakes for each quorum in the passed-in `quorumNumbers`

*Entry Points*:
* `BLSRegistryCoordinatorWithIndices.registerOperator`
* `BLSRegistryCoordinatorWithIndices.registerOperatorWithChurn`

*Effects*:
*
* For each quorum in `quorumNumbers`:
* Updates the `operator's` current stake for the quorum given by that quorum's `StrategyParams` and the `operator's` shares in the core `DelegationManager` contract.
* Updates the quorum's total stake to account for the `operator's` change in stake.

*Requirements*:
*
* Caller MUST be the `BLSRegistryCoordinatorWithIndices`
* For each quorum in `quorumNumbers`:
* The quorum MUST exist
* `operator` MUST have at least the minimum weight required for the quorum

#### `deregisterOperator`

Expand Down
4 changes: 2 additions & 2 deletions test/harnesses/BitmapUtilsWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract BitmapUtilsWrapper {
return BitmapUtils.countNumOnes(n);
}

function numberIsInBitmap(uint256 bitmap, uint8 numberToCheckForInclusion) external pure returns (bool) {
return BitmapUtils.numberIsInBitmap(bitmap, numberToCheckForInclusion);
function isSet(uint256 bitmap, uint8 numberToCheckForInclusion) external pure returns (bool) {
return BitmapUtils.isSet(bitmap, numberToCheckForInclusion);
}
}
35 changes: 5 additions & 30 deletions test/harnesses/StakeRegistryHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,36 +40,11 @@ contract StakeRegistryHarness is StakeRegistry {
__weightOfOperatorForQuorum[quorumNumber][operator] = weight;
}

// mocked function to register an operator without having to mock other elements
// This is just a copy/paste from `registerOperator`, since that no longer uses an internal method
function registerOperatorNonCoordinator(address operator, bytes32 operatorId, bytes calldata quorumNumbers) external returns (uint96[] memory, uint96[] memory) {
uint96[] memory currentStakes = new uint96[](quorumNumbers.length);
uint96[] memory totalStakes = new uint96[](quorumNumbers.length);
for (uint256 i = 0; i < quorumNumbers.length; i++) {

uint8 quorumNumber = uint8(quorumNumbers[i]);
require(_quorumExists(quorumNumber), "StakeRegistry.registerOperator: quorum does not exist");

// Retrieve the operator's current weighted stake for the quorum, reverting if they have not met
// the minimum.
(uint96 currentStake, bool hasMinimumStake) = _weightOfOperatorForQuorum(quorumNumber, operator);
require(
hasMinimumStake,
"StakeRegistry.registerOperator: Operator does not meet minimum stake requirement for quorum"
);

// Update the operator's stake
int256 stakeDelta = _recordOperatorStakeUpdate({
operatorId: operatorId,
quorumNumber: quorumNumber,
newStake: currentStake
});

// Update this quorum's total stake by applying the operator's delta
currentStakes[i] = currentStake;
totalStakes[i] = _recordTotalStakeUpdate(quorumNumber, stakeDelta);
}
function calculateDelta(uint96 prev, uint96 cur) external pure returns (int256) {
return _calculateDelta(prev, cur);
}

return (currentStakes, totalStakes);
function applyDelta(uint96 value, int256 delta) external pure returns (uint96) {
return _applyDelta(value, delta);
}
}
15 changes: 14 additions & 1 deletion test/tree/StakeRegistryUnit.tree
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,20 @@
### given the total stake history was updated in the current block
#### it should update the last total stake update with the new stake
# when updateOperatorStake is called
## TODO
## given caller is not the registry coordinator
### it should revert
## given quorum does not exist
### it should revert
## given the operator currently meets the minimum stake for the quorum
### given they still do after updating
#### it should update their history and apply the change to the total history
### given they no longer meet the minimum
#### it should set their stake to zero, remove it from the total stake, and add the quorum to the return bitmap
## given the operator does not currently meet the minimum stake for the quorum
### given they still do not meet the minimum
#### it should not perform any updates, and it should add the quorum to the return bitmap
### given they meet the minimum after the update
#### it should update the operator and total history with the stake
# when setMinimumStakeForQuorum is called ***
## TODO
# when addStrategies is called ***
Expand Down
14 changes: 7 additions & 7 deletions test/unit/BitmapUtils.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,15 @@ contract BitmapUtilsUnitTests is Test {
require(libraryOutput == numOnes, "inconsistency in countNumOnes function");
}

// @notice some simple sanity checks on the `numberIsInBitmap` function
// @notice some simple sanity checks on the `isSet` function
function testNumberIsInBitmap() public view {
require(bitmapUtilsWrapper.numberIsInBitmap(2 ** 6, 6), "numberIsInBitmap function is broken 0");
require(bitmapUtilsWrapper.numberIsInBitmap(1, 0), "numberIsInBitmap function is broken 1");
require(bitmapUtilsWrapper.numberIsInBitmap(255, 7), "numberIsInBitmap function is broken 2");
require(bitmapUtilsWrapper.numberIsInBitmap(1024, 10), "numberIsInBitmap function is broken 3");
require(bitmapUtilsWrapper.isSet(2 ** 6, 6), "isSet function is broken 0");
require(bitmapUtilsWrapper.isSet(1, 0), "isSet function is broken 1");
require(bitmapUtilsWrapper.isSet(255, 7), "isSet function is broken 2");
require(bitmapUtilsWrapper.isSet(1024, 10), "isSet function is broken 3");
for (uint256 i = 0; i < 256; ++i) {
require(bitmapUtilsWrapper.numberIsInBitmap(type(uint256).max, uint8(i)), "numberIsInBitmap function is broken 4");
require(!bitmapUtilsWrapper.numberIsInBitmap(0, uint8(i)), "numberIsInBitmap function is broken 5");
require(bitmapUtilsWrapper.isSet(type(uint256).max, uint8(i)), "isSet function is broken 4");
require(!bitmapUtilsWrapper.isSet(0, uint8(i)), "isSet function is broken 5");
}
}
}
Loading

0 comments on commit a6baa25

Please sign in to comment.