Skip to content

Commit

Permalink
feat(contracts): WIP update Symbiotic middleware, tests failing
Browse files Browse the repository at this point in the history
  • Loading branch information
mempirate committed Oct 8, 2024
1 parent 9c80589 commit 95650a5
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 149 deletions.
28 changes: 12 additions & 16 deletions bolt-contracts/src/contracts/BoltManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,17 @@ contract BoltManager is IBoltManager, Ownable {
/// @notice Allow an operator to signal indefinite opt-out from Bolt Protocol.
/// @dev Pausing activity does not prevent the operator from being slashable for
/// the current network epoch until the end of the slashing window.
function pauseOperator() public {
if (!operators.contains(msg.sender)) {
revert OperatorNotRegistered();
}

operators.disable(msg.sender);
function pauseOperator(
address operator
) external onlyMiddleware {
operators.disable(operator);
}

/// @notice Allow a disabled operator to signal opt-in to Bolt Protocol.
function unpauseOperator() public {
if (!operators.contains(msg.sender)) {
revert OperatorNotRegistered();
}

operators.enable(msg.sender);
function unpauseOperator(
address operator
) external onlyMiddleware {
operators.enable(operator);
}

/// @notice Check if an operator is currently enabled to work in Bolt Protocol.
Expand Down Expand Up @@ -216,17 +212,17 @@ contract BoltManager is IBoltManager, Ownable {
/// @notice Add a restaking protocol into Bolt
/// @param protocolMiddleware The address of the restaking protocol Bolt middleware
function addRestakingProtocol(
IBoltMiddleware protocolMiddleware
address protocolMiddleware
) public onlyOwner {
restakingProtocols.add(address(protocolMiddleware));
restakingProtocols.add(protocolMiddleware);
}

/// @notice Remove a restaking protocol from Bolt
/// @param protocolMiddleware The address of the restaking protocol Bolt middleware
function removeRestakingProtocol(
IBoltMiddleware protocolMiddleware
address protocolMiddleware
) public onlyOwner {
restakingProtocols.remove(address(protocolMiddleware));
restakingProtocols.remove(protocolMiddleware);
}

// ========= HELPER FUNCTIONS =========
Expand Down
145 changes: 35 additions & 110 deletions bolt-contracts/src/contracts/BoltSymbioticMiddleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ contract BoltSymbioticMiddleware is IBoltMiddleware, Ownable {
/// BLS pubkey and are assigned a sequence number.
IBoltManager public boltManager;

/// @notice Set of Symbiotic operator addresses that have opted in to Bolt Protocol.
EnumerableMap.AddressToUintMap private operators;

/// @notice Mapping of operator addresses to RPC endpoints.
mapping(address => string) private operatorRPCs;

/// @notice Set of Symbiotic protocol vaults that are used in Bolt Protocol.
EnumerableMap.AddressToUintMap private vaults;

Expand Down Expand Up @@ -130,20 +124,6 @@ contract BoltSymbioticMiddleware is IBoltMiddleware, Ownable {
return getEpochAtTs(Time.timestamp());
}

/// @notice Check if an operator address is authorized to work for a validator,
/// given the validator's pubkey hash. This function performs a lookup in the
/// validators registry to check if they explicitly authorized the operator.
/// @param operator The operator address to check the authorization for.
/// @param pubkeyHash The pubkey hash of the validator to check the authorization for.
/// @return True if the operator is authorized, false otherwise.
function isOperatorAuthorizedForValidator(address operator, bytes32 pubkeyHash) public view returns (bool) {
if (operator == address(0) || pubkeyHash == bytes32(0)) {
revert InvalidQuery();
}

return boltManager.validators().getValidatorByPubkeyHash(pubkeyHash).authorizedOperator == operator;
}

/// @notice Get the list of collateral addresses that are whitelisted.
/// @return collaterals The list of collateral addresses that are whitelisted.
function getWhitelistedCollaterals() public view returns (address[] memory collaterals) {
Expand Down Expand Up @@ -182,7 +162,7 @@ contract BoltSymbioticMiddleware is IBoltMiddleware, Ownable {
/// @notice Allow an operator to signal opt-in to Bolt Protocol.
/// @param operator The operator address to signal opt-in for.
function registerOperator(address operator, string calldata rpc) public {
if (operators.contains(operator)) {
if (boltManager.isOperator(operator)) {
revert AlreadyRegistered();
}

Expand All @@ -194,42 +174,37 @@ contract BoltSymbioticMiddleware is IBoltMiddleware, Ownable {
revert OperatorNotOptedIn();
}

operators.add(operator);
operators.enable(operator);

operatorRPCs[operator] = rpc;
boltManager.registerOperator(operator, rpc);
}

/// @notice Deregister a Symbiotic operator from working in Bolt Protocol.
/// @dev This does NOT deregister the operator from the Symbiotic network.
function deregisterOperator() public {
if (!operators.contains(msg.sender)) {
if (!boltManager.isOperator(msg.sender)) {
revert NotRegistered();
}

operators.remove(msg.sender);

delete operatorRPCs[msg.sender];
boltManager.deregisterOperator(msg.sender);
}

/// @notice Allow an operator to signal indefinite opt-out from Bolt Protocol.
/// @dev Pausing activity does not prevent the operator from being slashable for
/// the current network epoch until the end of the slashing window.
function pauseOperator() public {
if (!operators.contains(msg.sender)) {
if (!boltManager.isOperator(msg.sender)) {
revert NotRegistered();
}

operators.disable(msg.sender);
boltManager.pauseOperator(msg.sender);
}

/// @notice Allow a disabled operator to signal opt-in to Bolt Protocol.
function unpauseOperator() public {
if (!operators.contains(msg.sender)) {
if (!boltManager.isOperator(msg.sender)) {
revert NotRegistered();
}

operators.enable(msg.sender);
boltManager.unpauseOperator(msg.sender);
}

/// @notice Allow a vault to signal opt-in to Bolt Protocol.
Expand Down Expand Up @@ -283,66 +258,43 @@ contract BoltSymbioticMiddleware is IBoltMiddleware, Ownable {
return enabledTime != 0 && disabledTime == 0;
}

/// @notice Check if an operator is currently enabled to work in Bolt Protocol.
/// @param operator The operator address to check the enabled status for.
/// @return True if the operator is enabled, false otherwise.
function isOperatorEnabled(
/// @notice Get the collaterals and amounts staked by an operator across the supported strategies.
///
/// @param operator The operator address to get the collaterals and amounts staked for.
/// @return collaterals The collaterals staked by the operator.
/// @dev Assumes that the operator is registered and enabled.
function getOperatorCollaterals(
address operator
) public view returns (bool) {
(uint48 enabledTime, uint48 disabledTime) = operators.getTimes(operator);
return enabledTime != 0 && disabledTime == 0;
}

/// @notice Get the status of multiple proposers, given their pubkey hashes.
/// @param pubkeyHashes The pubkey hashes of the proposers to get the status for.
/// @return statuses The statuses of the proposers, including their operator and active stake.
function getProposersStatus(
bytes32[] calldata pubkeyHashes
) public view returns (IBoltValidators.ProposerStatus[] memory statuses) {
statuses = new IBoltValidators.ProposerStatus[](pubkeyHashes.length);
for (uint256 i = 0; i < pubkeyHashes.length; ++i) {
statuses[i] = getProposerStatus(pubkeyHashes[i]);
}
}

/// @notice Get the status of a proposer, given their pubkey hash.
/// @param pubkeyHash The pubkey hash of the proposer to get the status for.
/// @return status The status of the proposer, including their operator and active stake.
function getProposerStatus(
bytes32 pubkeyHash
) public view returns (IBoltValidators.ProposerStatus memory status) {
if (pubkeyHash == bytes32(0)) {
revert InvalidQuery();
}
) public view returns (address[] memory, uint256[] memory) {
address[] memory collateralTokens = new address[](vaults.length());
uint256[] memory amounts = new uint256[](vaults.length());

uint48 epochStartTs = getEpochStartTs(getEpochAtTs(Time.timestamp()));
IBoltValidators.Validator memory validator = boltManager.validators().getValidatorByPubkeyHash(pubkeyHash);
address operator = validator.authorizedOperator;

status.pubkeyHash = pubkeyHash;
status.active = validator.exists;
status.operator = operator;
status.operatorRPC = operatorRPCs[operator];

(uint48 enabledTime, uint48 disabledTime) = operators.getTimes(operator);
if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) {
return status;
}

status.collaterals = new address[](vaults.length());
status.amounts = new uint256[](vaults.length());

for (uint256 i = 0; i < vaults.length(); ++i) {
(address vault, uint48 enabledVaultTime, uint48 disabledVaultTime) = vaults.atWithTimes(i);
(address vault, uint48 enabledTime, uint48 disabledTime) = vaults.atWithTimes(i);

address collateral = IVault(vault).collateral();
status.collaterals[i] = collateral;
if (!_wasEnabledAt(enabledVaultTime, disabledVaultTime, epochStartTs)) {
if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) {
continue;
}

status.amounts[i] = getOperatorStakeAt(operator, collateral, epochStartTs);
address collateral = IVault(vault).collateral();
collateralTokens[i] = collateral;

// in order to have stake in a network, the operator needs to be opted in to that vault.
// this authorization is fully handled in the Vault, we just need to read the stake.
amounts[i] = IBaseDelegator(IVault(vault).delegator()).stakeAt(
// The stake for each subnetwork is stored in the vault's delegator contract.
// stakeAt returns the stake of "operator" at "timestamp" for "network" (or subnetwork)
// bytes(0) is for hints, which we don't currently use.
BOLT_SYMBIOTIC_NETWORK.subnetwork(0),
operator,
epochStartTs,
new bytes(0)
);
}

return (collateralTokens, amounts);
}

/// @notice Get the stake of an operator in Symbiotic protocol at the current timestamp.
Expand Down Expand Up @@ -397,33 +349,6 @@ contract BoltSymbioticMiddleware is IBoltMiddleware, Ownable {
return amount;
}

/// @notice Get the total stake of all Symbiotic operators at a given epoch for a collateral asset.
/// @param epoch The epoch to check the total stake for.
/// @param collateral The collateral address to check the total stake for.
/// @return totalStake The total stake of all operators at the given epoch, in collateral token.
function getTotalStake(uint48 epoch, address collateral) public view returns (uint256 totalStake) {
uint48 epochStartTs = getEpochStartTs(epoch);

// for epoch older than SLASHING_WINDOW total stake can be invalidated
if (
epochStartTs < SLASHING_WINDOW || epochStartTs < Time.timestamp() - SLASHING_WINDOW
|| epochStartTs > Time.timestamp()
) {
revert InvalidQuery();
}

for (uint256 i; i < operators.length(); ++i) {
(address operator, uint48 enabledTime, uint48 disabledTime) = operators.atWithTimes(i);

// just skip operator if it was added after the target epoch or paused
if (!_wasEnabledAt(enabledTime, disabledTime, epochStartTs)) {
continue;
}

totalStake += getOperatorStakeAt(operator, collateral, epochStartTs);
}
}

/// @notice Slash a given operator for a given amount of collateral.
/// @param timestamp The timestamp of the slash event.
/// @param operator The operator address to slash.
Expand Down
8 changes: 8 additions & 0 deletions bolt-contracts/src/interfaces/IBoltManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ interface IBoltManager {
address operator
) external;

function pauseOperator(
address operator
) external;

function unpauseOperator(
address operator
) external;

function isOperator(
address operator
) external view returns (bool);
Expand Down
37 changes: 22 additions & 15 deletions bolt-contracts/test/BoltManager.EigenLayer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {BoltValidators} from "../src/contracts/BoltValidators.sol";
import {BoltManager} from "../src/contracts/BoltManager.sol";
import {BoltEigenLayerMiddleware} from "../src/contracts/BoltEigenLayerMiddleware.sol";
import {IBoltValidators} from "../src/interfaces/IBoltValidators.sol";
import {IBoltManager} from "../src/interfaces/IBoltManager.sol";
import {IBoltMiddleware} from "../src/interfaces/IBoltMiddleware.sol";

import {AVSDirectoryStorage} from "@eigenlayer/src/contracts/core/AVSDirectoryStorage.sol";
Expand Down Expand Up @@ -58,13 +59,16 @@ contract BoltManagerEigenLayerTest is Test {
address(eigenLayerDeployer.delegationManager()),
address(eigenLayerDeployer.strategyManager())
);

// Register the middleware in the manager
vm.prank(admin);
manager.addRestakingProtocol(address(middleware));
}

function _adminRoutine() internal {
// PART 0: Admin setup -- Collateral whitelist
vm.startPrank(admin);
vm.prank(admin);
middleware.addWhitelistedCollateral(address(eigenLayerDeployer.weth()));
vm.stopPrank();
assertEq(middleware.getWhitelistedCollaterals().length, 1);
assertEq(middleware.getWhitelistedCollaterals()[0], address(eigenLayerDeployer.weth()));
}
Expand Down Expand Up @@ -144,7 +148,8 @@ contract BoltManagerEigenLayerTest is Test {
operator, address(middleware), IAVSDirectory.OperatorAVSRegistrationStatus.REGISTERED
);
middleware.registerOperator(operator, "https://bolt-rpc.io", operatorSignature);
assertEq(middleware.isOperatorEnabled(operator), true);

assertEq(manager.isOperatorEnabled(operator), true);

// PART 2: Validator and proposer opt into BOLT manager
//
Expand All @@ -168,25 +173,27 @@ contract BoltManagerEigenLayerTest is Test {
_eigenLayerOptInRoutine();
vm.prank(operator);
middleware.deregisterOperator();
vm.expectRevert(IBoltMiddleware.NotRegistered.selector);
middleware.isOperatorEnabled(operator);
vm.expectRevert(IBoltManager.OperatorNotRegistered.selector);
manager.isOperatorEnabled(operator);
}

function test_getEigenLayerOperatorStake() public {
_eigenLayerOptInRoutine();
// TODO:
// function test_getEigenLayerOperatorStake() public {
// _eigenLayerOptInRoutine();

uint256 amount = middleware.getOperatorStake(operator, address(eigenLayerDeployer.weth()));
uint256 totalStake = middleware.getTotalStake(2, address(eigenLayerDeployer.weth()));
assertEq(amount, 1 ether);
assertEq(totalStake, 1 ether);
}
// uint256 amount = middleware.getOperatorStake(operator, address(eigenLayerDeployer.weth()));
// // TODO:
// uint256 totalStake = middleware.getTotalStake(2, address(eigenLayerDeployer.weth()));
// assertEq(amount, 1 ether);
// assertEq(totalStake, 1 ether);
// }

function test_getEigenLayerProposerStatus() public {
_eigenLayerOptInRoutine();

bytes32 pubkeyHash = _pubkeyHash(validatorPubkey);

IBoltValidators.ProposerStatus memory status = middleware.getProposerStatus(pubkeyHash);
IBoltValidators.ProposerStatus memory status = manager.getProposerStatus(pubkeyHash);
assertEq(status.pubkeyHash, pubkeyHash);
assertEq(status.operator, operator);
assertEq(status.active, true);
Expand All @@ -211,7 +218,7 @@ contract BoltManagerEigenLayerTest is Test {
validators.registerValidatorUnsafe(pubkey, PRECONF_MAX_GAS_LIMIT, operator);
}

IBoltValidators.ProposerStatus[] memory statuses = middleware.getProposersStatus(pubkeyHashes);
IBoltValidators.ProposerStatus[] memory statuses = manager.getProposersStatus(pubkeyHashes);
assertEq(statuses.length, 10);
}

Expand All @@ -221,7 +228,7 @@ contract BoltManagerEigenLayerTest is Test {
bytes32 pubkeyHash = bytes32(uint256(1));

vm.expectRevert(IBoltValidators.ValidatorDoesNotExist.selector);
middleware.getProposerStatus(pubkeyHash);
manager.getProposerStatus(pubkeyHash);
}

function testGetWhitelistedCollaterals() public {
Expand Down
Loading

0 comments on commit 95650a5

Please sign in to comment.