Skip to content

Commit d477b66

Browse files
0x0aa0pakim249CAL
authored 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 <[email protected]> forge: fmt
1 parent f5adbca commit d477b66

File tree

2 files changed

+222
-2
lines changed

2 files changed

+222
-2
lines changed

src/OperatorStateRetriever.sol

+85
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,90 @@ 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+
)
114+
external
115+
view
116+
returns (uint256 quorumBitmap, Operator[][] memory operators, string[][] memory sockets)
117+
{
118+
bytes32[] memory operatorIds = new bytes32[](1);
119+
operatorIds[0] = operatorId;
120+
uint256 index =
121+
registryCoordinator.getQuorumBitmapIndicesAtBlockNumber(blockNumber, operatorIds)[0];
122+
123+
quorumBitmap =
124+
registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(operatorId, blockNumber, index);
125+
126+
bytes memory quorumNumbers = BitmapUtils.bitmapToBytesArray(quorumBitmap);
127+
128+
(operators, sockets) =
129+
getOperatorStateWithSocket(registryCoordinator, quorumNumbers, blockNumber);
130+
}
131+
132+
/// @dev Used below to avoid stack too deep.
133+
struct Registries {
134+
IStakeRegistry stakeRegistry;
135+
IIndexRegistry indexRegistry;
136+
IBLSApkRegistry blsApkRegistry;
137+
ISocketRegistry socketRegistry;
138+
}
139+
140+
/**
141+
* @notice returns the ordered list of operators (id, stake, socket) for each quorum. The AVS coordinator
142+
* may call this function directly to get the operator state for a given block number
143+
* @param registryCoordinator is the registry coordinator to fetch the AVS registry information from
144+
* @param quorumNumbers are the ids of the quorums to get the operator state for
145+
* @param blockNumber is the block number to get the operator state for
146+
* @return operators a 2d array of Operators. For each quorum, an ordered list of Operators
147+
* @return sockets a 2d array of sockets. For each quorum, an ordered list of sockets
148+
*/
149+
function getOperatorStateWithSocket(
150+
ISlashingRegistryCoordinator registryCoordinator,
151+
bytes memory quorumNumbers,
152+
uint32 blockNumber
153+
) public view returns (Operator[][] memory operators, string[][] memory sockets) {
154+
Registries memory registries = Registries({
155+
stakeRegistry: registryCoordinator.stakeRegistry(),
156+
indexRegistry: registryCoordinator.indexRegistry(),
157+
blsApkRegistry: registryCoordinator.blsApkRegistry(),
158+
socketRegistry: registryCoordinator.socketRegistry()
159+
});
160+
161+
operators = new Operator[][](quorumNumbers.length);
162+
sockets = new string[][](quorumNumbers.length);
163+
for (uint256 i = 0; i < quorumNumbers.length; i++) {
164+
uint8 quorumNumber = uint8(quorumNumbers[i]);
165+
bytes32[] memory operatorIds =
166+
registries.indexRegistry.getOperatorListAtBlockNumber(quorumNumber, blockNumber);
167+
operators[i] = new Operator[](operatorIds.length);
168+
sockets[i] = new string[](operatorIds.length);
169+
for (uint256 j = 0; j < operatorIds.length; j++) {
170+
operators[i][j] = Operator({
171+
operator: registries.blsApkRegistry.getOperatorFromPubkeyHash(operatorIds[j]),
172+
operatorId: bytes32(operatorIds[j]),
173+
stake: registries.stakeRegistry.getStakeAtBlockNumber(
174+
bytes32(operatorIds[j]), quorumNumber, blockNumber
175+
)
176+
});
177+
sockets[i][j] = registries.socketRegistry.getOperatorSocket(bytes32(operatorIds[j]));
178+
}
179+
}
180+
}
181+
97182
/**
98183
* @notice this is called by the AVS operator to get the relevant indices for the checkSignatures function
99184
* if they are not running an indexer

test/unit/OperatorStateRetrieverUnit.t.sol

+137-2
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,141 @@ 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+
function test_getOperatorStateWithSocket_revert_registeredFirstAfterReferenceBlockNumber()
157+
public
158+
{
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()
217+
public
218+
{
219+
cheats.roll(registrationBlockNumber);
220+
ISlashingRegistryCoordinator.OperatorSetParam memory operatorSetParams =
221+
ISlashingRegistryCoordinatorTypes.OperatorSetParam({
222+
maxOperatorCount: defaultMaxOperatorCount,
223+
kickBIPsOfOperatorStake: defaultKickBIPsOfOperatorStake,
224+
kickBIPsOfTotalStake: defaultKickBIPsOfTotalStake
225+
});
226+
uint96 minimumStake = 1;
227+
IStakeRegistry.StrategyParams[] memory strategyParams =
228+
new IStakeRegistry.StrategyParams[](1);
229+
strategyParams[0] = IStakeRegistryTypes.StrategyParams({
230+
strategy: IStrategy(address(1000)),
231+
multiplier: 1e16
232+
});
233+
234+
cheats.prank(registryCoordinator.owner());
235+
registryCoordinator.createTotalDelegatedStakeQuorum(
236+
operatorSetParams, minimumStake, strategyParams
237+
);
238+
239+
cheats.expectRevert(
240+
"IndexRegistry._operatorCountAtBlockNumber: quorum did not exist at given block number"
241+
);
242+
operatorStateRetriever.getOperatorStateWithSocket(
243+
registryCoordinator,
244+
BitmapUtils.bitmapToBytesArray(1 << numQuorums),
245+
uint32(registrationBlockNumber - 1)
246+
);
247+
}
248+
249+
function test_getOperatorStateWithSocket_returnsCorrect() public {
250+
uint256 quorumBitmapOne = 1;
251+
uint256 quorumBitmapThree = 3;
252+
cheats.roll(registrationBlockNumber);
253+
_registerOperatorWithCoordinator(defaultOperator, quorumBitmapOne, defaultPubKey);
254+
255+
address otherOperator = _incrementAddress(defaultOperator, 1);
256+
BN254.G1Point memory otherPubKey = BN254.G1Point(1, 2);
257+
bytes32 otherOperatorId = BN254.hashG1Point(otherPubKey);
258+
_registerOperatorWithCoordinator(
259+
otherOperator, quorumBitmapThree, otherPubKey, defaultStake - 1
260+
);
261+
262+
(OperatorStateRetriever.Operator[][] memory operators,) = operatorStateRetriever
263+
.getOperatorStateWithSocket(
264+
registryCoordinator,
265+
BitmapUtils.bitmapToBytesArray(quorumBitmapThree),
266+
uint32(block.number)
267+
);
268+
assertEq(operators.length, 2);
269+
assertEq(operators[0].length, 2);
270+
assertEq(operators[1].length, 1);
271+
assertEq(operators[0][0].operator, defaultOperator);
272+
assertEq(operators[0][0].operatorId, defaultOperatorId);
273+
assertEq(operators[0][0].stake, defaultStake);
274+
assertEq(operators[0][1].operator, otherOperator);
275+
assertEq(operators[0][1].operatorId, otherOperatorId);
276+
assertEq(operators[0][1].stake, defaultStake - 1);
277+
assertEq(operators[1][0].operator, otherOperator);
278+
assertEq(operators[1][0].operatorId, otherOperatorId);
279+
assertEq(operators[1][0].stake, defaultStake - 1);
280+
}
281+
147282
function test_getCheckSignaturesIndices_revert_neverRegistered() public {
148283
bytes32[] memory nonSignerOperatorIds = new bytes32[](1);
149284
nonSignerOperatorIds[0] = defaultOperatorId;
@@ -630,7 +765,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer {
630765
OperatorStateRetriever.Operator[][] memory operators,
631766
uint256[][] memory expectedOperatorOverallIndices,
632767
OperatorMetadata[] memory operatorMetadatas
633-
) internal {
768+
) internal pure {
634769
// for each quorum
635770
for (uint256 j = 0; j < quorumNumbers.length; j++) {
636771
// make sure the each operator id and stake is correct

0 commit comments

Comments
 (0)