Skip to content

Commit 7b4031c

Browse files
0x0aa0pakim249CAL
andcommitted
feat: operatorStateWithSocket (#418)
**Motivation:** *Explain here the context, and why you're making that change. What is the problem you're trying to solve.* **Modifications:** *Describe the modifications you've done.* **Result:** *After your change, what will change.* --------- Co-authored-by: Patrick Kim <pakim249@gmail.com>
1 parent f5adbca commit 7b4031c

File tree

2 files changed

+208
-2
lines changed

2 files changed

+208
-2
lines changed

src/OperatorStateRetriever.sol

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity ^0.8.27;
33

44
import {ISlashingRegistryCoordinator} from "./interfaces/ISlashingRegistryCoordinator.sol";
5+
import {ISocketRegistry} from "./interfaces/ISocketRegistry.sol";
56
import {IBLSApkRegistry} from "./interfaces/IBLSApkRegistry.sol";
67
import {IStakeRegistry} from "./interfaces/IStakeRegistry.sol";
78
import {IIndexRegistry} from "./interfaces/IIndexRegistry.sol";
@@ -94,6 +95,81 @@ contract OperatorStateRetriever {
9495
return operators;
9596
}
9697

98+
/**
99+
* @notice This function is intended to to be called by AVS operators every time a new task is created (i.e.)
100+
* the AVS coordinator makes a request to AVS operators. Since all of the crucial information is kept onchain,
101+
* operators don't need to run indexers to fetch the data.
102+
* @param registryCoordinator is the registry coordinator to fetch the AVS registry information from
103+
* @param operatorId the id of the operator to fetch the quorums lists
104+
* @param blockNumber is the block number to get the operator state for
105+
* @return quorumBitmap the quorumBitmap of the operator at the given blockNumber
106+
* @return operators a 2d array of Operators. For each quorum, an ordered list of Operators
107+
* @return sockets a 2d array of sockets. For each quorum, an ordered list of sockets
108+
*/
109+
function getOperatorStateWithSocket(
110+
ISlashingRegistryCoordinator registryCoordinator,
111+
bytes32 operatorId,
112+
uint32 blockNumber
113+
) external view returns (uint256 quorumBitmap, Operator[][] memory operators, string[][] memory sockets) {
114+
bytes32[] memory operatorIds = new bytes32[](1);
115+
operatorIds[0] = operatorId;
116+
uint256 index = registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0];
117+
118+
quorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index);
119+
120+
bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap);
121+
122+
(operators, sockets) =
123+
getOperatorStateWithSocket(registryCoordinator, quorumNumbers, blockNumber);
124+
}
125+
126+
/// @dev Used below to avoid stack too deep.
127+
struct Registries {
128+
IStakeRegistry stakeRegistry;
129+
IIndexRegistry indexRegistry;
130+
IBLSApkRegistry blsApkRegistry;
131+
ISocketRegistry socketRegistry;
132+
}
133+
134+
/**
135+
* @notice returns the ordered list of operators (id, stake, socket) for each quorum. The AVS coordinator
136+
* may call this function directly to get the operator state for a given block number
137+
* @param registryCoordinator is the registry coordinator to fetch the AVS registry information from
138+
* @param quorumNumbers are the ids of the quorums to get the operator state for
139+
* @param blockNumber is the block number to get the operator state for
140+
* @return operators a 2d array of Operators. For each quorum, an ordered list of Operators
141+
* @return sockets a 2d array of sockets. For each quorum, an ordered list of sockets
142+
*/
143+
function getOperatorStateWithSocket(
144+
ISlashingRegistryCoordinator registryCoordinator,
145+
bytes memory quorumNumbers,
146+
uint32 blockNumber
147+
) public view returns (Operator[][] memory operators, string[][] memory sockets) {
148+
Registries memory registries = Registries({
149+
stakeRegistry: registryCoordinator.stakeRegistry(),
150+
indexRegistry: registryCoordinator.indexRegistry(),
151+
blsApkRegistry: registryCoordinator.blsApkRegistry(),
152+
socketRegistry: registryCoordinator.socketRegistry()
153+
});
154+
155+
operators = new Operator[][](quorumNumbers.length);
156+
sockets = new string[][](quorumNumbers.length);
157+
for (uint256 i = 0; i < quorumNumbers.length; i++) {
158+
uint8 quorumNumber = uint8(quorumNumbers[i]);
159+
bytes32[] memory operatorIds = registries.indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber);
160+
operators[i] = new Operator[](operatorIds.length);
161+
sockets[i] = new string[](operatorIds.length);
162+
for (uint256 j = 0; j < operatorIds.length; j++) {
163+
operators[i][j] = Operator({
164+
operator: registries.blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]),
165+
operatorId: bytes32(operatorIds[j]),
166+
stake: registries.stakeRegistry.getStakeAtBlockNumber(bytes32(operatorIds[j]), quorumNumber, blockNumber)
167+
});
168+
sockets[i][j] = registries.socketRegistry.getOperatorSocket(bytes32(operatorIds[j]));
169+
}
170+
}
171+
}
172+
97173
/**
98174
* @notice this is called by the AVS operator to get the relevant indices for the checkSignatures function
99175
* if they are not running an indexer

test/unit/OperatorStateRetrieverUnit.t.sol

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pragma solidity ^0.8.27;
33

44
import "../utils/MockAVSDeployer.sol";
5-
import {IStakeRegistryErrors} from "../../src/interfaces/IStakeRegistry.sol";
5+
import {IStakeRegistryErrors, IStakeRegistryTypes} from "../../src/interfaces/IStakeRegistry.sol";
66
import {ISlashingRegistryCoordinatorTypes} from "../../src/interfaces/IRegistryCoordinator.sol";
77

88
contract OperatorStateRetrieverUnitTests is MockAVSDeployer {
@@ -144,6 +144,136 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer {
144144
assertEq(operators[1][0].stake, defaultStake - 1);
145145
}
146146

147+
function test_getOperatorStateWithSocket_revert_neverRegistered() public {
148+
cheats.expectRevert(
149+
"RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId"
150+
);
151+
operatorStateRetriever.getOperatorStateWithSocket(
152+
registryCoordinator, defaultOperatorId, uint32(block.number)
153+
);
154+
}
155+
156+
157+
158+
function test_getOperatorStateWithSocket_revert_registeredFirstAfterReferenceBlockNumber() public {
159+
cheats.roll(registrationBlockNumber);
160+
_registerOperatorWithCoordinator(defaultOperator, 1, defaultPubKey);
161+
162+
// should revert because the operator was registered for the first time after the reference block number
163+
cheats.expectRevert(
164+
"RegistryCoordinator.getQuorumBitmapIndexAtBlockNumber: no bitmap update found for operatorId"
165+
);
166+
operatorStateRetriever.getOperatorStateWithSocket(
167+
registryCoordinator, defaultOperatorId, registrationBlockNumber - 1
168+
);
169+
}
170+
171+
function test_getOperatorStateWithSocket_deregisteredBeforeReferenceBlockNumber() public {
172+
uint256 quorumBitmap = 1;
173+
cheats.roll(registrationBlockNumber);
174+
_registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey);
175+
176+
cheats.roll(registrationBlockNumber + 10);
177+
cheats.prank(defaultOperator);
178+
registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmap));
179+
180+
(uint256 fetchedQuorumBitmap, OperatorStateRetriever.Operator[][] memory operators,) =
181+
operatorStateRetriever.getOperatorStateWithSocket(
182+
registryCoordinator, defaultOperatorId, uint32(block.number)
183+
);
184+
assertEq(fetchedQuorumBitmap, 0);
185+
assertEq(operators.length, 0);
186+
}
187+
188+
function test_getOperatorStateWithSocket_registeredAtReferenceBlockNumber() public {
189+
uint256 quorumBitmap = 1;
190+
cheats.roll(registrationBlockNumber);
191+
_registerOperatorWithCoordinator(defaultOperator, quorumBitmap, defaultPubKey);
192+
193+
(uint256 fetchedQuorumBitmap, OperatorStateRetriever.Operator[][] memory operators,) =
194+
operatorStateRetriever.getOperatorStateWithSocket(
195+
registryCoordinator, defaultOperatorId, uint32(block.number)
196+
);
197+
assertEq(fetchedQuorumBitmap, 1);
198+
assertEq(operators.length, 1);
199+
assertEq(operators[0].length, 1);
200+
assertEq(operators[0][0].operator, defaultOperator);
201+
assertEq(operators[0][0].operatorId, defaultOperatorId);
202+
assertEq(operators[0][0].stake, defaultStake);
203+
}
204+
205+
function test_getOperatorStateWithSocket_revert_quorumNotCreatedAtCallTime() public {
206+
cheats.expectRevert(
207+
"IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number"
208+
);
209+
operatorStateRetriever.getOperatorStateWithSocket(
210+
registryCoordinator,
211+
BitmapUtils.bitmapToBytesArray(1 << numQuorums),
212+
uint32(block.number)
213+
);
214+
}
215+
216+
function test_getOperatorStateWithSocket_revert_quorumNotCreatedAtReferenceBlockNumber() public {
217+
cheats.roll(registrationBlockNumber);
218+
ISlashingRegistryCoordinator.OperatorSetParam memory operatorSetParams = ISlashingRegistryCoordinatorTypes
219+
.OperatorSetParam({
220+
maxOperatorCount: defaultMaxOperatorCount,
221+
kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake,
222+
kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake
223+
});
224+
uint96 minimumStake = 1;
225+
IStakeRegistry.StrategyParams[] memory strategyParams =
226+
new IStakeRegistry.StrategyParams[](1);
227+
strategyParams[0] =
228+
IStakeRegistryTypes.StrategyParams({strategy: IStrategy(address(1000)), multiplier: 1e16});
229+
230+
cheats.prank(registryCoordinator.owner());
231+
registryCoordinator.createTotalDelegatedStakeQuorum(operatorSetParams, minimumStake, strategyParams);
232+
233+
cheats.expectRevert(
234+
"IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number"
235+
);
236+
operatorStateRetriever.getOperatorStateWithSocket(
237+
registryCoordinator,
238+
BitmapUtils.bitmapToBytesArray(1 << numQuorums),
239+
uint32(registrationBlockNumber - 1)
240+
);
241+
}
242+
243+
244+
function test_getOperatorStateWithSocket_returnsCorrect() public {
245+
uint256 quorumBitmapOne = 1;
246+
uint256 quorumBitmapThree = 3;
247+
cheats.roll(registrationBlockNumber);
248+
_registerOperatorWithCoordinator(defaultOperator, quorumBitmapOne, defaultPubKey);
249+
250+
address otherOperator = _incrementAddress(defaultOperator, 1);
251+
BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2);
252+
bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey);
253+
_registerOperatorWithCoordinator(
254+
otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1
255+
);
256+
257+
(OperatorStateRetriever.Operator[][] memory operators,) = operatorStateRetriever
258+
.getOperatorStateWithSocket(
259+
registryCoordinator,
260+
BitmapUtils.bitmapToBytesArray(quorumBitmapThree),
261+
uint32(block.number)
262+
);
263+
assertEq(operators.length, 2);
264+
assertEq(operators[0].length, 2);
265+
assertEq(operators[1].length, 1);
266+
assertEq(operators[0][0].operator, defaultOperator);
267+
assertEq(operators[0][0].operatorId, defaultOperatorId);
268+
assertEq(operators[0][0].stake, defaultStake);
269+
assertEq(operators[0][1].operator, otherOperator);
270+
assertEq(operators[0][1].operatorId, otherOperatorId);
271+
assertEq(operators[0][1].stake, defaultStake - 1);
272+
assertEq(operators[1][0].operator, otherOperator);
273+
assertEq(operators[1][0].operatorId, otherOperatorId);
274+
assertEq(operators[1][0].stake, defaultStake - 1);
275+
}
276+
147277
function test_getCheckSignaturesIndices_revert_neverRegistered() public {
148278
bytes32[] memory nonSignerOperatorIds = new bytes32[](1);
149279
nonSignerOperatorIds[0] = defaultOperatorId;
@@ -630,7 +760,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer {
630760
OperatorStateRetriever.Operator[][] memory operators,
631761
uint256[][] memory expectedOperatorOverallIndices,
632762
OperatorMetadata[] memory operatorMetadatas
633-
) internal {
763+
) internal pure {
634764
// for each quorum
635765
for (uint256 j = 0; j < quorumNumbers.length; j++) {
636766
// make sure the each operator id and stake is correct

0 commit comments

Comments
 (0)