From 837ae96751859a096cb9d595eda354fb2b8f2247 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Tue, 1 Oct 2024 18:21:44 +0400 Subject: [PATCH 001/115] feat: new sdk --- foundry.toml | 1 + src/KeyManager.sol | 85 ++ src/KeyManager32.sol | 85 ++ src/KeyRegistry.sol | 79 -- src/KeyRegistry32.sol | 79 -- src/OperatorManager.sol | 112 +++ src/VaultConnector.sol | 210 ----- src/VaultManager.sol | 237 ++++++ .../simple-network/SimpleMiddleware.sol | 232 ++---- src/libraries/AddressWithTimes.sol | 73 ++ src/libraries/BitMaps.sol | 51 -- src/libraries/KeyData.sol | 22 - src/libraries/Subsets.sol | 120 --- test/SimpleMiddleware.t.sol | 724 +++++++++--------- 14 files changed, 1023 insertions(+), 1087 deletions(-) create mode 100644 src/KeyManager.sol create mode 100644 src/KeyManager32.sol delete mode 100644 src/KeyRegistry.sol delete mode 100644 src/KeyRegistry32.sol create mode 100644 src/OperatorManager.sol delete mode 100644 src/VaultConnector.sol create mode 100644 src/VaultManager.sol create mode 100644 src/libraries/AddressWithTimes.sol delete mode 100644 src/libraries/BitMaps.sol delete mode 100644 src/libraries/KeyData.sol delete mode 100644 src/libraries/Subsets.sol diff --git a/foundry.toml b/foundry.toml index 465d324..99d155a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,4 +2,5 @@ src = "src" out = "out" libs = ["lib"] +via_ir = true # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/src/KeyManager.sol b/src/KeyManager.sol new file mode 100644 index 0000000..1a42b61 --- /dev/null +++ b/src/KeyManager.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {AddressWithTimes} from "./libraries/AddressWithTimes.sol"; + +contract KeyManager is Ownable { + using AddressWithTimes for AddressWithTimes.Address; + + error DuplicateKey(); + error NotExistKey(); + error NoOperatorKey(); + + mapping(address => bytes) public keys; + mapping(bytes => AddressWithTimes.Address) public keyData; + uint48 public immutable SLASHING_WINDOW; + + constructor(address owner, uint48 slashingWindow) Ownable(owner) { + SLASHING_WINDOW = slashingWindow; + } + + function operatorByKey(bytes memory key) external view returns (address) { + return keyData[key].getAddress(); + } + + function operatorKey(address operator) external view returns (bytes memory) { + return keys[operator]; + } + + function keyWasActiveAt(bytes memory key, uint48 timestamp) external view returns (bool) { + return keyData[key].wasActiveAt(timestamp); + } + + function registerKey(address operator, bytes memory key) external onlyOwner { + if (keyData[key].getAddress() != address(0)) { + revert DuplicateKey(); + } + + keys[operator] = key; + keyData[key].set(operator); + } + + function pauseKey(bytes memory key) external onlyOwner { + if (keyData[key].getAddress() == address(0)) { + revert NotExistKey(); + } + + keyData[key].disable(); + } + + function unpauseKey(bytes memory key) external onlyOwner { + if (keyData[key].getAddress() == address(0)) { + revert NotExistKey(); + } + + keyData[key].checkUnpause(SLASHING_WINDOW); + keyData[key].enable(); + } + + function unregisterKey(bytes memory key) external onlyOwner { + if (keyData[key].getAddress() == address(0)) { + revert NotExistKey(); + } + + keyData[key].checkUnregister(SLASHING_WINDOW); + delete keys[keyData[key].getAddress()]; + delete keyData[key]; + } + + function updateKey(address operator, bytes memory key) external onlyOwner { + if (keyData[keys[operator]].getAddress() == address(0)) { + revert NoOperatorKey(); + } + + if (keyData[key].getAddress() != address(0)) { + revert DuplicateKey(); + } + + keyData[keys[operator]].checkUnregister(SLASHING_WINDOW); + keys[operator] = key; + keyData[key].set(operator); + } +} diff --git a/src/KeyManager32.sol b/src/KeyManager32.sol new file mode 100644 index 0000000..5dd1ee8 --- /dev/null +++ b/src/KeyManager32.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {AddressWithTimes} from "./libraries/AddressWithTimes.sol"; + +contract KeyManager32 is Ownable { + using AddressWithTimes for AddressWithTimes.Address; + + error DuplicateKey(); + error NotExistKey(); + error NoOperatorKey(); + + mapping(address => bytes32) public keys; + mapping(bytes32 => AddressWithTimes.Address) public keyData; + uint48 public immutable SLASHING_WINDOW; + + constructor(address owner, uint48 slashingWindow) Ownable(owner) { + SLASHING_WINDOW = slashingWindow; + } + + function operatorByKey(bytes32 key) external view returns (address) { + return keyData[key].getAddress(); + } + + function operatorKey(address operator) external view returns (bytes32) { + return keys[operator]; + } + + function keyWasActiveAt(bytes32 key, uint48 timestamp) external view returns (bool) { + return keyData[key].wasActiveAt(timestamp); + } + + function registerKey(address operator, bytes32 key) external onlyOwner { + if (keyData[key].getAddress() != address(0)) { + revert DuplicateKey(); + } + + keys[operator] = key; + keyData[key].set(operator); + } + + function pauseKey(bytes32 key) external onlyOwner { + if (keyData[key].getAddress() == address(0)) { + revert NotExistKey(); + } + + keyData[key].disable(); + } + + function unpauseKey(bytes32 key) external onlyOwner { + if (keyData[key].getAddress() == address(0)) { + revert NotExistKey(); + } + + keyData[key].checkUnpause(SLASHING_WINDOW); + keyData[key].enable(); + } + + function unregisterKey(bytes32 key) external onlyOwner { + if (keyData[key].getAddress() == address(0)) { + revert NotExistKey(); + } + + keyData[key].checkUnregister(SLASHING_WINDOW); + delete keys[keyData[key].getAddress()]; + delete keyData[key]; + } + + function updateKey(address operator, bytes32 key) external onlyOwner { + if (keyData[keys[operator]].getAddress() == address(0)) { + revert NoOperatorKey(); + } + + if (keyData[key].getAddress() != address(0)) { + revert DuplicateKey(); + } + + keyData[keys[operator]].checkUnregister(SLASHING_WINDOW); + keys[operator] = key; + keyData[key].set(operator); + } +} diff --git a/src/KeyRegistry.sol b/src/KeyRegistry.sol deleted file mode 100644 index 742a2cd..0000000 --- a/src/KeyRegistry.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; - -import {BitMaps} from "./libraries/BitMaps.sol"; -import {Subsets} from "./libraries/Subsets.sol"; -import {KeyData} from "./libraries/KeyData.sol"; - -library KeyRegistry { - using Checkpoints for Checkpoints.Trace208; - using BitMaps for BitMaps.BitMap; - using KeyData for uint256; - - error DuplicateKey(); - - struct Registry { - mapping(address => bytes[]) keys; - mapping(address => BitMaps.BitMap) keysStatus; - mapping(bytes => uint256) keyData; - } - - function getOperatorByKey(Registry storage self, bytes memory key) internal view returns (address) { - return self.keyData[key].getOperator(); - } - - function getKeyPosition(Registry storage self, bytes memory key) internal view returns (uint256) { - return self.keyData[key].getPosition(); - } - - function getOperatorKeys(Registry storage self, address operator) internal view returns (bytes[] memory) { - return self.keys[operator]; - } - - function OperatorKeyCount(Registry storage self, address operator) internal view returns (uint256) { - return self.keys[operator].length; - } - - function getEnabledOperatorKeys(Registry storage self, address operator) internal view returns (bytes[] memory) { - return Subsets.getEnabledSubset(self.keys[operator], self.keysStatus[operator], Time.timestamp()); - } - - function getEnabledOperatorKeysAt(Registry storage self, address operator, uint48 timestamp) - internal - view - returns (bytes[] memory) - { - return Subsets.getEnabledSubset(self.keys[operator], self.keysStatus[operator], timestamp); - } - - function registerOperatorKey(Registry storage self, address operator, bytes memory key) internal { - if (self.keyData[key].getOperator() != address(0)) { - revert DuplicateKey(); - } - - self.keyData[key] = KeyData.pack(operator, uint96(self.keys[operator].length)); - self.keys[operator].push(key); - } - - function enableOperatorKeys(Registry storage self, address operator, uint256[] memory positions) internal { - Subsets.enableSubset(self.keysStatus[operator], positions, self.keys[operator].length); - } - - function enableOperatorKey(Registry storage self, address operator, uint256 position) internal { - Subsets.enable(self.keysStatus[operator], position, self.keys[operator].length); - } - - function disableOperatorKeys(Registry storage self, address operator, uint256[] memory positions) internal { - Subsets.disableSubset(self.keysStatus[operator], positions, self.keys[operator].length); - } - - function disableOperatorKey(Registry storage self, address operator, uint256 position) internal { - Subsets.disable(self.keysStatus[operator], position, self.keys[operator].length); - } -} diff --git a/src/KeyRegistry32.sol b/src/KeyRegistry32.sol deleted file mode 100644 index 6aa240a..0000000 --- a/src/KeyRegistry32.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; - -import {BitMaps} from "./libraries/BitMaps.sol"; -import {Subsets} from "./libraries/Subsets.sol"; -import {KeyData} from "./libraries/KeyData.sol"; - -library KeyRegistry32 { - using Checkpoints for Checkpoints.Trace208; - using BitMaps for BitMaps.BitMap; - using KeyData for uint256; - - error DuplicateKey(); - - struct Registry { - mapping(address => bytes32[]) keys; - mapping(address => BitMaps.BitMap) keysStatus; - mapping(bytes32 => uint256) keyData; - } - - function getOperatorByKey(Registry storage self, bytes32 key) internal view returns (address) { - return self.keyData[key].getOperator(); - } - - function getKeyPosition(Registry storage self, bytes32 key) internal view returns (uint256) { - return self.keyData[key].getPosition(); - } - - function getOperatorKeys(Registry storage self, address operator) internal view returns (bytes32[] memory) { - return self.keys[operator]; - } - - function OperatorKeyCount(Registry storage self, address operator) internal view returns (uint256) { - return self.keys[operator].length; - } - - function getEnabledOperatorKeys(Registry storage self, address operator) internal view returns (bytes32[] memory) { - return Subsets.getEnabledSubset(self.keys[operator], self.keysStatus[operator], Time.timestamp()); - } - - function getEnabledOperatorKeysAt(Registry storage self, address operator, uint48 timestamp) - internal - view - returns (bytes32[] memory) - { - return Subsets.getEnabledSubset(self.keys[operator], self.keysStatus[operator], timestamp); - } - - function registerOperatorKey(Registry storage self, address operator, bytes32 key) internal { - if (self.keyData[key].getOperator() != address(0)) { - revert DuplicateKey(); - } - - self.keyData[key] = KeyData.pack(operator, uint96(self.keys[operator].length)); - self.keys[operator].push(key); - } - - function enableOperatorKeys(Registry storage self, address operator, uint256[] memory positions) internal { - Subsets.enableSubset(self.keysStatus[operator], positions, self.keys[operator].length); - } - - function enableOperatorKey(Registry storage self, address operator, uint256 position) internal { - Subsets.enable(self.keysStatus[operator], position, self.keys[operator].length); - } - - function disableOperatorKeys(Registry storage self, address operator, uint256[] memory positions) internal { - Subsets.disableSubset(self.keysStatus[operator], positions, self.keys[operator].length); - } - - function disableOperatorKey(Registry storage self, address operator, uint256 position) internal { - Subsets.disable(self.keysStatus[operator], position, self.keys[operator].length); - } -} diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol new file mode 100644 index 0000000..56ad417 --- /dev/null +++ b/src/OperatorManager.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; +import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +import {AddressWithTimes} from "./libraries/AddressWithTimes.sol"; + +contract OperatorManager is Ownable { + using AddressWithTimes for AddressWithTimes.Address; + + error NotOperator(); + error OperatorNotOptedIn(); + error OperatorNotRegistered(); + error OperatorAlreadyRegistred(); + + error SlashPeriodNotPassed(); + + address public immutable NETWORK; + address public immutable OPERATOR_REGISTRY; + address public immutable OPERATOR_NET_OPTIN; + uint48 public immutable SLASHING_WINDOW; + AddressWithTimes.Address[] public operators; + mapping(address => uint256) public operatorPositions; + + constructor( + address owner, + address network, + address operatorRegistry, + address operatorNetOptIn, + uint48 slashingWindow + ) Ownable(owner) { + NETWORK = network; + OPERATOR_REGISTRY = operatorRegistry; + OPERATOR_NET_OPTIN = operatorNetOptIn; + SLASHING_WINDOW = slashingWindow; + } + + function activeOperators(uint48 timestamp) public view returns (address[] memory) { + address[] memory _operators = new address[](operators.length); + uint256 len = 0; + for (uint256 i; i < operators.length; ++i) { + if (!operators[i].wasActiveAt(timestamp)) { + continue; + } + + _operators[len++] = operators[i].getAddress(); + } + + // shrink array to skip unused slots + /// @solidity memory-safe-assembly + assembly { + mstore(_operators, len) + } + + return _operators; + } + + function registerOperator(address operator) external onlyOwner { + if (operatorPositions[operator] != 0) { + revert OperatorAlreadyRegistred(); + } + + if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { + revert NotOperator(); + } + + if (!IOptInService(OPERATOR_NET_OPTIN).isOptedIn(operator, NETWORK)) { + revert OperatorNotOptedIn(); + } + + uint256 pos = operators.length; + operators.push(); + operators[pos].set(operator); + operatorPositions[operator] = pos + 1; + } + + function pauseOperator(address operator) external onlyOwner { + if (operatorPositions[operator] == 0) { + revert OperatorNotRegistered(); + } + + operators[operatorPositions[operator] - 1].disable(); + } + + function unpauseOperator(address operator) external onlyOwner { + if (operatorPositions[operator] == 0) { + revert OperatorNotRegistered(); + } + + operators[operatorPositions[operator] - 1].checkUnpause(SLASHING_WINDOW); + operators[operatorPositions[operator] - 1].enable(); + } + + function unregisterOperator(address operator) external onlyOwner { + if (operatorPositions[operator] == 0) { + revert OperatorNotRegistered(); + } + + uint256 pos = operatorPositions[operator] - 1; + operators[pos].checkUnregister(SLASHING_WINDOW); + operators[pos] = operators[operators.length - 1]; + operators.pop(); + + delete operatorPositions[operator]; + operatorPositions[operators[pos].getAddress()] = pos + 1; + } +} diff --git a/src/VaultConnector.sol b/src/VaultConnector.sol deleted file mode 100644 index 173f7e2..0000000 --- a/src/VaultConnector.sol +++ /dev/null @@ -1,210 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; -import {IEntity} from "@symbiotic/interfaces/common/IEntity.sol"; -import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; -import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; - -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - -import {BitMaps} from "./libraries/BitMaps.sol"; -import {Subsets} from "./libraries/Subsets.sol"; - -abstract contract VaultConnector { - using EnumerableSet for EnumerableSet.AddressSet; - using Subnetwork for address; - using BitMaps for BitMaps.BitMap; - - error NotVault(); - error NotExistVault(); - error VaultAlreadyRegistred(); - error VaultGracePeriodNotPassed(); - error VaultEpochTooShort(); - error InvalidVaultsPositions(); - - error TooOldEpoch(); - error InvalidEpoch(); - - error InvalidSubnetworksCnt(); - - error UnknownSlasherType(); - - address public immutable NETWORK; - address public immutable VAULT_REGISTRY; - uint48 public immutable SLASHING_WINDOW; - uint48 public constant INSTANT_SLASHER_TYPE = 0; - uint48 public constant VETO_SLASHER_TYPE = 1; - uint256 public subnetworks; - EnumerableSet.AddressSet internal sharedVaults; - BitMaps.BitMap internal sharedVaultsStatus; - EnumerableSet.AddressSet internal operatorVaults; - mapping(address => BitMaps.BitMap) internal operatorVaultsStatus; - - constructor(address _network, address _vaultRegistry, uint48 _slashingWindow) { - NETWORK = _network; - VAULT_REGISTRY = _vaultRegistry; - SLASHING_WINDOW = _slashingWindow; - subnetworks = 1; - } - - function getOperatorVaults() external view returns (address[] memory) { - return operatorVaults.values(); - } - - function getSharedVaults() external view returns (address[] memory) { - return sharedVaults.values(); - } - - function _getEnabledSharedVaults(uint48 timestamp) internal view returns (address[] memory vaults) { - return Subsets.getEnabledSubset(sharedVaults, sharedVaultsStatus, timestamp); - } - - function _getEnabledOperatorVaults(address operator, uint48 timestamp) - internal - view - returns (address[] memory vaults) - { - address[] memory enabledSharedVaults = _getEnabledSharedVaults(timestamp); - address[] memory enabledOperatorVaults = - Subsets.getEnabledSubset(operatorVaults, operatorVaultsStatus[operator], timestamp); - vaults = new address[](enabledSharedVaults.length + enabledOperatorVaults.length); - - for (uint256 i = 0; i < enabledSharedVaults.length; ++i) { - vaults[i] = enabledSharedVaults[i]; - } - - uint256 sharedVaultsLen = enabledSharedVaults.length; - - for (uint256 i = 0; i < enabledOperatorVaults.length; ++i) { - vaults[i + sharedVaultsLen] = enabledOperatorVaults[i + sharedVaultsLen]; - } - - return vaults; - } - - function _setSubnetworks(uint256 _subnetworks) internal { - if (subnetworks >= _subnetworks) { - revert InvalidSubnetworksCnt(); - } - - subnetworks = _subnetworks; - } - - function _registerVault(address vault, bool isSharedVault) internal { - if (operatorVaults.contains(vault) || sharedVaults.contains(vault)) { - revert VaultAlreadyRegistred(); - } - - if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { - revert NotVault(); - } - - uint48 vaultEpoch = IVault(vault).epochDuration(); - - address slasher = IVault(vault).slasher(); - if (slasher != address(0) && IEntity(slasher).TYPE() == VETO_SLASHER_TYPE) { - vaultEpoch -= IVetoSlasher(slasher).vetoDuration(); - } - - if (vaultEpoch < SLASHING_WINDOW) { - revert VaultEpochTooShort(); - } - - if (isSharedVault) { - sharedVaults.add(vault); - } else { - operatorVaults.add(vault); - } - } - - function _enableSharedVaults(uint256[] memory positions) internal { - Subsets.enableSubset(sharedVaultsStatus, positions, sharedVaults.length()); - } - - function _disableSharedVaults(uint256[] memory positions) internal { - Subsets.disableSubset(sharedVaultsStatus, positions, sharedVaults.length()); - } - - function _enableOperatorVaults(address operator, uint256[] memory positions) internal { - Subsets.enableSubset(operatorVaultsStatus[operator], positions, operatorVaults.length()); - } - - function _disableOperatorVaults(address operator, uint256[] memory positions) internal { - Subsets.disableSubset(operatorVaultsStatus[operator], positions, operatorVaults.length()); - } - - function _getOperatorStake(address operator, uint48 timestamp) internal view returns (uint256 stake) { - address[] memory vaults = _getEnabledOperatorVaults(operator, timestamp); - - for (uint256 i; i < vaults.length; ++i) { - address vault = vaults[i]; - for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { - bytes32 subnetwork = NETWORK.subnetwork(subnet); - stake += IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); - } - } - - return stake; - } - - function _calcTotalStake(uint48 timestamp, address[] memory operators) internal view returns (uint256 totalStake) { - if (timestamp < Time.timestamp() - SLASHING_WINDOW) { - revert TooOldEpoch(); - } - - if (timestamp > Time.timestamp()) { - revert InvalidEpoch(); - } - - for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = _getOperatorStake(operators[i], timestamp); - totalStake += operatorStake; - } - } - - function _slash(uint48 timestamp, address operator, uint256 amount) internal { - if (timestamp < Time.timestamp() - SLASHING_WINDOW) { - revert TooOldEpoch(); - } - - uint256 totalOperatorStake = _getOperatorStake(operator, timestamp); - - amount = Math.min(totalOperatorStake, amount); - - // simple pro-rata slasher - address[] memory vaults = _getEnabledOperatorVaults(operator, timestamp); - - for (uint256 i = 0; i < vaults.length; ++i) { - for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { - bytes32 subnetwork = NETWORK.subnetwork(subnet); - address vault = vaults[i]; - - uint256 vaultStake = - IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); - - _slashVault(timestamp, vault, subnetwork, operator, amount * vaultStake / totalOperatorStake); - } - } - } - - function _slashVault(uint48 timestamp, address vault, bytes32 subnetwork, address operator, uint256 amount) - private - { - address slasher = IVault(vault).slasher(); - uint256 slasherType = IEntity(slasher).TYPE(); - if (slasherType == INSTANT_SLASHER_TYPE) { - ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, new bytes(0)); - } else if (slasherType == VETO_SLASHER_TYPE) { - IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, new bytes(0)); - } else { - revert UnknownSlasherType(); - } - } -} diff --git a/src/VaultManager.sol b/src/VaultManager.sol new file mode 100644 index 0000000..a59b76e --- /dev/null +++ b/src/VaultManager.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; +import {IEntity} from "@symbiotic/interfaces/common/IEntity.sol"; +import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; +import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +import {AddressWithTimes} from "./libraries/AddressWithTimes.sol"; + +contract VaultManager is Ownable { + using AddressWithTimes for AddressWithTimes.Address; + using Subnetwork for address; + + error NotVault(); + error VaultNotRegistered(); + error VaultAlreadyRegistred(); + error VaultEpochTooShort(); + + error TooOldEpoch(); + error InvalidEpoch(); + + error InvalidSubnetworksCnt(); + + error SlashPeriodNotPassed(); + error UnknownSlasherType(); + + address public immutable NETWORK; + address public immutable VAULT_REGISTRY; + uint48 public immutable SLASHING_WINDOW; + uint48 public constant INSTANT_SLASHER_TYPE = 0; + uint48 public constant VETO_SLASHER_TYPE = 1; + uint256 public subnetworks; + AddressWithTimes.Address[] public sharedVaults; + mapping(address => AddressWithTimes.Address[]) public operatorVaults; + mapping(address => uint256) public vaultPositions; + + constructor(address owner, address network, address vaultRegistry, uint48 slashingWindow) Ownable(owner) { + NETWORK = network; + VAULT_REGISTRY = vaultRegistry; + SLASHING_WINDOW = slashingWindow; + subnetworks = 1; + } + + function activeVaults(address operator, uint48 timestamp) public view returns (address[] memory) { + address[] memory vaults = new address[](sharedVaults.length + operatorVaults[operator].length); + uint256 len = 0; + for (uint256 i; i < sharedVaults.length; ++i) { + if (!sharedVaults[i].wasActiveAt(timestamp)) { + continue; + } + + vaults[len++] = sharedVaults[i].getAddress(); + } + + for (uint256 i; i < operatorVaults[operator].length; ++i) { + if (!operatorVaults[operator][i].wasActiveAt(timestamp)) { + continue; + } + + vaults[len++] = operatorVaults[operator][i].getAddress(); + } + + // shrink array to skip unused slots + /// @solidity memory-safe-assembly + assembly { + mstore(vaults, len) + } + + return vaults; + } + + function setSubnetworks(uint256 _subnetworks) external onlyOwner { + if (subnetworks >= _subnetworks) { + revert InvalidSubnetworksCnt(); + } + + subnetworks = _subnetworks; + } + + function registerSharedVault(address vault) external onlyOwner { + if (vaultPositions[vault] != 0) { + revert VaultAlreadyRegistred(); + } + + _checkVault(vault); + + uint256 pos = sharedVaults.length; + sharedVaults.push(); + sharedVaults[pos].set(vault); + vaultPositions[vault] = pos + 1; + } + + function registerOperatorVault(address vault, address operator) external onlyOwner { + if (vaultPositions[vault] != 0) { + revert VaultAlreadyRegistred(); + } + + _checkVault(vault); + + uint256 pos = operatorVaults[operator].length; + operatorVaults[operator].push(); + operatorVaults[operator][pos].set(vault); + vaultPositions[vault] = pos + 1; + } + + function pauseSharedVault(address vault) external onlyOwner { + if (vaultPositions[vault] == 0) { + revert VaultNotRegistered(); + } + + sharedVaults[vaultPositions[vault] - 1].disable(); + } + + function unpauseSharedVault(address vault) external onlyOwner { + if (vaultPositions[vault] == 0) { + revert VaultNotRegistered(); + } + + sharedVaults[vaultPositions[vault] - 1].checkUnpause(SLASHING_WINDOW); + sharedVaults[vaultPositions[vault] - 1].enable(); + } + + function pauseOperatorVault(address vault) external onlyOwner { + if (vaultPositions[vault] == 0) { + revert VaultNotRegistered(); + } + + sharedVaults[vaultPositions[vault] - 1].disable(); + } + + function unpauseOperatorVault(address operator, address vault) external onlyOwner { + if (vaultPositions[vault] == 0) { + revert VaultNotRegistered(); + } + + operatorVaults[operator][vaultPositions[vault] - 1].checkUnpause(SLASHING_WINDOW); + operatorVaults[operator][vaultPositions[vault] - 1].enable(); + } + + function unregisterSharedVault(address vault) external onlyOwner { + if (vaultPositions[vault] == 0) { + revert VaultNotRegistered(); + } + + uint256 pos = vaultPositions[vault] - 1; + sharedVaults[pos].checkUnregister(SLASHING_WINDOW); + sharedVaults[pos] = sharedVaults[sharedVaults.length - 1]; + sharedVaults.pop(); + + delete vaultPositions[vault]; + vaultPositions[sharedVaults[pos].getAddress()] = pos + 1; + } + + function unregisterOperatorVault(address operator, address vault) external onlyOwner { + if (vaultPositions[vault] == 0) { + revert VaultNotRegistered(); + } + + uint256 pos = vaultPositions[vault] - 1; + operatorVaults[operator][pos].checkUnregister(SLASHING_WINDOW); + operatorVaults[operator][pos] = operatorVaults[operator][operatorVaults[operator].length - 1]; + operatorVaults[operator].pop(); + + delete vaultPositions[vault]; + vaultPositions[operatorVaults[operator][pos].getAddress()] = pos + 1; + } + + function getOperatorStake(address operator, uint48 timestamp) public view returns (uint256 stake) { + address[] memory vaults = activeVaults(operator, timestamp); + + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { + bytes32 subnetwork = NETWORK.subnetwork(subnet); + stake += IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); + } + } + + return stake; + } + + function calcTotalStake(uint48 timestamp, address[] memory operators) external view returns (uint256 totalStake) { + if (timestamp < Time.timestamp() - SLASHING_WINDOW) { + revert TooOldEpoch(); + } + + if (timestamp > Time.timestamp()) { + revert InvalidEpoch(); + } + + for (uint256 i; i < operators.length; ++i) { + uint256 operatorStake = getOperatorStake(operators[i], timestamp); + totalStake += operatorStake; + } + } + + // ONYL DELEGATECALL FROM MIDDLEWARE + function slashVault(uint48 timestamp, address vault, bytes32 subnetwork, address operator, uint256 amount) + external + { + address slasher = IVault(vault).slasher(); + uint256 slasherType = IEntity(slasher).TYPE(); + if (slasherType == INSTANT_SLASHER_TYPE) { + ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, new bytes(0)); + } else if (slasherType == VETO_SLASHER_TYPE) { + IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, new bytes(0)); + } else { + revert UnknownSlasherType(); + } + } + + function _checkVault(address vault) private view { + if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { + revert NotVault(); + } + + uint48 vaultEpoch = IVault(vault).epochDuration(); + + address slasher = IVault(vault).slasher(); + if (slasher != address(0) && IEntity(slasher).TYPE() == VETO_SLASHER_TYPE) { + vaultEpoch -= IVetoSlasher(slasher).vetoDuration(); + } + + if (vaultEpoch < SLASHING_WINDOW) { + revert VaultEpochTooShort(); + } + } +} diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index 3070301..ac5214c 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -1,72 +1,62 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; -import {IEntity} from "@symbiotic/interfaces/common/IEntity.sol"; import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {IBaseSlasher} from "@symbiotic/interfaces/slasher/IBaseSlasher.sol"; -import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; -import {IEntity} from "@symbiotic/interfaces/common/IEntity.sol"; import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -import {VaultConnector} from "../../VaultConnector.sol"; -import {KeyRegistry32} from "../../KeyRegistry32.sol"; -import {BitMaps} from "../../libraries/BitMaps.sol"; -import {Subsets} from "../../libraries/Subsets.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +import {VaultManager} from "../../VaultManager.sol"; +import {OperatorManager} from "../../OperatorManager.sol"; +import {KeyManager32} from "../../KeyManager32.sol"; -contract SimpleMiddleware is VaultConnector, Ownable { - using EnumerableSet for EnumerableSet.AddressSet; - using KeyRegistry32 for KeyRegistry32.Registry; - using BitMaps for BitMaps.BitMap; +contract SimpleMiddleware is Ownable { using Subnetwork for address; - error NotOperator(); - error OperatorNotOptedIn(); - error OperatorNotRegistred(); - error OperarorGracePeriodNotPassed(); - error OperatorAlreadyRegistred(); - error NotOperatorKey(); - error SlashingWindowTooShort(); + error TooOldEpoch(); + error InvalidEpoch(); + error InvalidSlash(); struct ValidatorData { uint256 stake; bytes32 key; } - address public immutable OPERATOR_REGISTRY; - address public immutable OPERATOR_NET_OPTIN; - address public immutable OWNER; + address public immutable NETWORK; uint48 public immutable EPOCH_DURATION; uint48 public immutable START_TIME; + uint48 public immutable SLASHING_WINDOW; - EnumerableSet.AddressSet private operators; - BitMaps.BitMap internal operatorsStatus; - KeyRegistry32.Registry private keyRegistry; + address public immutable vaultManager; + address public immutable operatorManager; + address public immutable keyManager; constructor( - address _network, - address _operatorRegistry, - address _vaultRegistry, - address _operatorNetOptin, - address _owner, - uint48 _epochDuration, - uint48 _slashingWindow - ) VaultConnector(_network, _vaultRegistry, _slashingWindow) Ownable(_owner) { - if (_slashingWindow < _epochDuration) { + address network, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + address owner, + uint48 epochDuration, + uint48 slashingWindow + ) Ownable(owner) { + if (slashingWindow < epochDuration) { revert SlashingWindowTooShort(); } + vaultManager = address(new VaultManager(owner, network, vaultRegistry, slashingWindow)); + operatorManager = + address(new OperatorManager(owner, network, operatorRegistry, operatorNetOptin, slashingWindow)); + keyManager = address(new KeyManager32(owner, slashingWindow)); + + NETWORK = network; START_TIME = Time.timestamp(); - EPOCH_DURATION = _epochDuration; - OWNER = _owner; - OPERATOR_REGISTRY = _operatorRegistry; - OPERATOR_NET_OPTIN = _operatorNetOptin; + EPOCH_DURATION = epochDuration; + SLASHING_WINDOW = slashingWindow; } function getEpochStartTs(uint48 epoch) public view returns (uint48 timestamp) { @@ -81,111 +71,6 @@ contract SimpleMiddleware is VaultConnector, Ownable { return getEpochAtTs(Time.timestamp()); } - function getEnabledVaults(uint48 epoch) external view returns (address[] memory _operatorVaults) { - uint48 epochStartTs = getEpochStartTs(epoch); - return _getEnabledSharedVaults(epochStartTs); - } - - function getEnabledOperators(uint48 epoch) public view returns (address[] memory _operatorVaults) { - uint48 epochStartTs = getEpochStartTs(epoch); - return Subsets.getEnabledSubset(operators, operatorsStatus, epochStartTs); - } - - function getOperatorByKey(bytes32 key) public view returns (address) { - return keyRegistry.getOperatorByKey(key); - } - - function getCurrentOperatorKey(address operator) public view returns (bytes32) { - return getOperatorKeyAt(operator, Time.timestamp()); - } - - function getOperatorKeyAt(address operator, uint48 timestamp) public view returns (bytes32) { - bytes32[] memory keys = keyRegistry.getEnabledOperatorKeysAt(operator, timestamp); - if (keys.length == 0) { - return bytes32(0); - } - return keys[0]; - } - - function setSubnetworks(uint256 _subnetworks) external onlyOwner { - _setSubnetworks(_subnetworks); - } - - function registerOperator(address operator, bytes32 key) external onlyOwner { - if (operators.contains(operator)) { - revert OperatorAlreadyRegistred(); - } - - if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { - revert NotOperator(); - } - - if (!IOptInService(OPERATOR_NET_OPTIN).isOptedIn(operator, NETWORK)) { - revert OperatorNotOptedIn(); - } - - keyRegistry.registerOperatorKey(operator, key); - keyRegistry.enableOperatorKey(operator, 0); - - operators.add(operator); - Subsets.enable(operatorsStatus, operators.length() - 1, operators.length()); - } - - function updateOperatorKey(address operator, bytes32 key) external onlyOwner { - if (!operators.contains(operator)) { - revert OperatorNotRegistred(); - } - - address keyOperator = getOperatorByKey(key); - - if (keyOperator == address(0)) { - keyRegistry.registerOperatorKey(operator, key); - keyRegistry.enableOperatorKey(operator, 0); - return; - } - - if (keyOperator != operator) { - revert NotOperatorKey(); - } - - bytes32 currentKey = getCurrentOperatorKey(operator); - uint256 currentKeyPosition = keyRegistry.getKeyPosition(currentKey); - keyRegistry.disableOperatorKey(operator, currentKeyPosition); - - uint256 position = keyRegistry.getKeyPosition(key); - keyRegistry.enableOperatorKey(operator, position); - } - - function enableOperator(address operator) external onlyOwner { - uint256 position = operators._inner._positions[bytes32(uint256(uint160(operator)))] - 1; - Subsets.enable(operatorsStatus, position, operators.length()); - } - - function disableOperator(address operator) external onlyOwner { - uint256 position = operators._inner._positions[bytes32(uint256(uint160(operator)))] - 1; - Subsets.disable(operatorsStatus, position, operators.length()); - } - - function registerVault(address vault) external onlyOwner { - _registerVault(vault, true); - Subsets.enable(sharedVaultsStatus, sharedVaults.length() - 1, sharedVaults.length()); - } - - function enableVault(address vault) external onlyOwner { - uint256 position = sharedVaults._inner._positions[bytes32(uint256(uint160(vault)))] - 1; - Subsets.enable(sharedVaultsStatus, position, sharedVaults.length()); - } - - function disableVault(address vault) external onlyOwner { - uint256 position = sharedVaults._inner._positions[bytes32(uint256(uint160(vault)))] - 1; - Subsets.disable(sharedVaultsStatus, position, sharedVaults.length()); - } - - function getOperatorStake(address operator, uint48 epoch) public view returns (uint256 stake) { - uint48 epochStartTs = getEpochStartTs(epoch); - return _getOperatorStake(operator, epochStartTs); - } - function getTotalStake(uint48 epoch) public view returns (uint256 totalStake) { uint48 epochStartTs = getEpochStartTs(epoch); @@ -197,44 +82,63 @@ contract SimpleMiddleware is VaultConnector, Ownable { revert InvalidEpoch(); } - address[] memory _operators = getEnabledOperators(epoch); + address[] memory operators = OperatorManager(operatorManager).activeOperators(epochStartTs); - for (uint256 i; i < _operators.length; ++i) { - uint256 operatorStake = _getOperatorStake(_operators[i], epochStartTs); + for (uint256 i; i < operators.length; ++i) { + uint256 operatorStake = VaultManager(vaultManager).getOperatorStake(operators[i], epochStartTs); totalStake += operatorStake; } } - function getValidatorSet(uint48 epoch) public view returns (ValidatorData[] memory validatorsData) { + function getValidatorSet(uint48 epoch) public view returns (ValidatorData[] memory validatorSet) { uint48 epochStartTs = getEpochStartTs(epoch); - validatorsData = new ValidatorData[](operators.length()); - uint256 valIdx = 0; - address[] memory _operators = getEnabledOperators(epoch); + address[] memory operators = OperatorManager(operatorManager).activeOperators(epochStartTs); + validatorSet = new ValidatorData[](operators.length); + uint256 len = 0; - for (uint256 i; i < _operators.length; ++i) { - address operator = _operators[i]; + for (uint256 i; i < operators.length; ++i) { + address operator = operators[i]; - bytes32 key = getOperatorKeyAt(operator, epochStartTs); - if (key == bytes32(0)) { + bytes32 key = KeyManager32(keyManager).operatorKey(operator); + if (key == bytes32(0) || !KeyManager32(keyManager).keyWasActiveAt(key, epochStartTs)) { continue; } - uint256 stake = _getOperatorStake(operator, epochStartTs); - - validatorsData[valIdx++] = ValidatorData(stake, key); + uint256 stake = VaultManager(vaultManager).getOperatorStake(operator, epochStartTs); + validatorSet[len++] = ValidatorData(stake, key); } // shrink array to skip unused slots /// @solidity memory-safe-assembly assembly { - mstore(validatorsData, valIdx) + mstore(validatorSet, len) } } // just for example, our devnets don't support slashing function slash(uint48 epoch, address operator, uint256 amount) public onlyOwner { uint48 epochStartTs = getEpochStartTs(epoch); - _slash(epochStartTs, operator, amount); + uint256 totalStake = VaultManager(vaultManager).getOperatorStake(operator, epochStartTs); + address[] memory vaults = VaultManager(vaultManager).activeVaults(operator, epochStartTs); + + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + for (uint96 subnet = 0; subnet < VaultManager(vaultManager).subnetworks(); ++subnet) { + bytes32 subnetwork = NETWORK.subnetwork(subnet); + uint256 stake = + IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, epochStartTs, ""); + uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); + (bool success,) = vaultManager.delegatecall( + abi.encodeWithSelector( + VaultManager.slashVault.selector, epochStartTs, vault, subnetwork, operator, slashAmount + ) + ); + + if (!success) { + revert InvalidSlash(); + } + } + } } } diff --git a/src/libraries/AddressWithTimes.sol b/src/libraries/AddressWithTimes.sol new file mode 100644 index 0000000..a86a94e --- /dev/null +++ b/src/libraries/AddressWithTimes.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + +/** + * @dev This library adds helper functions for an optimized enable/diasble time data storaging. + */ +library AddressWithTimes { + struct Address { + address _address; + uint48 enabled; + uint48 disabled; + } + + error AlreadyAdded(); + error AlreadyEnabled(); + error NotEnabled(); + error SlashPeriodNotPassed(); + + function getAddress(Address storage self) internal view returns (address) { + return self._address; + } + + function get(Address storage self) internal view returns (address, uint48, uint48) { + return (self._address, self.enabled, self.disabled); + } + + function set(Address storage self, address _address) internal { + self._address = _address; + self.enabled = Time.timestamp(); + self.disabled = 0; + } + + /** + * @dev Enable a given key. + */ + function enable(Address storage self) internal { + if (self.enabled != 0 && self.disabled == 0) { + revert AlreadyEnabled(); + } + + self.enabled = Time.timestamp(); + self.disabled = 0; + } + + /** + * @dev Disable a given key. + */ + function disable(Address storage self) internal { + if (self.enabled == 0 || self.disabled != 0) { + revert NotEnabled(); + } + + self.disabled = Time.timestamp(); + } + + function wasActiveAt(Address storage self, uint48 timestamp) internal view returns (bool) { + return self.enabled != 0 && self.enabled <= timestamp && (self.disabled == 0 || self.disabled >= timestamp); + } + + function checkUnpause(Address storage self, uint48 slashingWindow) internal view { + if (self.disabled + slashingWindow >= Time.timestamp()) { + revert SlashPeriodNotPassed(); + } + } + + function checkUnregister(Address storage self, uint48 slashingWindow) internal view { + if (self.disabled == 0 || self.disabled + slashingWindow >= Time.timestamp()) { + revert SlashPeriodNotPassed(); + } + } +} diff --git a/src/libraries/BitMaps.sol b/src/libraries/BitMaps.sol deleted file mode 100644 index 76b7201..0000000 --- a/src/libraries/BitMaps.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; - -library BitMaps { - using Checkpoints for Checkpoints.Trace208; - - struct BitMap { - mapping(uint256 bucket => Checkpoints.Trace208) _data; - } - - function get(BitMap storage bitmap, uint256 index, uint48 timestamp) internal view returns (bool) { - uint256 bucket = index / 208; - index %= 208; - uint208 mask = uint208(1 << index); - return bitmap._data[bucket].upperLookupRecent(timestamp) & mask != 0; - } - - /** - * @dev Sets the bit at `index` to the boolean `value`. - */ - function setTo(BitMap storage bitmap, uint256 index, bool value) internal { - if (value) { - set(bitmap, index); - } else { - unset(bitmap, index); - } - } - - /** - * @dev Sets the bit at `index`. - */ - function set(BitMap storage bitmap, uint256 index) internal { - uint256 bucket = index / 208; - index %= 208; - uint208 mask = uint208(1 << index); - bitmap._data[bucket].push(Time.timestamp(), bitmap._data[bucket].latest() | mask); - } - - /** - * @dev Unsets the bit at `index`. - */ - function unset(BitMap storage bitmap, uint256 index) internal { - uint256 bucket = index / 208; - index %= 208; - uint208 mask = uint208(1 << index); - bitmap._data[bucket].push(Time.timestamp(), bitmap._data[bucket].latest() & ~mask); - } -} diff --git a/src/libraries/KeyData.sol b/src/libraries/KeyData.sol deleted file mode 100644 index a062980..0000000 --- a/src/libraries/KeyData.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -library KeyData { - // Mask for uint96 (0xFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000000000000000) - uint256 private constant UINT96_MASK = (1 << 96) - 1; - - // Offset for address in the uint256 slot - uint256 private constant ADDRESS_SHIFT = 96; - - function pack(address operator, uint96 position) internal pure returns (uint256) { - return (uint256(position) | (uint256(uint160(operator)) << ADDRESS_SHIFT)); - } - - function getOperator(uint256 self) internal pure returns (address) { - return address(uint160(self >> ADDRESS_SHIFT)); - } - - function getPosition(uint256 self) internal pure returns (uint256) { - return self & UINT96_MASK; - } -} diff --git a/src/libraries/Subsets.sol b/src/libraries/Subsets.sol deleted file mode 100644 index c0eb386..0000000 --- a/src/libraries/Subsets.sol +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -import {BitMaps} from "./BitMaps.sol"; - -library Subsets { - using BitMaps for BitMaps.BitMap; - using EnumerableSet for EnumerableSet.AddressSet; - - error InvalidVaultsPositions(); - - function enableSubset(BitMaps.BitMap storage setStatus, uint256[] memory positions, uint256 setLength) external { - _verifyPositions(positions, setLength); - for (uint256 i = 0; i < positions.length; ++i) { - setStatus.set(positions[i]); - } - } - - function enable(BitMaps.BitMap storage setStatus, uint256 position, uint256 setLength) external { - _verifyPosition(position, setLength); - setStatus.set(position); - } - - function disableSubset(BitMaps.BitMap storage setStatus, uint256[] memory positions, uint256 setLength) external { - _verifyPositions(positions, setLength); - for (uint256 i = 0; i < positions.length; ++i) { - setStatus.unset(positions[i]); - } - } - - function disable(BitMaps.BitMap storage setStatus, uint256 position, uint256 setLength) external { - _verifyPosition(position, setLength); - setStatus.unset(position); - } - - function getEnabledSubset(EnumerableSet.AddressSet storage set, BitMaps.BitMap storage setStatus, uint48 timestamp) - external - view - returns (address[] memory) - { - address[] memory _set = set.values(); - uint256 length = 0; - for (uint256 i = 0; i < _set.length; ++i) { - if (setStatus.get(i, timestamp)) { - _set[length++] = _set[i]; - } - } - - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { - mstore(_set, length) - } - - return _set; - } - - function getEnabledSubset(bytes32[] storage set, BitMaps.BitMap storage setStatus, uint48 timestamp) - external - view - returns (bytes32[] memory) - { - bytes32[] memory _set = set; - uint256 length = 0; - for (uint256 i = 0; i < _set.length; ++i) { - if (setStatus.get(i, timestamp)) { - _set[length++] = _set[i]; - } - } - - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { - mstore(_set, length) - } - - return _set; - } - - function getEnabledSubset(bytes[] storage set, BitMaps.BitMap storage setStatus, uint48 timestamp) - external - view - returns (bytes[] memory) - { - bytes[] memory _set = set; - uint256 length = 0; - for (uint256 i = 0; i < _set.length; ++i) { - if (setStatus.get(i, timestamp)) { - _set[length++] = _set[i]; - } - } - - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { - mstore(_set, length) - } - - return _set; - } - - function _verifyPositions(uint256[] memory positions, uint256 setLength) private pure { - _verifyPosition(positions[positions.length - 1], setLength); - - for (uint256 i = 1; i < positions.length; ++i) { - if (positions[i] <= positions[i - 1]) { - revert InvalidVaultsPositions(); - } - } - } - - function _verifyPosition(uint256 position, uint256 setLength) private pure { - if (position >= setLength) { - revert InvalidVaultsPositions(); - } - } -} diff --git a/test/SimpleMiddleware.t.sol b/test/SimpleMiddleware.t.sol index b255e44..f9f2271 100644 --- a/test/SimpleMiddleware.t.sol +++ b/test/SimpleMiddleware.t.sol @@ -1,362 +1,362 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {Test, console2} from "forge-std/Test.sol"; - -import {SimpleMiddleware} from "src/examples/simple-network/SimpleMiddleware.sol"; - -import {VaultFactory} from "@symbiotic/contracts/VaultFactory.sol"; -import {DelegatorFactory} from "@symbiotic/contracts/DelegatorFactory.sol"; -import {SlasherFactory} from "@symbiotic/contracts/SlasherFactory.sol"; -import {NetworkRegistry} from "@symbiotic/contracts/NetworkRegistry.sol"; -import {OperatorRegistry} from "@symbiotic/contracts/OperatorRegistry.sol"; -import {MetadataService} from "@symbiotic/contracts/service/MetadataService.sol"; -import {NetworkMiddlewareService} from "@symbiotic/contracts/service/NetworkMiddlewareService.sol"; -import {OptInService} from "@symbiotic/contracts/service/OptInService.sol"; - -import {Vault} from "@symbiotic/contracts/vault/Vault.sol"; -import {NetworkRestakeDelegator} from "@symbiotic/contracts/delegator/NetworkRestakeDelegator.sol"; -import {FullRestakeDelegator} from "@symbiotic/contracts/delegator/FullRestakeDelegator.sol"; -import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; -import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; - -import {Token} from "@symbiotic-test/mocks/Token.sol"; -import {VaultConfigurator, IVaultConfigurator} from "@symbiotic/contracts/VaultConfigurator.sol"; -import {IVault} from "@symbiotic/interfaces/IVaultConfigurator.sol"; -import {INetworkRestakeDelegator} from "@symbiotic/interfaces/delegator/INetworkRestakeDelegator.sol"; -import {IFullRestakeDelegator, IBaseDelegator} from "@symbiotic/interfaces/delegator/IFullRestakeDelegator.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {IVaultStorage} from "@symbiotic/interfaces/vault/IVaultStorage.sol"; -import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; -import {IBaseSlasher} from "@symbiotic/interfaces/slasher/IBaseSlasher.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - -contract SimpleMiddlewareTest is Test { - using Subnetwork for address; - - address owner; - address alice; - uint256 alicePrivateKey; - address bob; - uint256 bobPrivateKey; - - SimpleMiddleware simpleMiddleware; - - VaultFactory vaultFactory; - DelegatorFactory delegatorFactory; - SlasherFactory slasherFactory; - NetworkRegistry networkRegistry; - OperatorRegistry operatorRegistry; - MetadataService operatorMetadataService; - MetadataService networkMetadataService; - NetworkMiddlewareService networkMiddlewareService; - OptInService networkVaultOptInService; - OptInService operatorVaultOptInService; - OptInService operatorNetworkOptInService; - - Token collateral; - VaultConfigurator vaultConfigurator; - - function setUp() public { - owner = address(this); - (alice, alicePrivateKey) = makeAddrAndKey("alice"); - (bob, bobPrivateKey) = makeAddrAndKey("bob"); - - vaultFactory = new VaultFactory(owner); - delegatorFactory = new DelegatorFactory(owner); - slasherFactory = new SlasherFactory(owner); - networkRegistry = new NetworkRegistry(); - operatorRegistry = new OperatorRegistry(); - operatorMetadataService = new MetadataService(address(operatorRegistry)); - networkMetadataService = new MetadataService(address(networkRegistry)); - networkMiddlewareService = new NetworkMiddlewareService(address(networkRegistry)); - operatorVaultOptInService = new OptInService(address(operatorRegistry), address(vaultFactory)); - operatorNetworkOptInService = new OptInService(address(operatorRegistry), address(networkRegistry)); - - address vaultImpl = - address(new Vault(address(delegatorFactory), address(slasherFactory), address(vaultFactory))); - vaultFactory.whitelist(vaultImpl); - - address networkRestakeDelegatorImpl = address( - new NetworkRestakeDelegator( - address(networkRegistry), - address(vaultFactory), - address(operatorVaultOptInService), - address(operatorNetworkOptInService), - address(delegatorFactory), - delegatorFactory.totalTypes() - ) - ); - delegatorFactory.whitelist(networkRestakeDelegatorImpl); - - address fullRestakeDelegatorImpl = address( - new FullRestakeDelegator( - address(networkRegistry), - address(vaultFactory), - address(operatorVaultOptInService), - address(operatorNetworkOptInService), - address(delegatorFactory), - delegatorFactory.totalTypes() - ) - ); - delegatorFactory.whitelist(fullRestakeDelegatorImpl); - - address slasherImpl = address( - new Slasher( - address(vaultFactory), - address(networkMiddlewareService), - address(slasherFactory), - slasherFactory.totalTypes() - ) - ); - slasherFactory.whitelist(slasherImpl); - - address vetoSlasherImpl = address( - new VetoSlasher( - address(vaultFactory), - address(networkMiddlewareService), - address(networkRegistry), - address(slasherFactory), - slasherFactory.totalTypes() - ) - ); - slasherFactory.whitelist(vetoSlasherImpl); - - vaultConfigurator = - new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); - - collateral = new Token("Token"); - } - - struct Data { - uint256[3] deposits; - uint256[3][2] networkLimits; - uint256[3][3][2] operatorLimits; - } - - function test_All(Data memory data) public { - uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - address network = address(11_111); - simpleMiddleware = new SimpleMiddleware( - network, address(operatorRegistry), address(vaultFactory), address(vaultFactory), alice, 1 days, 3 days - ); - - _registerNetwork(network, address(simpleMiddleware)); - - uint256 subnetworksN = 2; - vm.startPrank(alice); - simpleMiddleware.setSubnetworks(subnetworksN); - vm.stopPrank(); - - uint256 vaultsN = 3; - Vault[] memory vaults = new Vault[](vaultsN); - address[] memory _vaults = new address[](vaultsN); - for (uint256 i; i < vaultsN; ++i) { - (Vault vault,,) = _getVaultAndDelegatorAndSlasher(7 days, 1 days); - vaults[i] = vault; - _vaults[i] = address(vault); - - vm.startPrank(alice); - simpleMiddleware.registerVault(address(vault)); - vm.stopPrank(); - - data.deposits[i] = bound(data.deposits[i], 1, 100 ether); - _deposit(alice, vault, data.deposits[i]); - - for (uint96 j; j < subnetworksN; ++j) { - data.networkLimits[j][i] = bound(data.networkLimits[j][i], 1, type(uint256).max); - _setMaxNetworkLimit(network, FullRestakeDelegator(vault.delegator()), j, data.networkLimits[j][i]); - _setNetworkLimit( - alice, FullRestakeDelegator(vault.delegator()), network.subnetwork(j), data.networkLimits[j][i] - ); - } - } - - uint256 operatorsN = 3; - address[] memory operators = new address[](operatorsN); - for (uint256 i; i < operatorsN; ++i) { - address operator = address(uint160(111 + i)); - operators[i] = operator; - - _registerOperator(operator); - - _optInOperatorNetwork(operator, network); - - vm.startPrank(alice); - simpleMiddleware.registerOperator(operator, bytes32(uint256(uint160(operator)))); - vm.stopPrank(); - - for (uint256 j; j < vaultsN; ++j) { - Vault vault = vaults[j]; - - _optInOperatorVault(operator, vault); - } - - simpleMiddleware.enableVaults(operator, _vaults); - - for (uint96 j; j < subnetworksN; ++j) { - for (uint256 k; k < vaultsN; ++k) { - Vault vault = vaults[k]; - - data.operatorLimits[j][k][i] = bound(data.operatorLimits[j][k][i], 1, type(uint256).max); - _setOperatorNetworkLimit( - alice, - FullRestakeDelegator(vault.delegator()), - network.subnetwork(j), - operator, - data.operatorLimits[j][k][i] - ); - } - } - } - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - for (uint256 i; i < operatorsN; ++i) { - address operator = operators[i]; - - uint256 operatorStake; - for (uint256 j; j < vaultsN; ++j) { - Vault vault = Vault(vaults[j]); - - uint256 vaultStake; - for (uint96 k; k < subnetworksN; ++k) { - bytes32 subnetwork = network.subnetwork(k); - - vaultStake += - Math.min(Math.min(data.operatorLimits[k][j][i], data.networkLimits[k][j]), data.deposits[j]); - } - - operatorStake += Math.min(vaultStake, data.deposits[j]); - } - - assertEq(operatorStake, simpleMiddleware.getOperatorStake(operator, 0)); - } - } - - function _getVaultAndDelegatorAndSlasher(uint48 epochDuration, uint48 vetoDuration) - internal - returns (Vault, FullRestakeDelegator, VetoSlasher) - { - address[] memory networkLimitSetRoleHolders = new address[](1); - networkLimitSetRoleHolders[0] = alice; - address[] memory operatorNetworkLimitSetRoleHolders = new address[](1); - operatorNetworkLimitSetRoleHolders[0] = alice; - (address vault_, address delegator_, address slasher_) = vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: alice, - vaultParams: IVault.InitParams({ - collateral: address(collateral), - delegator: address(0), - slasher: address(0), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice - }), - delegatorIndex: 1, - delegatorParams: abi.encode( - IFullRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, - hook: address(0), - hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders - }) - ), - withSlasher: true, - slasherIndex: 1, - slasherParams: abi.encode(IVetoSlasher.InitParams({vetoDuration: vetoDuration, resolverSetEpochsDelay: 3})) - }) - ); - - return (Vault(vault_), FullRestakeDelegator(delegator_), VetoSlasher(slasher_)); - } - - function _deposit(address user, Vault vault, uint256 amount) - internal - returns (uint256 depositedAmount, uint256 mintedShares) - { - collateral.transfer(user, amount); - vm.startPrank(user); - collateral.approve(address(vault), amount); - (depositedAmount, mintedShares) = vault.deposit(user, amount); - vm.stopPrank(); - } - - function _setNetworkLimit(address user, FullRestakeDelegator delegator, bytes32 subnetwork, uint256 amount) - internal - { - vm.startPrank(user); - delegator.setNetworkLimit(subnetwork, amount); - vm.stopPrank(); - } - - function _setOperatorNetworkLimit( - address user, - FullRestakeDelegator delegator, - bytes32 subnetwork, - address operator, - uint256 amount - ) internal { - vm.startPrank(user); - delegator.setOperatorNetworkLimit(subnetwork, operator, amount); - vm.stopPrank(); - } - - function _setMaxNetworkLimit(address user, FullRestakeDelegator delegator, uint96 identifier, uint256 amount) - internal - { - vm.startPrank(user); - delegator.setMaxNetworkLimit(identifier, amount); - vm.stopPrank(); - } - - function _registerOperator(address user) internal { - vm.startPrank(user); - operatorRegistry.registerOperator(); - vm.stopPrank(); - } - - function _registerNetwork(address user, address middleware) internal { - vm.startPrank(user); - networkRegistry.registerNetwork(); - networkMiddlewareService.setMiddleware(middleware); - vm.stopPrank(); - } - - function _optInOperatorVault(address user, Vault vault) internal { - vm.startPrank(user); - operatorVaultOptInService.optIn(address(vault)); - vm.stopPrank(); - } - - function _optOutOperatorVault(address user, Vault vault) internal { - vm.startPrank(user); - operatorVaultOptInService.optOut(address(vault)); - vm.stopPrank(); - } - - function _optInOperatorNetwork(address user, address network) internal { - vm.startPrank(user); - operatorNetworkOptInService.optIn(network); - vm.stopPrank(); - } - - function _optOutOperatorNetwork(address user, address network) internal { - vm.startPrank(user); - operatorNetworkOptInService.optOut(network); - vm.stopPrank(); - } -} +// // SPDX-License-Identifier: MIT +// pragma solidity 0.8.25; + +// import {Test, console2} from "forge-std/Test.sol"; + +// import {SimpleMiddleware} from "src/examples/simple-network/SimpleMiddleware.sol"; + +// import {VaultFactory} from "@symbiotic/contracts/VaultFactory.sol"; +// import {DelegatorFactory} from "@symbiotic/contracts/DelegatorFactory.sol"; +// import {SlasherFactory} from "@symbiotic/contracts/SlasherFactory.sol"; +// import {NetworkRegistry} from "@symbiotic/contracts/NetworkRegistry.sol"; +// import {OperatorRegistry} from "@symbiotic/contracts/OperatorRegistry.sol"; +// import {MetadataService} from "@symbiotic/contracts/service/MetadataService.sol"; +// import {NetworkMiddlewareService} from "@symbiotic/contracts/service/NetworkMiddlewareService.sol"; +// import {OptInService} from "@symbiotic/contracts/service/OptInService.sol"; + +// import {Vault} from "@symbiotic/contracts/vault/Vault.sol"; +// import {NetworkRestakeDelegator} from "@symbiotic/contracts/delegator/NetworkRestakeDelegator.sol"; +// import {FullRestakeDelegator} from "@symbiotic/contracts/delegator/FullRestakeDelegator.sol"; +// import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; +// import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; + +// import {Token} from "@symbiotic-test/mocks/Token.sol"; +// import {VaultConfigurator, IVaultConfigurator} from "@symbiotic/contracts/VaultConfigurator.sol"; +// import {IVault} from "@symbiotic/interfaces/IVaultConfigurator.sol"; +// import {INetworkRestakeDelegator} from "@symbiotic/interfaces/delegator/INetworkRestakeDelegator.sol"; +// import {IFullRestakeDelegator, IBaseDelegator} from "@symbiotic/interfaces/delegator/IFullRestakeDelegator.sol"; +// import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; + +// import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// import {IVaultStorage} from "@symbiotic/interfaces/vault/IVaultStorage.sol"; +// import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; +// import {IBaseSlasher} from "@symbiotic/interfaces/slasher/IBaseSlasher.sol"; +// import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +// contract SimpleMiddlewareTest is Test { +// using Subnetwork for address; + +// address owner; +// address alice; +// uint256 alicePrivateKey; +// address bob; +// uint256 bobPrivateKey; + +// SimpleMiddleware simpleMiddleware; + +// VaultFactory vaultFactory; +// DelegatorFactory delegatorFactory; +// SlasherFactory slasherFactory; +// NetworkRegistry networkRegistry; +// OperatorRegistry operatorRegistry; +// MetadataService operatorMetadataService; +// MetadataService networkMetadataService; +// NetworkMiddlewareService networkMiddlewareService; +// OptInService networkVaultOptInService; +// OptInService operatorVaultOptInService; +// OptInService operatorNetworkOptInService; + +// Token collateral; +// VaultConfigurator vaultConfigurator; + +// function setUp() public { +// owner = address(this); +// (alice, alicePrivateKey) = makeAddrAndKey("alice"); +// (bob, bobPrivateKey) = makeAddrAndKey("bob"); + +// vaultFactory = new VaultFactory(owner); +// delegatorFactory = new DelegatorFactory(owner); +// slasherFactory = new SlasherFactory(owner); +// networkRegistry = new NetworkRegistry(); +// operatorRegistry = new OperatorRegistry(); +// operatorMetadataService = new MetadataService(address(operatorRegistry)); +// networkMetadataService = new MetadataService(address(networkRegistry)); +// networkMiddlewareService = new NetworkMiddlewareService(address(networkRegistry)); +// operatorVaultOptInService = new OptInService(address(operatorRegistry), address(vaultFactory)); +// operatorNetworkOptInService = new OptInService(address(operatorRegistry), address(networkRegistry)); + +// address vaultImpl = +// address(new Vault(address(delegatorFactory), address(slasherFactory), address(vaultFactory))); +// vaultFactory.whitelist(vaultImpl); + +// address networkRestakeDelegatorImpl = address( +// new NetworkRestakeDelegator( +// address(networkRegistry), +// address(vaultFactory), +// address(operatorVaultOptInService), +// address(operatorNetworkOptInService), +// address(delegatorFactory), +// delegatorFactory.totalTypes() +// ) +// ); +// delegatorFactory.whitelist(networkRestakeDelegatorImpl); + +// address fullRestakeDelegatorImpl = address( +// new FullRestakeDelegator( +// address(networkRegistry), +// address(vaultFactory), +// address(operatorVaultOptInService), +// address(operatorNetworkOptInService), +// address(delegatorFactory), +// delegatorFactory.totalTypes() +// ) +// ); +// delegatorFactory.whitelist(fullRestakeDelegatorImpl); + +// address slasherImpl = address( +// new Slasher( +// address(vaultFactory), +// address(networkMiddlewareService), +// address(slasherFactory), +// slasherFactory.totalTypes() +// ) +// ); +// slasherFactory.whitelist(slasherImpl); + +// address vetoSlasherImpl = address( +// new VetoSlasher( +// address(vaultFactory), +// address(networkMiddlewareService), +// address(networkRegistry), +// address(slasherFactory), +// slasherFactory.totalTypes() +// ) +// ); +// slasherFactory.whitelist(vetoSlasherImpl); + +// vaultConfigurator = +// new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); + +// collateral = new Token("Token"); +// } + +// struct Data { +// uint256[3] deposits; +// uint256[3][2] networkLimits; +// uint256[3][3][2] operatorLimits; +// } + +// function test_All(Data memory data) public { +// uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; +// blockTimestamp = blockTimestamp + 1_720_700_948; +// vm.warp(blockTimestamp); + +// address network = address(11_111); +// simpleMiddleware = new SimpleMiddleware( +// network, address(operatorRegistry), address(vaultFactory), address(vaultFactory), alice, 1 days, 3 days +// ); + +// _registerNetwork(network, address(simpleMiddleware)); + +// uint256 subnetworksN = 2; +// vm.startPrank(alice); +// simpleMiddleware.setSubnetworks(subnetworksN); +// vm.stopPrank(); + +// uint256 vaultsN = 3; +// Vault[] memory vaults = new Vault[](vaultsN); +// address[] memory _vaults = new address[](vaultsN); +// for (uint256 i; i < vaultsN; ++i) { +// (Vault vault,,) = _getVaultAndDelegatorAndSlasher(7 days, 1 days); +// vaults[i] = vault; +// _vaults[i] = address(vault); + +// vm.startPrank(alice); +// simpleMiddleware.registerVault(address(vault)); +// vm.stopPrank(); + +// data.deposits[i] = bound(data.deposits[i], 1, 100 ether); +// _deposit(alice, vault, data.deposits[i]); + +// for (uint96 j; j < subnetworksN; ++j) { +// data.networkLimits[j][i] = bound(data.networkLimits[j][i], 1, type(uint256).max); +// _setMaxNetworkLimit(network, FullRestakeDelegator(vault.delegator()), j, data.networkLimits[j][i]); +// _setNetworkLimit( +// alice, FullRestakeDelegator(vault.delegator()), network.subnetwork(j), data.networkLimits[j][i] +// ); +// } +// } + +// simpleMiddleware.enableSharedVaults(_vaults); + +// uint256 operatorsN = 3; +// address[] memory operators = new address[](operatorsN); +// for (uint256 i; i < operatorsN; ++i) { +// address operator = address(uint160(111 + i)); +// operators[i] = operator; + +// _registerOperator(operator); + +// _optInOperatorNetwork(operator, network); + +// vm.startPrank(alice); +// simpleMiddleware.registerOperator(operator, bytes32(uint256(uint160(operator)))); +// vm.stopPrank(); + +// for (uint256 j; j < vaultsN; ++j) { +// Vault vault = vaults[j]; + +// _optInOperatorVault(operator, vault); +// } + +// for (uint96 j; j < subnetworksN; ++j) { +// for (uint256 k; k < vaultsN; ++k) { +// Vault vault = vaults[k]; + +// data.operatorLimits[j][k][i] = bound(data.operatorLimits[j][k][i], 1, type(uint256).max); +// _setOperatorNetworkLimit( +// alice, +// FullRestakeDelegator(vault.delegator()), +// network.subnetwork(j), +// operator, +// data.operatorLimits[j][k][i] +// ); +// } +// } +// } + +// blockTimestamp = blockTimestamp + 1; +// vm.warp(blockTimestamp); + +// for (uint256 i; i < operatorsN; ++i) { +// address operator = operators[i]; + +// uint256 operatorStake; +// for (uint256 j; j < vaultsN; ++j) { +// Vault vault = Vault(vaults[j]); + +// uint256 vaultStake; +// for (uint96 k; k < subnetworksN; ++k) { +// bytes32 subnetwork = network.subnetwork(k); + +// vaultStake += +// Math.min(Math.min(data.operatorLimits[k][j][i], data.networkLimits[k][j]), data.deposits[j]); +// } + +// operatorStake += Math.min(vaultStake, data.deposits[j]); +// } + +// assertEq(operatorStake, simpleMiddleware.getOperatorStake(operator, 0)); +// } +// } + +// function _getVaultAndDelegatorAndSlasher(uint48 epochDuration, uint48 vetoDuration) +// internal +// returns (Vault, FullRestakeDelegator, VetoSlasher) +// { +// address[] memory networkLimitSetRoleHolders = new address[](1); +// networkLimitSetRoleHolders[0] = alice; +// address[] memory operatorNetworkLimitSetRoleHolders = new address[](1); +// operatorNetworkLimitSetRoleHolders[0] = alice; +// (address vault_, address delegator_, address slasher_) = vaultConfigurator.create( +// IVaultConfigurator.InitParams({ +// version: vaultFactory.lastVersion(), +// owner: alice, +// vaultParams: IVault.InitParams({ +// collateral: address(collateral), +// delegator: address(0), +// slasher: address(0), +// burner: address(0xdEaD), +// epochDuration: epochDuration, +// depositWhitelist: false, +// defaultAdminRoleHolder: alice, +// depositWhitelistSetRoleHolder: alice, +// depositorWhitelistRoleHolder: alice +// }), +// delegatorIndex: 1, +// delegatorParams: abi.encode( +// IFullRestakeDelegator.InitParams({ +// baseParams: IBaseDelegator.BaseParams({ +// defaultAdminRoleHolder: alice, +// hook: address(0), +// hookSetRoleHolder: alice +// }), +// networkLimitSetRoleHolders: networkLimitSetRoleHolders, +// operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders +// }) +// ), +// withSlasher: true, +// slasherIndex: 1, +// slasherParams: abi.encode(IVetoSlasher.InitParams({vetoDuration: vetoDuration, resolverSetEpochsDelay: 3})) +// }) +// ); + +// return (Vault(vault_), FullRestakeDelegator(delegator_), VetoSlasher(slasher_)); +// } + +// function _deposit(address user, Vault vault, uint256 amount) +// internal +// returns (uint256 depositedAmount, uint256 mintedShares) +// { +// collateral.transfer(user, amount); +// vm.startPrank(user); +// collateral.approve(address(vault), amount); +// (depositedAmount, mintedShares) = vault.deposit(user, amount); +// vm.stopPrank(); +// } + +// function _setNetworkLimit(address user, FullRestakeDelegator delegator, bytes32 subnetwork, uint256 amount) +// internal +// { +// vm.startPrank(user); +// delegator.setNetworkLimit(subnetwork, amount); +// vm.stopPrank(); +// } + +// function _setOperatorNetworkLimit( +// address user, +// FullRestakeDelegator delegator, +// bytes32 subnetwork, +// address operator, +// uint256 amount +// ) internal { +// vm.startPrank(user); +// delegator.setOperatorNetworkLimit(subnetwork, operator, amount); +// vm.stopPrank(); +// } + +// function _setMaxNetworkLimit(address user, FullRestakeDelegator delegator, uint96 identifier, uint256 amount) +// internal +// { +// vm.startPrank(user); +// delegator.setMaxNetworkLimit(identifier, amount); +// vm.stopPrank(); +// } + +// function _registerOperator(address user) internal { +// vm.startPrank(user); +// operatorRegistry.registerOperator(); +// vm.stopPrank(); +// } + +// function _registerNetwork(address user, address middleware) internal { +// vm.startPrank(user); +// networkRegistry.registerNetwork(); +// networkMiddlewareService.setMiddleware(middleware); +// vm.stopPrank(); +// } + +// function _optInOperatorVault(address user, Vault vault) internal { +// vm.startPrank(user); +// operatorVaultOptInService.optIn(address(vault)); +// vm.stopPrank(); +// } + +// function _optOutOperatorVault(address user, Vault vault) internal { +// vm.startPrank(user); +// operatorVaultOptInService.optOut(address(vault)); +// vm.stopPrank(); +// } + +// function _optInOperatorNetwork(address user, address network) internal { +// vm.startPrank(user); +// operatorNetworkOptInService.optIn(network); +// vm.stopPrank(); +// } + +// function _optOutOperatorNetwork(address user, address network) internal { +// vm.startPrank(user); +// operatorNetworkOptInService.optOut(network); +// vm.stopPrank(); +// } +// } From ae49372b8f5e00e00408dcd79855af6844e23a46 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Wed, 2 Oct 2024 15:54:38 +0400 Subject: [PATCH 002/115] refactor: arch with abstract contracts --- src/KeyManager.sol | 15 +- src/KeyManager32.sol | 21 +-- src/MiddlewareStorage.sol | 34 +++++ src/OperatorManager.sol | 81 ++-------- src/VaultManager.sol | 141 ++++-------------- .../simple-network/SimpleMiddleware.sol | 46 ++---- src/libraries/AddressWithTimes.sol | 73 --------- src/libraries/ArrayWithTimes.sol | 134 +++++++++++++++++ 8 files changed, 235 insertions(+), 310 deletions(-) create mode 100644 src/MiddlewareStorage.sol delete mode 100644 src/libraries/AddressWithTimes.sol create mode 100644 src/libraries/ArrayWithTimes.sol diff --git a/src/KeyManager.sol b/src/KeyManager.sol index 1a42b61..5c02486 100644 --- a/src/KeyManager.sol +++ b/src/KeyManager.sol @@ -2,24 +2,19 @@ pragma solidity 0.8.25; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {AddressWithTimes} from "./libraries/AddressWithTimes.sol"; +import {MiddlewareStorage} from "./MiddlewareStorage.sol"; +import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; -contract KeyManager is Ownable { - using AddressWithTimes for AddressWithTimes.Address; +abstract contract KeyManager is MiddlewareStorage { + using ArrayWithTimes for ArrayWithTimes.Address; error DuplicateKey(); error NotExistKey(); error NoOperatorKey(); mapping(address => bytes) public keys; - mapping(bytes => AddressWithTimes.Address) public keyData; - uint48 public immutable SLASHING_WINDOW; - - constructor(address owner, uint48 slashingWindow) Ownable(owner) { - SLASHING_WINDOW = slashingWindow; - } + mapping(bytes => ArrayWithTimes.Address) internal keyData; function operatorByKey(bytes memory key) external view returns (address) { return keyData[key].getAddress(); diff --git a/src/KeyManager32.sol b/src/KeyManager32.sol index 5dd1ee8..798d38f 100644 --- a/src/KeyManager32.sol +++ b/src/KeyManager32.sol @@ -2,34 +2,29 @@ pragma solidity 0.8.25; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {AddressWithTimes} from "./libraries/AddressWithTimes.sol"; +import {MiddlewareStorage} from "./MiddlewareStorage.sol"; +import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; -contract KeyManager32 is Ownable { - using AddressWithTimes for AddressWithTimes.Address; +abstract contract KeyManager32 is MiddlewareStorage { + using ArrayWithTimes for ArrayWithTimes.Address; error DuplicateKey(); error NotExistKey(); error NoOperatorKey(); mapping(address => bytes32) public keys; - mapping(bytes32 => AddressWithTimes.Address) public keyData; - uint48 public immutable SLASHING_WINDOW; + mapping(bytes32 => ArrayWithTimes.Address) internal keyData; - constructor(address owner, uint48 slashingWindow) Ownable(owner) { - SLASHING_WINDOW = slashingWindow; - } - - function operatorByKey(bytes32 key) external view returns (address) { + function operatorByKey(bytes32 key) public view returns (address) { return keyData[key].getAddress(); } - function operatorKey(address operator) external view returns (bytes32) { + function operatorKey(address operator) public view returns (bytes32) { return keys[operator]; } - function keyWasActiveAt(bytes32 key, uint48 timestamp) external view returns (bool) { + function keyWasActiveAt(bytes32 key, uint48 timestamp) public view returns (bool) { return keyData[key].wasActiveAt(timestamp); } diff --git a/src/MiddlewareStorage.sol b/src/MiddlewareStorage.sol new file mode 100644 index 0000000..5d952ea --- /dev/null +++ b/src/MiddlewareStorage.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +abstract contract MiddlewareStorage is Ownable { + uint256 public subnetworks; + + address public immutable NETWORK; + uint48 public immutable SLASHING_WINDOW; + address public immutable VAULT_REGISTRY; + address public immutable OPERATOR_REGISTRY; + address public immutable OPERATOR_NET_OPTIN; + + uint48 public constant INSTANT_SLASHER_TYPE = 0; + uint48 public constant VETO_SLASHER_TYPE = 1; + + constructor( + address owner, + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptIn + ) Ownable(owner) { + subnetworks = 1; + + NETWORK = network; + SLASHING_WINDOW = slashingWindow; + VAULT_REGISTRY = vaultRegistry; + OPERATOR_REGISTRY = operatorRegistry; + OPERATOR_NET_OPTIN = operatorNetOptIn; + } +} diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index 56ad417..960345e 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -8,63 +8,24 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {AddressWithTimes} from "./libraries/AddressWithTimes.sol"; +import {MiddlewareStorage} from "./MiddlewareStorage.sol"; +import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; -contract OperatorManager is Ownable { - using AddressWithTimes for AddressWithTimes.Address; +abstract contract OperatorManager is MiddlewareStorage { + using ArrayWithTimes for ArrayWithTimes.AddressArray; error NotOperator(); error OperatorNotOptedIn(); error OperatorNotRegistered(); error OperatorAlreadyRegistred(); - error SlashPeriodNotPassed(); - - address public immutable NETWORK; - address public immutable OPERATOR_REGISTRY; - address public immutable OPERATOR_NET_OPTIN; - uint48 public immutable SLASHING_WINDOW; - AddressWithTimes.Address[] public operators; - mapping(address => uint256) public operatorPositions; - - constructor( - address owner, - address network, - address operatorRegistry, - address operatorNetOptIn, - uint48 slashingWindow - ) Ownable(owner) { - NETWORK = network; - OPERATOR_REGISTRY = operatorRegistry; - OPERATOR_NET_OPTIN = operatorNetOptIn; - SLASHING_WINDOW = slashingWindow; - } + ArrayWithTimes.AddressArray internal operators; function activeOperators(uint48 timestamp) public view returns (address[] memory) { - address[] memory _operators = new address[](operators.length); - uint256 len = 0; - for (uint256 i; i < operators.length; ++i) { - if (!operators[i].wasActiveAt(timestamp)) { - continue; - } - - _operators[len++] = operators[i].getAddress(); - } - - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { - mstore(_operators, len) - } - - return _operators; + return operators.getActive(timestamp); } function registerOperator(address operator) external onlyOwner { - if (operatorPositions[operator] != 0) { - revert OperatorAlreadyRegistred(); - } - if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { revert NotOperator(); } @@ -73,40 +34,18 @@ contract OperatorManager is Ownable { revert OperatorNotOptedIn(); } - uint256 pos = operators.length; - operators.push(); - operators[pos].set(operator); - operatorPositions[operator] = pos + 1; + operators.register(operator); } function pauseOperator(address operator) external onlyOwner { - if (operatorPositions[operator] == 0) { - revert OperatorNotRegistered(); - } - - operators[operatorPositions[operator] - 1].disable(); + operators.pause(operator); } function unpauseOperator(address operator) external onlyOwner { - if (operatorPositions[operator] == 0) { - revert OperatorNotRegistered(); - } - - operators[operatorPositions[operator] - 1].checkUnpause(SLASHING_WINDOW); - operators[operatorPositions[operator] - 1].enable(); + operators.unpause(operator, SLASHING_WINDOW); } function unregisterOperator(address operator) external onlyOwner { - if (operatorPositions[operator] == 0) { - revert OperatorNotRegistered(); - } - - uint256 pos = operatorPositions[operator] - 1; - operators[pos].checkUnregister(SLASHING_WINDOW); - operators[pos] = operators[operators.length - 1]; - operators.pop(); - - delete operatorPositions[operator]; - operatorPositions[operators[pos].getAddress()] = pos + 1; + operators.unregister(operator, SLASHING_WINDOW); } } diff --git a/src/VaultManager.sol b/src/VaultManager.sol index a59b76e..a690d64 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -11,13 +11,13 @@ import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {AddressWithTimes} from "./libraries/AddressWithTimes.sol"; +import {MiddlewareStorage} from "./MiddlewareStorage.sol"; +import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; -contract VaultManager is Ownable { - using AddressWithTimes for AddressWithTimes.Address; +abstract contract VaultManager is MiddlewareStorage { + using ArrayWithTimes for ArrayWithTimes.AddressArray; using Subnetwork for address; error NotVault(); @@ -30,45 +30,23 @@ contract VaultManager is Ownable { error InvalidSubnetworksCnt(); - error SlashPeriodNotPassed(); error UnknownSlasherType(); - address public immutable NETWORK; - address public immutable VAULT_REGISTRY; - uint48 public immutable SLASHING_WINDOW; - uint48 public constant INSTANT_SLASHER_TYPE = 0; - uint48 public constant VETO_SLASHER_TYPE = 1; - uint256 public subnetworks; - AddressWithTimes.Address[] public sharedVaults; - mapping(address => AddressWithTimes.Address[]) public operatorVaults; - mapping(address => uint256) public vaultPositions; - - constructor(address owner, address network, address vaultRegistry, uint48 slashingWindow) Ownable(owner) { - NETWORK = network; - VAULT_REGISTRY = vaultRegistry; - SLASHING_WINDOW = slashingWindow; - subnetworks = 1; - } + ArrayWithTimes.AddressArray internal sharedVaults; + mapping(address => ArrayWithTimes.AddressArray) internal operatorVaults; function activeVaults(address operator, uint48 timestamp) public view returns (address[] memory) { - address[] memory vaults = new address[](sharedVaults.length + operatorVaults[operator].length); + address[] memory activeSharedVaults = sharedVaults.getActive(timestamp); + address[] memory activeOperatorVaults = operatorVaults[operator].getActive(timestamp); + // TODO how to optimize memory alloc + address[] memory vaults = new address[](activeSharedVaults.length + activeOperatorVaults.length); uint256 len = 0; - for (uint256 i; i < sharedVaults.length; ++i) { - if (!sharedVaults[i].wasActiveAt(timestamp)) { - continue; - } - - vaults[len++] = sharedVaults[i].getAddress(); + for (uint256 i; i < activeSharedVaults.length; ++i) { + vaults[len++] = activeSharedVaults[i]; } - - for (uint256 i; i < operatorVaults[operator].length; ++i) { - if (!operatorVaults[operator][i].wasActiveAt(timestamp)) { - continue; - } - - vaults[len++] = operatorVaults[operator][i].getAddress(); + for (uint256 i; i < activeOperatorVaults.length; ++i) { + vaults[len++] = activeOperatorVaults[i]; } - // shrink array to skip unused slots /// @solidity memory-safe-assembly assembly { @@ -78,100 +56,38 @@ contract VaultManager is Ownable { return vaults; } - function setSubnetworks(uint256 _subnetworks) external onlyOwner { - if (subnetworks >= _subnetworks) { - revert InvalidSubnetworksCnt(); - } - - subnetworks = _subnetworks; - } - function registerSharedVault(address vault) external onlyOwner { - if (vaultPositions[vault] != 0) { - revert VaultAlreadyRegistred(); - } - - _checkVault(vault); - - uint256 pos = sharedVaults.length; - sharedVaults.push(); - sharedVaults[pos].set(vault); - vaultPositions[vault] = pos + 1; + _validateVault(vault); + sharedVaults.register(vault); } function registerOperatorVault(address vault, address operator) external onlyOwner { - if (vaultPositions[vault] != 0) { - revert VaultAlreadyRegistred(); - } - - _checkVault(vault); - - uint256 pos = operatorVaults[operator].length; - operatorVaults[operator].push(); - operatorVaults[operator][pos].set(vault); - vaultPositions[vault] = pos + 1; + _validateVault(vault); + operatorVaults[operator].register(vault); } function pauseSharedVault(address vault) external onlyOwner { - if (vaultPositions[vault] == 0) { - revert VaultNotRegistered(); - } - - sharedVaults[vaultPositions[vault] - 1].disable(); + sharedVaults.pause(vault); } function unpauseSharedVault(address vault) external onlyOwner { - if (vaultPositions[vault] == 0) { - revert VaultNotRegistered(); - } - - sharedVaults[vaultPositions[vault] - 1].checkUnpause(SLASHING_WINDOW); - sharedVaults[vaultPositions[vault] - 1].enable(); + sharedVaults.unpause(vault, SLASHING_WINDOW); } - function pauseOperatorVault(address vault) external onlyOwner { - if (vaultPositions[vault] == 0) { - revert VaultNotRegistered(); - } - - sharedVaults[vaultPositions[vault] - 1].disable(); + function pauseOperatorVault(address operator, address vault) external onlyOwner { + operatorVaults[operator].pause(vault); } function unpauseOperatorVault(address operator, address vault) external onlyOwner { - if (vaultPositions[vault] == 0) { - revert VaultNotRegistered(); - } - - operatorVaults[operator][vaultPositions[vault] - 1].checkUnpause(SLASHING_WINDOW); - operatorVaults[operator][vaultPositions[vault] - 1].enable(); + operatorVaults[operator].unpause(vault, SLASHING_WINDOW); } function unregisterSharedVault(address vault) external onlyOwner { - if (vaultPositions[vault] == 0) { - revert VaultNotRegistered(); - } - - uint256 pos = vaultPositions[vault] - 1; - sharedVaults[pos].checkUnregister(SLASHING_WINDOW); - sharedVaults[pos] = sharedVaults[sharedVaults.length - 1]; - sharedVaults.pop(); - - delete vaultPositions[vault]; - vaultPositions[sharedVaults[pos].getAddress()] = pos + 1; + sharedVaults.unregister(vault, SLASHING_WINDOW); } function unregisterOperatorVault(address operator, address vault) external onlyOwner { - if (vaultPositions[vault] == 0) { - revert VaultNotRegistered(); - } - - uint256 pos = vaultPositions[vault] - 1; - operatorVaults[operator][pos].checkUnregister(SLASHING_WINDOW); - operatorVaults[operator][pos] = operatorVaults[operator][operatorVaults[operator].length - 1]; - operatorVaults[operator].pop(); - - delete vaultPositions[vault]; - vaultPositions[operatorVaults[operator][pos].getAddress()] = pos + 1; + operatorVaults[operator].unregister(vault, SLASHING_WINDOW); } function getOperatorStake(address operator, uint48 timestamp) public view returns (uint256 stake) { @@ -201,11 +117,12 @@ contract VaultManager is Ownable { uint256 operatorStake = getOperatorStake(operators[i], timestamp); totalStake += operatorStake; } + + return totalStake; } - // ONYL DELEGATECALL FROM MIDDLEWARE function slashVault(uint48 timestamp, address vault, bytes32 subnetwork, address operator, uint256 amount) - external + internal { address slasher = IVault(vault).slasher(); uint256 slasherType = IEntity(slasher).TYPE(); @@ -218,7 +135,7 @@ contract VaultManager is Ownable { } } - function _checkVault(address vault) private view { + function _validateVault(address vault) private view { if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { revert NotVault(); } diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index ac5214c..eabbe20 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -12,13 +12,12 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {VaultManager} from "../../VaultManager.sol"; import {OperatorManager} from "../../OperatorManager.sol"; import {KeyManager32} from "../../KeyManager32.sol"; +import {MiddlewareStorage} from "../..//MiddlewareStorage.sol"; -contract SimpleMiddleware is Ownable { +contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager32 { using Subnetwork for address; error SlashingWindowTooShort(); - error TooOldEpoch(); - error InvalidEpoch(); error InvalidSlash(); struct ValidatorData { @@ -26,10 +25,8 @@ contract SimpleMiddleware is Ownable { bytes32 key; } - address public immutable NETWORK; uint48 public immutable EPOCH_DURATION; uint48 public immutable START_TIME; - uint48 public immutable SLASHING_WINDOW; address public immutable vaultManager; address public immutable operatorManager; @@ -43,18 +40,11 @@ contract SimpleMiddleware is Ownable { address owner, uint48 epochDuration, uint48 slashingWindow - ) Ownable(owner) { + ) MiddlewareStorage(owner, network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) { if (slashingWindow < epochDuration) { revert SlashingWindowTooShort(); } - vaultManager = address(new VaultManager(owner, network, vaultRegistry, slashingWindow)); - operatorManager = - address(new OperatorManager(owner, network, operatorRegistry, operatorNetOptin, slashingWindow)); - keyManager = address(new KeyManager32(owner, slashingWindow)); - - NETWORK = network; - START_TIME = Time.timestamp(); EPOCH_DURATION = epochDuration; SLASHING_WINDOW = slashingWindow; } @@ -82,30 +72,32 @@ contract SimpleMiddleware is Ownable { revert InvalidEpoch(); } - address[] memory operators = OperatorManager(operatorManager).activeOperators(epochStartTs); + address[] memory operators = activeOperators(epochStartTs); for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = VaultManager(vaultManager).getOperatorStake(operators[i], epochStartTs); + uint256 operatorStake = getOperatorStake(operators[i], epochStartTs); totalStake += operatorStake; } + + return totalStake; } function getValidatorSet(uint48 epoch) public view returns (ValidatorData[] memory validatorSet) { uint48 epochStartTs = getEpochStartTs(epoch); - address[] memory operators = OperatorManager(operatorManager).activeOperators(epochStartTs); + address[] memory operators = activeOperators(epochStartTs); validatorSet = new ValidatorData[](operators.length); uint256 len = 0; for (uint256 i; i < operators.length; ++i) { address operator = operators[i]; - bytes32 key = KeyManager32(keyManager).operatorKey(operator); - if (key == bytes32(0) || !KeyManager32(keyManager).keyWasActiveAt(key, epochStartTs)) { + bytes32 key = operatorKey(operator); + if (key == bytes32(0) || !keyWasActiveAt(key, epochStartTs)) { continue; } - uint256 stake = VaultManager(vaultManager).getOperatorStake(operator, epochStartTs); + uint256 stake = getOperatorStake(operator, epochStartTs); validatorSet[len++] = ValidatorData(stake, key); } @@ -119,25 +111,17 @@ contract SimpleMiddleware is Ownable { // just for example, our devnets don't support slashing function slash(uint48 epoch, address operator, uint256 amount) public onlyOwner { uint48 epochStartTs = getEpochStartTs(epoch); - uint256 totalStake = VaultManager(vaultManager).getOperatorStake(operator, epochStartTs); - address[] memory vaults = VaultManager(vaultManager).activeVaults(operator, epochStartTs); + uint256 totalStake = getOperatorStake(operator, epochStartTs); + address[] memory vaults = activeVaults(operator, epochStartTs); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; - for (uint96 subnet = 0; subnet < VaultManager(vaultManager).subnetworks(); ++subnet) { + for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { bytes32 subnetwork = NETWORK.subnetwork(subnet); uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, epochStartTs, ""); uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); - (bool success,) = vaultManager.delegatecall( - abi.encodeWithSelector( - VaultManager.slashVault.selector, epochStartTs, vault, subnetwork, operator, slashAmount - ) - ); - - if (!success) { - revert InvalidSlash(); - } + slashVault(epochStartTs, vault, subnetwork, operator, slashAmount); } } } diff --git a/src/libraries/AddressWithTimes.sol b/src/libraries/AddressWithTimes.sol deleted file mode 100644 index a86a94e..0000000 --- a/src/libraries/AddressWithTimes.sol +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; - -/** - * @dev This library adds helper functions for an optimized enable/diasble time data storaging. - */ -library AddressWithTimes { - struct Address { - address _address; - uint48 enabled; - uint48 disabled; - } - - error AlreadyAdded(); - error AlreadyEnabled(); - error NotEnabled(); - error SlashPeriodNotPassed(); - - function getAddress(Address storage self) internal view returns (address) { - return self._address; - } - - function get(Address storage self) internal view returns (address, uint48, uint48) { - return (self._address, self.enabled, self.disabled); - } - - function set(Address storage self, address _address) internal { - self._address = _address; - self.enabled = Time.timestamp(); - self.disabled = 0; - } - - /** - * @dev Enable a given key. - */ - function enable(Address storage self) internal { - if (self.enabled != 0 && self.disabled == 0) { - revert AlreadyEnabled(); - } - - self.enabled = Time.timestamp(); - self.disabled = 0; - } - - /** - * @dev Disable a given key. - */ - function disable(Address storage self) internal { - if (self.enabled == 0 || self.disabled != 0) { - revert NotEnabled(); - } - - self.disabled = Time.timestamp(); - } - - function wasActiveAt(Address storage self, uint48 timestamp) internal view returns (bool) { - return self.enabled != 0 && self.enabled <= timestamp && (self.disabled == 0 || self.disabled >= timestamp); - } - - function checkUnpause(Address storage self, uint48 slashingWindow) internal view { - if (self.disabled + slashingWindow >= Time.timestamp()) { - revert SlashPeriodNotPassed(); - } - } - - function checkUnregister(Address storage self, uint48 slashingWindow) internal view { - if (self.disabled == 0 || self.disabled + slashingWindow >= Time.timestamp()) { - revert SlashPeriodNotPassed(); - } - } -} diff --git a/src/libraries/ArrayWithTimes.sol b/src/libraries/ArrayWithTimes.sol new file mode 100644 index 0000000..32a02c9 --- /dev/null +++ b/src/libraries/ArrayWithTimes.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + +library ArrayWithTimes { + using ArrayWithTimes for Address; + + struct AddressArray { + Address[] array; + mapping(address => uint256) positions; + } + + struct Address { + address _address; + uint48 enabled; + uint48 disabled; + } + + error AlreadyRegistered(); + error NotRegistered(); + error AlreadyEnabled(); + error NotEnabled(); + error ImmutablePeriodNotPassed(); + + function getActive(AddressArray storage self, uint48 timestamp) internal view returns (address[] memory) { + address[] memory array = new address[](self.array.length); + uint256 len = 0; + for (uint256 i; i < self.array.length; ++i) { + if (!self.array[i].wasActiveAt(timestamp)) { + continue; + } + + array[len++] = self.array[i].getAddress(); + } + + // shrink array to skip unused slots + /// @solidity memory-safe-assembly + assembly { + mstore(array, len) + } + + return array; + } + + function register(AddressArray storage self, address addr) internal { + if (self.positions[addr] != 0) { + revert AlreadyRegistered(); + } + + uint256 pos = self.array.length; + self.array.push(); + self.array[pos].set(addr); + self.positions[addr] = pos + 1; + } + + function pause(AddressArray storage self, address addr) internal { + if (self.positions[addr] == 0) { + revert NotRegistered(); + } + + self.array[self.positions[addr] - 1].disable(); + } + + function unpause(AddressArray storage self, address addr, uint48 immutablePeriod) internal { + if (self.positions[addr] == 0) { + revert NotRegistered(); + } + + self.array[self.positions[addr] - 1].checkUnpause(immutablePeriod); + self.array[self.positions[addr] - 1].enable(); + } + + function unregister(AddressArray storage self, address addr, uint48 immutablePeriod) internal { + if (self.positions[addr] == 0) { + revert NotRegistered(); + } + + uint256 pos = self.positions[addr] - 1; + self.array[pos].checkUnregister(immutablePeriod); + self.array[pos] = self.array[self.array.length - 1]; + self.array.pop(); + + delete self.positions[addr]; + self.positions[self.array[pos].getAddress()] = pos + 1; + } + + function getAddress(Address storage self) internal view returns (address) { + return self._address; + } + + function get(Address storage self) internal view returns (address, uint48, uint48) { + return (self._address, self.enabled, self.disabled); + } + + function set(Address storage self, address _address) internal { + self._address = _address; + self.enabled = Time.timestamp(); + self.disabled = 0; + } + + function enable(Address storage self) internal { + if (self.enabled != 0 && self.disabled == 0) { + revert AlreadyEnabled(); + } + + self.enabled = Time.timestamp(); + self.disabled = 0; + } + + function disable(Address storage self) internal { + if (self.enabled == 0 || self.disabled != 0) { + revert NotEnabled(); + } + + self.disabled = Time.timestamp(); + } + + function wasActiveAt(Address storage self, uint48 timestamp) internal view returns (bool) { + return self.enabled != 0 && self.enabled <= timestamp && (self.disabled == 0 || self.disabled >= timestamp); + } + + function checkUnpause(Address storage self, uint48 slashingWindow) internal view { + if (self.disabled + slashingWindow >= Time.timestamp()) { + revert ImmutablePeriodNotPassed(); + } + } + + function checkUnregister(Address storage self, uint48 slashingWindow) internal view { + if (self.disabled == 0 || self.disabled + slashingWindow >= Time.timestamp()) { + revert ImmutablePeriodNotPassed(); + } + } +} From dbbd79dc2dac193165656eaaa9f6ddf68900024b Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Wed, 2 Oct 2024 17:57:33 +0400 Subject: [PATCH 003/115] refactor: redundant key management --- src/BLSKeyManager.sol | 37 +++++++++ src/KeyManager.sol | 55 ++----------- src/KeyManager32.sol | 80 ------------------- src/VaultManager.sol | 17 ++-- .../simple-network/SimpleMiddleware.sol | 19 ++++- src/libraries/ArrayWithTimes.sol | 8 +- 6 files changed, 68 insertions(+), 148 deletions(-) create mode 100644 src/BLSKeyManager.sol delete mode 100644 src/KeyManager32.sol diff --git a/src/BLSKeyManager.sol b/src/BLSKeyManager.sol new file mode 100644 index 0000000..de5bb00 --- /dev/null +++ b/src/BLSKeyManager.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + +import {MiddlewareStorage} from "./MiddlewareStorage.sol"; +import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; + +abstract contract BLSKeyManager is MiddlewareStorage { + using ArrayWithTimes for ArrayWithTimes.Address; + + error DuplicateBLSKey(); + + mapping(address => bytes) public blsKeys; + mapping(bytes => ArrayWithTimes.Address) internal blsKeyData; + + function operatorByBLSKey(bytes memory key) public view returns (address) { + return blsKeyData[key].getAddress(); + } + + function operatorBLSKey(address operator) public view returns (bytes memory) { + return blsKeys[operator]; + } + + function blsKeyWasActiveAt(bytes memory key, uint48 timestamp) public view returns (bool) { + return blsKeyData[key].wasActiveAt(timestamp); + } + + function updateBLSKey(address operator, bytes memory key) external onlyOwner { + if (blsKeyData[key].getAddress() != address(0)) { + revert DuplicateBLSKey(); + } + + blsKeys[operator] = key; + blsKeyData[key].set(operator); + } +} diff --git a/src/KeyManager.sol b/src/KeyManager.sol index 5c02486..b933ff8 100644 --- a/src/KeyManager.sol +++ b/src/KeyManager.sol @@ -10,25 +10,23 @@ abstract contract KeyManager is MiddlewareStorage { using ArrayWithTimes for ArrayWithTimes.Address; error DuplicateKey(); - error NotExistKey(); - error NoOperatorKey(); - mapping(address => bytes) public keys; - mapping(bytes => ArrayWithTimes.Address) internal keyData; + mapping(address => bytes32) public keys; + mapping(bytes32 => ArrayWithTimes.Address) internal keyData; - function operatorByKey(bytes memory key) external view returns (address) { + function operatorByKey(bytes32 key) public view returns (address) { return keyData[key].getAddress(); } - function operatorKey(address operator) external view returns (bytes memory) { + function operatorKey(address operator) public view returns (bytes32) { return keys[operator]; } - function keyWasActiveAt(bytes memory key, uint48 timestamp) external view returns (bool) { + function keyWasActiveAt(bytes32 key, uint48 timestamp) public view returns (bool) { return keyData[key].wasActiveAt(timestamp); } - function registerKey(address operator, bytes memory key) external onlyOwner { + function updateKey(address operator, bytes32 key) external onlyOwner { if (keyData[key].getAddress() != address(0)) { revert DuplicateKey(); } @@ -36,45 +34,4 @@ abstract contract KeyManager is MiddlewareStorage { keys[operator] = key; keyData[key].set(operator); } - - function pauseKey(bytes memory key) external onlyOwner { - if (keyData[key].getAddress() == address(0)) { - revert NotExistKey(); - } - - keyData[key].disable(); - } - - function unpauseKey(bytes memory key) external onlyOwner { - if (keyData[key].getAddress() == address(0)) { - revert NotExistKey(); - } - - keyData[key].checkUnpause(SLASHING_WINDOW); - keyData[key].enable(); - } - - function unregisterKey(bytes memory key) external onlyOwner { - if (keyData[key].getAddress() == address(0)) { - revert NotExistKey(); - } - - keyData[key].checkUnregister(SLASHING_WINDOW); - delete keys[keyData[key].getAddress()]; - delete keyData[key]; - } - - function updateKey(address operator, bytes memory key) external onlyOwner { - if (keyData[keys[operator]].getAddress() == address(0)) { - revert NoOperatorKey(); - } - - if (keyData[key].getAddress() != address(0)) { - revert DuplicateKey(); - } - - keyData[keys[operator]].checkUnregister(SLASHING_WINDOW); - keys[operator] = key; - keyData[key].set(operator); - } } diff --git a/src/KeyManager32.sol b/src/KeyManager32.sol deleted file mode 100644 index 798d38f..0000000 --- a/src/KeyManager32.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; - -import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; - -abstract contract KeyManager32 is MiddlewareStorage { - using ArrayWithTimes for ArrayWithTimes.Address; - - error DuplicateKey(); - error NotExistKey(); - error NoOperatorKey(); - - mapping(address => bytes32) public keys; - mapping(bytes32 => ArrayWithTimes.Address) internal keyData; - - function operatorByKey(bytes32 key) public view returns (address) { - return keyData[key].getAddress(); - } - - function operatorKey(address operator) public view returns (bytes32) { - return keys[operator]; - } - - function keyWasActiveAt(bytes32 key, uint48 timestamp) public view returns (bool) { - return keyData[key].wasActiveAt(timestamp); - } - - function registerKey(address operator, bytes32 key) external onlyOwner { - if (keyData[key].getAddress() != address(0)) { - revert DuplicateKey(); - } - - keys[operator] = key; - keyData[key].set(operator); - } - - function pauseKey(bytes32 key) external onlyOwner { - if (keyData[key].getAddress() == address(0)) { - revert NotExistKey(); - } - - keyData[key].disable(); - } - - function unpauseKey(bytes32 key) external onlyOwner { - if (keyData[key].getAddress() == address(0)) { - revert NotExistKey(); - } - - keyData[key].checkUnpause(SLASHING_WINDOW); - keyData[key].enable(); - } - - function unregisterKey(bytes32 key) external onlyOwner { - if (keyData[key].getAddress() == address(0)) { - revert NotExistKey(); - } - - keyData[key].checkUnregister(SLASHING_WINDOW); - delete keys[keyData[key].getAddress()]; - delete keyData[key]; - } - - function updateKey(address operator, bytes32 key) external onlyOwner { - if (keyData[keys[operator]].getAddress() == address(0)) { - revert NoOperatorKey(); - } - - if (keyData[key].getAddress() != address(0)) { - revert DuplicateKey(); - } - - keyData[keys[operator]].checkUnregister(SLASHING_WINDOW); - keys[operator] = key; - keyData[key].set(operator); - } -} diff --git a/src/VaultManager.sol b/src/VaultManager.sol index a690d64..d6aa4f0 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -38,19 +38,14 @@ abstract contract VaultManager is MiddlewareStorage { function activeVaults(address operator, uint48 timestamp) public view returns (address[] memory) { address[] memory activeSharedVaults = sharedVaults.getActive(timestamp); address[] memory activeOperatorVaults = operatorVaults[operator].getActive(timestamp); - // TODO how to optimize memory alloc - address[] memory vaults = new address[](activeSharedVaults.length + activeOperatorVaults.length); - uint256 len = 0; - for (uint256 i; i < activeSharedVaults.length; ++i) { - vaults[len++] = activeSharedVaults[i]; + + uint256 activeSharedVaultsLen = activeSharedVaults.length; + address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults.length); + for (uint256 i; i < activeSharedVaultsLen; ++i) { + vaults[i] = activeSharedVaults[i]; } for (uint256 i; i < activeOperatorVaults.length; ++i) { - vaults[len++] = activeOperatorVaults[i]; - } - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { - mstore(vaults, len) + vaults[activeSharedVaultsLen + i] = activeOperatorVaults[i]; } return vaults; diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index eabbe20..11b72e1 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -11,14 +11,15 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {VaultManager} from "../../VaultManager.sol"; import {OperatorManager} from "../../OperatorManager.sol"; -import {KeyManager32} from "../../KeyManager32.sol"; +import {KeyManager} from "../../KeyManager.sol"; import {MiddlewareStorage} from "../..//MiddlewareStorage.sol"; -contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager32 { +contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { using Subnetwork for address; error SlashingWindowTooShort(); - error InvalidSlash(); + error InactiveKeySlash(); + error NotExistKeySlash(); struct ValidatorData { uint256 stake; @@ -109,8 +110,18 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager32 { } // just for example, our devnets don't support slashing - function slash(uint48 epoch, address operator, uint256 amount) public onlyOwner { + function slash(uint48 epoch, bytes32 key, uint256 amount) public onlyOwner { uint48 epochStartTs = getEpochStartTs(epoch); + address operator = operatorByKey(key); + + if (operator == address(0)) { + revert NotExistKeySlash(); + } + + if (!keyWasActiveAt(key, epochStartTs)) { + revert InactiveKeySlash(); + } + uint256 totalStake = getOperatorStake(operator, epochStartTs); address[] memory vaults = activeVaults(operator, epochStartTs); diff --git a/src/libraries/ArrayWithTimes.sol b/src/libraries/ArrayWithTimes.sol index 32a02c9..596c85b 100644 --- a/src/libraries/ArrayWithTimes.sol +++ b/src/libraries/ArrayWithTimes.sol @@ -67,7 +67,7 @@ library ArrayWithTimes { revert NotRegistered(); } - self.array[self.positions[addr] - 1].checkUnpause(immutablePeriod); + self.array[self.positions[addr] - 1].validateUnpause(immutablePeriod); self.array[self.positions[addr] - 1].enable(); } @@ -77,7 +77,7 @@ library ArrayWithTimes { } uint256 pos = self.positions[addr] - 1; - self.array[pos].checkUnregister(immutablePeriod); + self.array[pos].validateUnregister(immutablePeriod); self.array[pos] = self.array[self.array.length - 1]; self.array.pop(); @@ -120,13 +120,13 @@ library ArrayWithTimes { return self.enabled != 0 && self.enabled <= timestamp && (self.disabled == 0 || self.disabled >= timestamp); } - function checkUnpause(Address storage self, uint48 slashingWindow) internal view { + function validateUnpause(Address storage self, uint48 slashingWindow) internal view { if (self.disabled + slashingWindow >= Time.timestamp()) { revert ImmutablePeriodNotPassed(); } } - function checkUnregister(Address storage self, uint48 slashingWindow) internal view { + function validateUnregister(Address storage self, uint48 slashingWindow) internal view { if (self.disabled == 0 || self.disabled + slashingWindow >= Time.timestamp()) { revert ImmutablePeriodNotPassed(); } From 38988835d6a4fc9fea94d4c7fae773a02392e796 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Fri, 4 Oct 2024 15:04:14 +0400 Subject: [PATCH 004/115] refactor: add hints for slashing, gettters --- src/MiddlewareStorage.sol | 4 +- src/OperatorManager.sol | 8 ++ src/VaultManager.sol | 88 +++++++++++++++++-- .../simple-network/SimpleMiddleware.sol | 55 +++++++----- src/libraries/ArrayWithTimes.sol | 12 +++ 5 files changed, 134 insertions(+), 33 deletions(-) diff --git a/src/MiddlewareStorage.sol b/src/MiddlewareStorage.sol index 5d952ea..cfc0e2d 100644 --- a/src/MiddlewareStorage.sol +++ b/src/MiddlewareStorage.sol @@ -12,8 +12,8 @@ abstract contract MiddlewareStorage is Ownable { address public immutable OPERATOR_REGISTRY; address public immutable OPERATOR_NET_OPTIN; - uint48 public constant INSTANT_SLASHER_TYPE = 0; - uint48 public constant VETO_SLASHER_TYPE = 1; + uint64 public constant INSTANT_SLASHER_TYPE = 0; + uint64 public constant VETO_SLASHER_TYPE = 1; constructor( address owner, diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index 960345e..a9addbb 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -21,6 +21,14 @@ abstract contract OperatorManager is MiddlewareStorage { ArrayWithTimes.AddressArray internal operators; + function operatorsLength() external view returns (uint256) { + return operators.length(); + } + + function operatorWithTimesAt(uint256 pos) external view returns (address, uint48, uint48) { + return operators.at(pos); + } + function activeOperators(uint48 timestamp) public view returns (address[] memory) { return operators.getActive(timestamp); } diff --git a/src/VaultManager.sol b/src/VaultManager.sol index d6aa4f0..68f985b 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -31,10 +31,39 @@ abstract contract VaultManager is MiddlewareStorage { error InvalidSubnetworksCnt(); error UnknownSlasherType(); + error NonVetoSlasher(); ArrayWithTimes.AddressArray internal sharedVaults; mapping(address => ArrayWithTimes.AddressArray) internal operatorVaults; + struct SlashResponse { + address vault; + uint64 slasherType; + bytes32 subnetwork; + uint256 response; // if instant slashed amount else slash index + } + + function sharedVaultsLength() external view returns (uint256) { + return sharedVaults.length(); + } + + function sharedVaultWithTimesAt(uint256 pos) external view returns (address, uint48, uint48) { + return sharedVaults.at(pos); + } + + function operatorVaultsLength(address operator) external view returns (uint256) { + return operatorVaults[operator].length(); + } + + function opeartorVaultWithTimesAt(address operator, uint256 pos) external view returns (address, uint48, uint48) { + return operatorVaults[operator].at(pos); + } + + // useful when vaults have different assets + function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256) { + return stake; + } + function activeVaults(address operator, uint48 timestamp) public view returns (address[] memory) { address[] memory activeSharedVaults = sharedVaults.getActive(timestamp); address[] memory activeOperatorVaults = operatorVaults[operator].getActive(timestamp); @@ -99,7 +128,22 @@ abstract contract VaultManager is MiddlewareStorage { return stake; } - function calcTotalStake(uint48 timestamp, address[] memory operators) external view returns (uint256 totalStake) { + function getOperatorPower(address operator, uint48 timestamp) public view returns (uint256 power) { + address[] memory vaults = activeVaults(operator, timestamp); + + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { + bytes32 subnetwork = NETWORK.subnetwork(subnet); + uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); + power += stakeToPower(vault, stake); + } + } + + return power; + } + + function totalStake(uint48 timestamp, address[] memory operators) internal view returns (uint256 stake) { if (timestamp < Time.timestamp() - SLASHING_WINDOW) { revert TooOldEpoch(); } @@ -110,26 +154,52 @@ abstract contract VaultManager is MiddlewareStorage { for (uint256 i; i < operators.length; ++i) { uint256 operatorStake = getOperatorStake(operators[i], timestamp); - totalStake += operatorStake; + stake += operatorStake; } - return totalStake; + return stake; } - function slashVault(uint48 timestamp, address vault, bytes32 subnetwork, address operator, uint256 amount) - internal - { + function slashVault( + uint48 timestamp, + address vault, + bytes32 subnetwork, + address operator, + uint256 amount, + bytes calldata hints + ) internal returns (SlashResponse memory resp) { address slasher = IVault(vault).slasher(); - uint256 slasherType = IEntity(slasher).TYPE(); + uint64 slasherType = IEntity(slasher).TYPE(); + resp.vault = vault; + resp.slasherType = slasherType; + resp.subnetwork = subnetwork; if (slasherType == INSTANT_SLASHER_TYPE) { - ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, new bytes(0)); + resp.response = ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, hints); } else if (slasherType == VETO_SLASHER_TYPE) { - IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, new bytes(0)); + resp.response = IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, hints); } else { revert UnknownSlasherType(); } } + function executeSlash(address vault, uint256 slashIndex, bytes calldata hints) + external + onlyOwner + returns (uint256 slashedAmount) + { + if (!sharedVaults.contains(vault)) { + revert NotVault(); + } + + address slasher = IVault(vault).slasher(); + uint64 slasherType = IEntity(slasher).TYPE(); + if (slasherType != VETO_SLASHER_TYPE) { + revert NonVetoSlasher(); + } + + return IVetoSlasher(slasher).executeSlash(slashIndex, hints); + } + function _validateVault(address vault) private view { if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { revert NotVault(); diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index 11b72e1..cb7fd0a 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -20,6 +20,7 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { error SlashingWindowTooShort(); error InactiveKeySlash(); error NotExistKeySlash(); + error InvalidHints(); struct ValidatorData { uint256 stake; @@ -62,25 +63,11 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { return getEpochAtTs(Time.timestamp()); } - function getTotalStake(uint48 epoch) public view returns (uint256 totalStake) { + function getTotalStake(uint48 epoch) public view returns (uint256) { uint48 epochStartTs = getEpochStartTs(epoch); - - if (epochStartTs < Time.timestamp() - SLASHING_WINDOW) { - revert TooOldEpoch(); - } - - if (epochStartTs > Time.timestamp()) { - revert InvalidEpoch(); - } - address[] memory operators = activeOperators(epochStartTs); - for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = getOperatorStake(operators[i], epochStartTs); - totalStake += operatorStake; - } - - return totalStake; + return totalStake(epochStartTs, operators); } function getValidatorSet(uint48 epoch) public view returns (ValidatorData[] memory validatorSet) { @@ -98,8 +85,8 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { continue; } - uint256 stake = getOperatorStake(operator, epochStartTs); - validatorSet[len++] = ValidatorData(stake, key); + uint256 power = getOperatorPower(operator, epochStartTs); + validatorSet[len++] = ValidatorData(power, key); } // shrink array to skip unused slots @@ -109,8 +96,14 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { } } - // just for example, our devnets don't support slashing - function slash(uint48 epoch, bytes32 key, uint256 amount) public onlyOwner { + // Here are the hints getter + // https://github.com/symbioticfi/core/blob/main/src/contracts/hints/SlasherHints.sol + // https://github.com/symbioticfi/core/blob/main/src/contracts/hints/DelegatorHints.sol + function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[] calldata stakeHints, bytes[] calldata slashHints) + public + onlyOwner + returns (SlashResponse[] memory slashResponses) + { uint48 epochStartTs = getEpochStartTs(epoch); address operator = operatorByKey(key); @@ -124,16 +117,34 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { uint256 totalStake = getOperatorStake(operator, epochStartTs); address[] memory vaults = activeVaults(operator, epochStartTs); + slashResponses = new SlashResponse[](vaults.length * subnetworks); + uint256 len = 0; + + if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { + revert InvalidHints(); + } for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { bytes32 subnetwork = NETWORK.subnetwork(subnet); uint256 stake = - IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, epochStartTs, ""); + IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, epochStartTs, stakeHints[i]); + uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); - slashVault(epochStartTs, vault, subnetwork, operator, slashAmount); + if (slashAmount == 0) { + continue; + } + + slashResponses[len++] = + slashVault(epochStartTs, vault, subnetwork, operator, slashAmount, slashHints[i]); } } + + // shrink array to skip unused slots + /// @solidity memory-safe-assembly + assembly { + mstore(slashResponses, len) + } } } diff --git a/src/libraries/ArrayWithTimes.sol b/src/libraries/ArrayWithTimes.sol index 596c85b..100a2b1 100644 --- a/src/libraries/ArrayWithTimes.sol +++ b/src/libraries/ArrayWithTimes.sol @@ -23,6 +23,14 @@ library ArrayWithTimes { error NotEnabled(); error ImmutablePeriodNotPassed(); + function length(AddressArray storage self) internal view returns (uint256) { + return self.array.length; + } + + function at(AddressArray storage self, uint256 pos) internal view returns (address, uint48, uint48) { + return self.array[pos].get(); + } + function getActive(AddressArray storage self, uint48 timestamp) internal view returns (address[] memory) { address[] memory array = new address[](self.array.length); uint256 len = 0; @@ -85,6 +93,10 @@ library ArrayWithTimes { self.positions[self.array[pos].getAddress()] = pos + 1; } + function contains(AddressArray storage self, address addr) internal view returns (bool) { + return self.positions[addr] == 0; + } + function getAddress(Address storage self) internal view returns (address) { return self._address; } From 2c3114ab85dd7546d7156ffa04d5e958abc0e779 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Wed, 9 Oct 2024 01:56:36 +0400 Subject: [PATCH 005/115] fix: optimize our data structure with times, add vault init check, valid hints in slash, keys determinism --- lib/core | 2 +- lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- src/BLSKeyManager.sol | 20 +- src/KeyManager.sol | 20 +- src/MiddlewareStorage.sol | 5 +- src/OperatorManager.sol | 6 +- src/VaultManager.sol | 47 +++- .../simple-network/SimpleMiddleware.sol | 82 +++---- src/libraries/ArrayWithTimes.sol | 146 ------------- src/libraries/EnumerableSetWithTimeData.sol | 200 ++++++++++++++++++ 11 files changed, 323 insertions(+), 209 deletions(-) delete mode 100644 src/libraries/ArrayWithTimes.sol create mode 100644 src/libraries/EnumerableSetWithTimeData.sol diff --git a/lib/core b/lib/core index 76bf709..5996d26 160000 --- a/lib/core +++ b/lib/core @@ -1 +1 @@ -Subproject commit 76bf709458410b2682f7bc20c6e3a90845bf4b51 +Subproject commit 5996d2676d57b56b4568e9378461dfb0e0bdd42d diff --git a/lib/forge-std b/lib/forge-std index 4695fac..035de35 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 4695fac44b2934aaa6d7150e2eaf0256fdc566a7 +Subproject commit 035de35f5e366c8d6ed142aec4ccb57fe2dd87d4 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 55fd53c..6325009 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 55fd53c6d2d516f0606a5830659e2ccb1fedb090 +Subproject commit 632500967504310a07f9d2c70ad378cf53be0109 diff --git a/src/BLSKeyManager.sol b/src/BLSKeyManager.sol index de5bb00..2d16feb 100644 --- a/src/BLSKeyManager.sol +++ b/src/BLSKeyManager.sol @@ -4,21 +4,27 @@ pragma solidity 0.8.25; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; +import {EnumerableSetWithTimeData} from "./libraries/EnumerableSetWithTimeData.sol"; abstract contract BLSKeyManager is MiddlewareStorage { - using ArrayWithTimes for ArrayWithTimes.Address; + using EnumerableSetWithTimeData for EnumerableSetWithTimeData.Inner; error DuplicateBLSKey(); + error FreshBLSKey(); mapping(address => bytes) public blsKeys; - mapping(bytes => ArrayWithTimes.Address) internal blsKeyData; + mapping(address => bytes) public prevBLSKeys; + mapping(bytes => EnumerableSetWithTimeData.Inner) internal blsKeyData; function operatorByBLSKey(bytes memory key) public view returns (address) { return blsKeyData[key].getAddress(); } function operatorBLSKey(address operator) public view returns (bytes memory) { + if (blsKeyData[blsKeys[operator]].enabled == Time.timestamp()) { + return prevBLSKeys[operator]; + } + return blsKeys[operator]; } @@ -31,6 +37,14 @@ abstract contract BLSKeyManager is MiddlewareStorage { revert DuplicateBLSKey(); } + if (keccak256(blsKeys[operator]) != keccak256("")) { + if (blsKeyData[blsKeys[operator]].enabled == Time.timestamp()) { + revert FreshBLSKey(); + } + + prevBLSKeys[operator] = blsKeys[operator]; + } + blsKeys[operator] = key; blsKeyData[key].set(operator); } diff --git a/src/KeyManager.sol b/src/KeyManager.sol index b933ff8..1a0f138 100644 --- a/src/KeyManager.sol +++ b/src/KeyManager.sol @@ -4,21 +4,27 @@ pragma solidity 0.8.25; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; +import {EnumerableSetWithTimeData} from "./libraries/EnumerableSetWithTimeData.sol"; abstract contract KeyManager is MiddlewareStorage { - using ArrayWithTimes for ArrayWithTimes.Address; + using EnumerableSetWithTimeData for EnumerableSetWithTimeData.Inner; error DuplicateKey(); + error FreshKey(); mapping(address => bytes32) public keys; - mapping(bytes32 => ArrayWithTimes.Address) internal keyData; + mapping(address => bytes32) public prevKeys; + mapping(bytes32 => EnumerableSetWithTimeData.Inner) internal keyData; function operatorByKey(bytes32 key) public view returns (address) { return keyData[key].getAddress(); } function operatorKey(address operator) public view returns (bytes32) { + if (keyData[keys[operator]].enabled == Time.timestamp()) { + return prevKeys[operator]; + } + return keys[operator]; } @@ -31,6 +37,14 @@ abstract contract KeyManager is MiddlewareStorage { revert DuplicateKey(); } + if (keys[operator] != bytes32(0)) { + if (keyData[keys[operator]].enabled == Time.timestamp()) { + revert FreshKey(); + } + + prevKeys[operator] = keys[operator]; + } + keys[operator] = key; keyData[key].set(operator); } diff --git a/src/MiddlewareStorage.sol b/src/MiddlewareStorage.sol index cfc0e2d..a6c47db 100644 --- a/src/MiddlewareStorage.sol +++ b/src/MiddlewareStorage.sol @@ -4,8 +4,6 @@ pragma solidity 0.8.25; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; abstract contract MiddlewareStorage is Ownable { - uint256 public subnetworks; - address public immutable NETWORK; uint48 public immutable SLASHING_WINDOW; address public immutable VAULT_REGISTRY; @@ -14,6 +12,7 @@ abstract contract MiddlewareStorage is Ownable { uint64 public constant INSTANT_SLASHER_TYPE = 0; uint64 public constant VETO_SLASHER_TYPE = 1; + uint160 public constant DEFAULT_SUBNETWORK = 0; constructor( address owner, @@ -23,8 +22,6 @@ abstract contract MiddlewareStorage is Ownable { address operatorRegistry, address operatorNetOptIn ) Ownable(owner) { - subnetworks = 1; - NETWORK = network; SLASHING_WINDOW = slashingWindow; VAULT_REGISTRY = vaultRegistry; diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index a9addbb..1b31240 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -9,17 +9,17 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; +import {EnumerableSetWithTimeData} from "./libraries/EnumerableSetWithTimeData.sol"; abstract contract OperatorManager is MiddlewareStorage { - using ArrayWithTimes for ArrayWithTimes.AddressArray; + using EnumerableSetWithTimeData for EnumerableSetWithTimeData.AddressSet; error NotOperator(); error OperatorNotOptedIn(); error OperatorNotRegistered(); error OperatorAlreadyRegistred(); - ArrayWithTimes.AddressArray internal operators; + EnumerableSetWithTimeData.AddressSet internal operators; function operatorsLength() external view returns (uint256) { return operators.length(); diff --git a/src/VaultManager.sol b/src/VaultManager.sol index 68f985b..063ba14 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -14,13 +14,16 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {ArrayWithTimes} from "./libraries/ArrayWithTimes.sol"; +import {EnumerableSetWithTimeData} from "./libraries/EnumerableSetWithTimeData.sol"; abstract contract VaultManager is MiddlewareStorage { - using ArrayWithTimes for ArrayWithTimes.AddressArray; + using EnumerableSetWithTimeData for EnumerableSetWithTimeData.AddressSet; + using EnumerableSetWithTimeData for EnumerableSetWithTimeData.Uint160Set; + using Subnetwork for address; error NotVault(); + error VaultNotInitialized(); error VaultNotRegistered(); error VaultAlreadyRegistred(); error VaultEpochTooShort(); @@ -33,8 +36,10 @@ abstract contract VaultManager is MiddlewareStorage { error UnknownSlasherType(); error NonVetoSlasher(); - ArrayWithTimes.AddressArray internal sharedVaults; - mapping(address => ArrayWithTimes.AddressArray) internal operatorVaults; + EnumerableSetWithTimeData.Uint160Set subnetworks; + + EnumerableSetWithTimeData.AddressSet internal sharedVaults; + mapping(address => EnumerableSetWithTimeData.AddressSet) internal operatorVaults; struct SlashResponse { address vault; @@ -43,6 +48,18 @@ abstract contract VaultManager is MiddlewareStorage { uint256 response; // if instant slashed amount else slash index } + constructor() { + subnetworks.register(DEFAULT_SUBNETWORK); + } + + function subnetworksLength() external view returns (uint256) { + return subnetworks.length(); + } + + function subnetworkWithTimesAt(uint256 pos) external view returns (uint160, uint48, uint48) { + return subnetworks.at(pos); + } + function sharedVaultsLength() external view returns (uint256) { return sharedVaults.length(); } @@ -64,6 +81,10 @@ abstract contract VaultManager is MiddlewareStorage { return stake; } + function activeSubnetworks(uint48 timestamp) public view returns (uint160[] memory) { + return subnetworks.getActive(timestamp); + } + function activeVaults(address operator, uint48 timestamp) public view returns (address[] memory) { address[] memory activeSharedVaults = sharedVaults.getActive(timestamp); address[] memory activeOperatorVaults = operatorVaults[operator].getActive(timestamp); @@ -116,11 +137,12 @@ abstract contract VaultManager is MiddlewareStorage { function getOperatorStake(address operator, uint48 timestamp) public view returns (uint256 stake) { address[] memory vaults = activeVaults(operator, timestamp); + uint160[] memory _subnetworks = activeSubnetworks(timestamp); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; - for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { - bytes32 subnetwork = NETWORK.subnetwork(subnet); + for (uint256 j; j < _subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); stake += IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); } } @@ -130,11 +152,12 @@ abstract contract VaultManager is MiddlewareStorage { function getOperatorPower(address operator, uint48 timestamp) public view returns (uint256 power) { address[] memory vaults = activeVaults(operator, timestamp); + uint160[] memory _subnetworks = activeSubnetworks(timestamp); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; - for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { - bytes32 subnetwork = NETWORK.subnetwork(subnet); + for (uint256 j; j < _subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); power += stakeToPower(vault, stake); } @@ -168,6 +191,10 @@ abstract contract VaultManager is MiddlewareStorage { uint256 amount, bytes calldata hints ) internal returns (SlashResponse memory resp) { + if (!sharedVaults.contains(vault)) { + revert NotVault(); + } + address slasher = IVault(vault).slasher(); uint64 slasherType = IEntity(slasher).TYPE(); resp.vault = vault; @@ -205,6 +232,10 @@ abstract contract VaultManager is MiddlewareStorage { revert NotVault(); } + if (!IVault(vault).isInitialized()) { + revert VaultNotInitialized(); + } + uint48 vaultEpoch = IVault(vault).epochDuration(); address slasher = IVault(vault).slasher(); diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index cb7fd0a..518874d 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -23,17 +23,13 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { error InvalidHints(); struct ValidatorData { - uint256 stake; + uint256 power; bytes32 key; } uint48 public immutable EPOCH_DURATION; uint48 public immutable START_TIME; - address public immutable vaultManager; - address public immutable operatorManager; - address public immutable keyManager; - constructor( address network, address operatorRegistry, @@ -51,29 +47,33 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { SLASHING_WINDOW = slashingWindow; } - function getEpochStartTs(uint48 epoch) public view returns (uint48 timestamp) { + function getEpochStart(uint48 epoch) public view returns (uint48 timestamp) { return START_TIME + epoch * EPOCH_DURATION; } - function getEpochAtTs(uint48 timestamp) public view returns (uint48 epoch) { + function getEpochAt(uint48 timestamp) public view returns (uint48 epoch) { return (timestamp - START_TIME) / EPOCH_DURATION; } function getCurrentEpoch() public view returns (uint48 epoch) { - return getEpochAtTs(Time.timestamp()); + return getEpochAt(Time.timestamp()); + } + + function getCurrentEpochStart() public view returns (uint48 timestamp) { + return START_TIME + getCurrentEpoch() * EPOCH_DURATION; } - function getTotalStake(uint48 epoch) public view returns (uint256) { - uint48 epochStartTs = getEpochStartTs(epoch); - address[] memory operators = activeOperators(epochStartTs); + function getTotalStake() public view returns (uint256) { + uint48 epochStart = getCurrentEpochStart(); + address[] memory operators = activeOperators(epochStart); - return totalStake(epochStartTs, operators); + return totalStake(epochStart, operators); } - function getValidatorSet(uint48 epoch) public view returns (ValidatorData[] memory validatorSet) { - uint48 epochStartTs = getEpochStartTs(epoch); + function getValidatorSet() public view returns (ValidatorData[] memory validatorSet) { + uint48 epochStart = getCurrentEpochStart(); - address[] memory operators = activeOperators(epochStartTs); + address[] memory operators = activeOperators(epochStart); validatorSet = new ValidatorData[](operators.length); uint256 len = 0; @@ -81,17 +81,15 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { address operator = operators[i]; bytes32 key = operatorKey(operator); - if (key == bytes32(0) || !keyWasActiveAt(key, epochStartTs)) { + if (key == bytes32(0) || !keyWasActiveAt(key, epochStart)) { continue; } - uint256 power = getOperatorPower(operator, epochStartTs); + uint256 power = getOperatorPower(operator, epochStart); validatorSet[len++] = ValidatorData(power, key); } - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(validatorSet, len) } } @@ -99,25 +97,29 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { // Here are the hints getter // https://github.com/symbioticfi/core/blob/main/src/contracts/hints/SlasherHints.sol // https://github.com/symbioticfi/core/blob/main/src/contracts/hints/DelegatorHints.sol - function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[] calldata stakeHints, bytes[] calldata slashHints) - public - onlyOwner - returns (SlashResponse[] memory slashResponses) - { - uint48 epochStartTs = getEpochStartTs(epoch); + function slash( + uint48 epoch, + bytes32 key, + uint256 amount, + bytes[][] calldata stakeHints, + bytes[] calldata slashHints + ) public onlyOwner returns (SlashResponse[] memory slashResponses) { + uint48 epochStart = getEpochStart(epoch); address operator = operatorByKey(key); if (operator == address(0)) { revert NotExistKeySlash(); } - if (!keyWasActiveAt(key, epochStartTs)) { + if (!keyWasActiveAt(key, epochStart)) { revert InactiveKeySlash(); } - uint256 totalStake = getOperatorStake(operator, epochStartTs); - address[] memory vaults = activeVaults(operator, epochStartTs); - slashResponses = new SlashResponse[](vaults.length * subnetworks); + uint256 totalStake = getOperatorStake(operator, epochStart); + address[] memory vaults = activeVaults(operator, epochStart); + uint160[] memory _subnetworks = activeSubnetworks(epochStart); + + slashResponses = new SlashResponse[](vaults.length * _subnetworks.length); uint256 len = 0; if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { @@ -125,25 +127,27 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { } for (uint256 i; i < vaults.length; ++i) { + if (stakeHints[i].length != _subnetworks.length) { + revert InvalidHints(); + } + address vault = vaults[i]; - for (uint96 subnet = 0; subnet < subnetworks; ++subnet) { - bytes32 subnetwork = NETWORK.subnetwork(subnet); - uint256 stake = - IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, epochStartTs, stakeHints[i]); + for (uint256 j = 0; j < _subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); + uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( + subnetwork, operator, epochStart, stakeHints[i][j] + ); uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); if (slashAmount == 0) { continue; } - slashResponses[len++] = - slashVault(epochStartTs, vault, subnetwork, operator, slashAmount, slashHints[i]); + slashResponses[len++] = slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); } } - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { + assembly ("memory-safe") { mstore(slashResponses, len) } } diff --git a/src/libraries/ArrayWithTimes.sol b/src/libraries/ArrayWithTimes.sol deleted file mode 100644 index 100a2b1..0000000 --- a/src/libraries/ArrayWithTimes.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; - -library ArrayWithTimes { - using ArrayWithTimes for Address; - - struct AddressArray { - Address[] array; - mapping(address => uint256) positions; - } - - struct Address { - address _address; - uint48 enabled; - uint48 disabled; - } - - error AlreadyRegistered(); - error NotRegistered(); - error AlreadyEnabled(); - error NotEnabled(); - error ImmutablePeriodNotPassed(); - - function length(AddressArray storage self) internal view returns (uint256) { - return self.array.length; - } - - function at(AddressArray storage self, uint256 pos) internal view returns (address, uint48, uint48) { - return self.array[pos].get(); - } - - function getActive(AddressArray storage self, uint48 timestamp) internal view returns (address[] memory) { - address[] memory array = new address[](self.array.length); - uint256 len = 0; - for (uint256 i; i < self.array.length; ++i) { - if (!self.array[i].wasActiveAt(timestamp)) { - continue; - } - - array[len++] = self.array[i].getAddress(); - } - - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { - mstore(array, len) - } - - return array; - } - - function register(AddressArray storage self, address addr) internal { - if (self.positions[addr] != 0) { - revert AlreadyRegistered(); - } - - uint256 pos = self.array.length; - self.array.push(); - self.array[pos].set(addr); - self.positions[addr] = pos + 1; - } - - function pause(AddressArray storage self, address addr) internal { - if (self.positions[addr] == 0) { - revert NotRegistered(); - } - - self.array[self.positions[addr] - 1].disable(); - } - - function unpause(AddressArray storage self, address addr, uint48 immutablePeriod) internal { - if (self.positions[addr] == 0) { - revert NotRegistered(); - } - - self.array[self.positions[addr] - 1].validateUnpause(immutablePeriod); - self.array[self.positions[addr] - 1].enable(); - } - - function unregister(AddressArray storage self, address addr, uint48 immutablePeriod) internal { - if (self.positions[addr] == 0) { - revert NotRegistered(); - } - - uint256 pos = self.positions[addr] - 1; - self.array[pos].validateUnregister(immutablePeriod); - self.array[pos] = self.array[self.array.length - 1]; - self.array.pop(); - - delete self.positions[addr]; - self.positions[self.array[pos].getAddress()] = pos + 1; - } - - function contains(AddressArray storage self, address addr) internal view returns (bool) { - return self.positions[addr] == 0; - } - - function getAddress(Address storage self) internal view returns (address) { - return self._address; - } - - function get(Address storage self) internal view returns (address, uint48, uint48) { - return (self._address, self.enabled, self.disabled); - } - - function set(Address storage self, address _address) internal { - self._address = _address; - self.enabled = Time.timestamp(); - self.disabled = 0; - } - - function enable(Address storage self) internal { - if (self.enabled != 0 && self.disabled == 0) { - revert AlreadyEnabled(); - } - - self.enabled = Time.timestamp(); - self.disabled = 0; - } - - function disable(Address storage self) internal { - if (self.enabled == 0 || self.disabled != 0) { - revert NotEnabled(); - } - - self.disabled = Time.timestamp(); - } - - function wasActiveAt(Address storage self, uint48 timestamp) internal view returns (bool) { - return self.enabled != 0 && self.enabled <= timestamp && (self.disabled == 0 || self.disabled >= timestamp); - } - - function validateUnpause(Address storage self, uint48 slashingWindow) internal view { - if (self.disabled + slashingWindow >= Time.timestamp()) { - revert ImmutablePeriodNotPassed(); - } - } - - function validateUnregister(Address storage self, uint48 slashingWindow) internal view { - if (self.disabled == 0 || self.disabled + slashingWindow >= Time.timestamp()) { - revert ImmutablePeriodNotPassed(); - } - } -} diff --git a/src/libraries/EnumerableSetWithTimeData.sol b/src/libraries/EnumerableSetWithTimeData.sol new file mode 100644 index 0000000..8cb4def --- /dev/null +++ b/src/libraries/EnumerableSetWithTimeData.sol @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + +library EnumerableSetWithTimeData { + using EnumerableSetWithTimeData for Inner; + using EnumerableSetWithTimeData for Uint160Set; + + struct Uint160Set { + Inner[] array; + mapping(uint160 => uint256) positions; + } + + struct AddressSet { + Uint160Set set; + } + + struct Inner { + uint160 value; + uint48 enabled; + uint48 disabled; + } + + error AlreadyRegistered(); + error NotRegistered(); + error AlreadyEnabled(); + error NotEnabled(); + error ImmutablePeriodNotPassed(); + + function length(AddressSet storage self) internal view returns (uint256) { + return self.set.length(); + } + + function at(AddressSet storage self, uint256 pos) internal view returns (address, uint48, uint48) { + (uint160 value, uint48 enabled, uint48 disabled) = self.set.at(pos); + return (address(value), enabled, disabled); + } + + function getActive(AddressSet storage self, uint48 timestamp) internal view returns (address[] memory array) { + uint160[] memory uint160Array = self.set.getActive(timestamp); + + assembly ("memory-safe") { + array := uint160Array + } + + return array; + } + + function register(AddressSet storage self, address addr) internal { + self.set.register(uint160(addr)); + } + + function pause(AddressSet storage self, address addr) internal { + self.set.pause(uint160(addr)); + } + + function unpause(AddressSet storage self, address addr, uint48 immutablePeriod) internal { + self.set.unpause(uint160(addr), immutablePeriod); + } + + function unregister(AddressSet storage self, address addr, uint48 immutablePeriod) internal { + self.set.unregister(uint160(addr), immutablePeriod); + } + + function contains(AddressSet storage self, address addr) internal view returns (bool) { + return self.set.contains(uint160(addr)); + } + + function length(Uint160Set storage self) internal view returns (uint256) { + return self.array.length; + } + + function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint48, uint48) { + return self.array[pos].get(); + } + + function getActive(Uint160Set storage self, uint48 timestamp) internal view returns (uint160[] memory) { + uint160[] memory array = new uint160[](self.array.length); + uint256 len = 0; + for (uint256 i; i < self.array.length; ++i) { + if (!self.array[i].wasActiveAt(timestamp)) { + continue; + } + + array[len++] = self.array[i].getValue(); + } + + // shrink array to skip unused slots + /// @solidity memory-safe-assembly + assembly { + mstore(array, len) + } + + return array; + } + + function register(Uint160Set storage self, uint160 value) internal { + if (self.positions[value] != 0) { + revert AlreadyRegistered(); + } + + uint256 pos = self.array.length; + Inner storage element = self.array.push(); + element.set(value); + self.positions[value] = pos + 1; + } + + function pause(Uint160Set storage self, uint160 value) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + self.array[self.positions[value] - 1].disable(); + } + + function unpause(Uint160Set storage self, uint160 value, uint48 immutablePeriod) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + self.array[self.positions[value] - 1].validateUnpause(immutablePeriod); + self.array[self.positions[value] - 1].enable(); + } + + function unregister(Uint160Set storage self, uint160 value, uint48 immutablePeriod) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + uint256 pos = self.positions[value] - 1; + self.array[pos].validateUnregister(immutablePeriod); + self.array[pos] = self.array[self.array.length - 1]; + self.array.pop(); + + delete self.positions[value]; + self.positions[self.array[pos].getValue()] = pos + 1; + } + + function contains(Uint160Set storage self, uint160 value) internal view returns (bool) { + return self.positions[value] != 0; + } + + function getValue(Inner storage self) internal view returns (uint160) { + return self.value; + } + + function getAddress(Inner storage self) internal view returns (address) { + return address(self.value); + } + + function get(Inner storage self) internal view returns (uint160, uint48, uint48) { + return (self.value, self.enabled, self.disabled); + } + + function set(Inner storage self, uint160 value) internal { + self.value = value; + self.enabled = Time.timestamp(); + self.disabled = 0; + } + + function set(Inner storage self, address addr) internal { + self.value = uint160(addr); + self.enabled = Time.timestamp(); + self.disabled = 0; + } + + function enable(Inner storage self) internal { + if (self.enabled != 0 && self.disabled == 0) { + revert AlreadyEnabled(); + } + + self.enabled = Time.timestamp(); + self.disabled = 0; + } + + function disable(Inner storage self) internal { + if (self.enabled == 0 || self.disabled != 0) { + revert NotEnabled(); + } + + self.disabled = Time.timestamp(); + } + + function wasActiveAt(Inner storage self, uint48 timestamp) internal view returns (bool) { + return self.enabled != 0 && self.enabled < timestamp && (self.disabled == 0 || self.disabled >= timestamp); + } + + function validateUnpause(Inner storage self, uint48 slashingWindow) internal view { + if (self.disabled + slashingWindow >= Time.timestamp()) { + revert ImmutablePeriodNotPassed(); + } + } + + function validateUnregister(Inner storage self, uint48 slashingWindow) internal view { + if (self.disabled == 0 || self.disabled + slashingWindow >= Time.timestamp()) { + revert ImmutablePeriodNotPassed(); + } + } +} From 7278b86a5dcce9e12a00f65269a9293764a883ad Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 10 Oct 2024 03:44:18 +0400 Subject: [PATCH 006/115] refactor: changed timestamps to epoch --- src/BLSKeyManager.sol | 61 ++- src/BaseMiddleware.sol | 160 ++++++++ src/KeyManager.sol | 61 ++- src/MiddlewareStorage.sol | 31 -- src/OperatorManager.sol | 51 ++- src/VaultManager.sol | 201 +++++++--- .../simple-network/SimpleMiddleware.sol | 149 ++++---- src/libraries/EnumerableSetWithTimeData.sol | 200 ---------- src/libraries/PauseableEnumerableSet.sol | 350 ++++++++++++++++++ 9 files changed, 850 insertions(+), 414 deletions(-) create mode 100644 src/BaseMiddleware.sol delete mode 100644 src/MiddlewareStorage.sol delete mode 100644 src/libraries/EnumerableSetWithTimeData.sol create mode 100644 src/libraries/PauseableEnumerableSet.sol diff --git a/src/BLSKeyManager.sol b/src/BLSKeyManager.sol index 2d16feb..c98fc0f 100644 --- a/src/BLSKeyManager.sol +++ b/src/BLSKeyManager.sol @@ -1,51 +1,74 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {BaseMiddleware} from "./BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; -import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {EnumerableSetWithTimeData} from "./libraries/EnumerableSetWithTimeData.sol"; - -abstract contract BLSKeyManager is MiddlewareStorage { - using EnumerableSetWithTimeData for EnumerableSetWithTimeData.Inner; +abstract contract BLSKeyManager is BaseMiddleware { + using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateBLSKey(); - error FreshBLSKey(); - mapping(address => bytes) public blsKeys; - mapping(address => bytes) public prevBLSKeys; - mapping(bytes => EnumerableSetWithTimeData.Inner) internal blsKeyData; + bytes32 private constant ZERO_BYTES_HASH = keccak256(""); // Constant representing an empty hash + + mapping(address => bytes) public blsKeys; // Mapping from operator addresses to their BLS keys + mapping(address => bytes) public prevBLSKeys; // Mapping from operator addresses to their previous BLS keys + mapping(bytes => PauseableEnumerableSet.Inner) internal blsKeyData; // Mapping from BLS keys to their associated data + /* + * Returns the operator address associated with a given BLS key. + * @param key The BLS key for which to find the associated operator. + * @return The address of the operator linked to the specified BLS key. + */ function operatorByBLSKey(bytes memory key) public view returns (address) { return blsKeyData[key].getAddress(); } + /* + * Returns the current BLS key for a given operator. + * If the key has changed in the current epoch, returns the previous key. + * @param operator The address of the operator. + * @return The BLS key associated with the specified operator. + */ function operatorBLSKey(address operator) public view returns (bytes memory) { - if (blsKeyData[blsKeys[operator]].enabled == Time.timestamp()) { + if (blsKeyData[blsKeys[operator]].enabledEpoch == getCurrentEpoch()) { return prevBLSKeys[operator]; } return blsKeys[operator]; } - function blsKeyWasActiveAt(bytes memory key, uint48 timestamp) public view returns (bool) { - return blsKeyData[key].wasActiveAt(timestamp); + /* + * Checks if a given BLS key was active at a specified epoch. + * @param epoch The epoch to check for key activity. + * @param key The BLS key to check. + * @return A boolean indicating whether the BLS key was active at the specified epoch. + */ + function blsKeyWasActiveAt(uint48 epoch, bytes memory key) public view returns (bool) { + return blsKeyData[key].wasActiveAt(epoch); } + /* + * Updates the BLS key associated with an operator. + * If the new key already exists, a DuplicateBLSKey error is thrown. + * @param operator The address of the operator whose BLS key is to be updated. + * @param key The new BLS key to associate with the operator. + */ function updateBLSKey(address operator, bytes memory key) external onlyOwner { + uint48 epoch = getCurrentEpoch(); + if (blsKeyData[key].getAddress() != address(0)) { revert DuplicateBLSKey(); } - if (keccak256(blsKeys[operator]) != keccak256("")) { - if (blsKeyData[blsKeys[operator]].enabled == Time.timestamp()) { - revert FreshBLSKey(); - } - + if (keccak256(blsKeys[operator]) != ZERO_BYTES_HASH && blsKeyData[blsKeys[operator]].enabledEpoch != epoch) { prevBLSKeys[operator] = blsKeys[operator]; } blsKeys[operator] = key; - blsKeyData[key].set(operator); + + if (keccak256(key) != ZERO_BYTES_HASH) { + blsKeyData[key].set(epoch, operator); + } } } diff --git a/src/BaseMiddleware.sol b/src/BaseMiddleware.sol new file mode 100644 index 0000000..5244167 --- /dev/null +++ b/src/BaseMiddleware.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; + +abstract contract BaseMiddleware is Ownable { + using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; + + error ZeroSlashingWindow(); // Error thrown when the slashing window is set to zero + + address public immutable NETWORK; // Address of the network + uint48 public immutable SLASHING_WINDOW; // Duration of the slashing window + address public immutable VAULT_REGISTRY; // Address of the vault registry + address public immutable OPERATOR_REGISTRY; // Address of the operator registry + address public immutable OPERATOR_NET_OPTIN; // Address of the operator network opt-in service + + uint64 public constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type + uint64 public constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type + uint96 public constant DEFAULT_SUBNETWORK = 0; // Default subnetwork identifier + + uint48 public immutable EPOCH_DURATION; // Duration of each epoch + uint48 public immutable START_TIME; // Start time of the epoch + + PauseableEnumerableSet.Uint160Set subnetworks; // Set of active subnetworks + + /* + * Constructor for initializing the BaseMiddleware contract. + * @param owner The address of the contract owner. + * @param network The address of the network. + * @param epochDuration The duration of each epoch. + * @param slashingWindow The duration of the slashing window. + * @param vaultRegistry The address of the vault registry. + * @param operatorRegistry The address of the operator registry. + * @param operatorNetOptIn The address of the operator network opt-in service. + */ + constructor( + address owner, + address network, + uint48 epochDuration, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptIn + ) Ownable(owner) { + if (SLASHING_WINDOW == 0) { + revert ZeroSlashingWindow(); + } + + NETWORK = network; + EPOCH_DURATION = epochDuration; + SLASHING_WINDOW = slashingWindow; + VAULT_REGISTRY = vaultRegistry; + OPERATOR_REGISTRY = operatorRegistry; + OPERATOR_NET_OPTIN = operatorNetOptIn; + + subnetworks.register(getCurrentEpoch(), uint160(DEFAULT_SUBNETWORK)); // Register default subnetwork + } + + /* + * Returns the start timestamp of a given epoch. + * @param epoch The epoch number. + * @return The start timestamp of the specified epoch. + */ + function getEpochStart(uint48 epoch) public view returns (uint48 timestamp) { + return START_TIME + epoch * EPOCH_DURATION; + } + + /* + * Returns the epoch number corresponding to a given timestamp. + * @param timestamp The timestamp to convert to an epoch number. + * @return The epoch number associated with the specified timestamp. + */ + function getEpochAt(uint48 timestamp) public view returns (uint48 epoch) { + return (timestamp - START_TIME) / EPOCH_DURATION; + } + + /* + * Returns the current epoch number based on the current timestamp. + * @return The current epoch number. + */ + function getCurrentEpoch() public view returns (uint48 epoch) { + return getEpochAt(Time.timestamp()); + } + + /* + * Returns the next epoch number. + * @return The next epoch number. + */ + function getNextEpoch() public view returns (uint48 epoch) { + return getCurrentEpoch() + 1; + } + + /* + * Returns the start timestamp of the current epoch. + * @return The start timestamp of the current epoch. + */ + function getCurrentEpochStart() public view returns (uint48 timestamp) { + return START_TIME + getCurrentEpoch() * EPOCH_DURATION; + } + + /* + * Returns the number of subnetworks registered. + * @return The count of registered subnetworks. + */ + function subnetworksLength() external view returns (uint256) { + return subnetworks.length(); + } + + /* + * Returns the subnetwork information at a specified position. + * @param pos The index of the subnetwork. + * @return The subnetwork details including address, enabled epoch, and disabled epoch. + */ + function subnetworkWithTimesAt(uint256 pos) external view returns (uint160, uint48, uint48) { + return subnetworks.at(pos); + } + + /* + * Returns an array of active subnetworks for the current epoch. + * @return An array of active subnetwork addresses. + */ + function activeSubnetworks() public view returns (uint160[] memory) { + return subnetworks.getActive(getCurrentEpoch()); + } + + /* + * Registers a new subnetwork. + * @param subnetwork The identifier of the subnetwork to register. + */ + function registerSubnetwork(uint96 subnetwork) external onlyOwner { + subnetworks.register(getNextEpoch(), uint160(subnetwork)); + } + + /* + * Pauses a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to pause. + */ + function pauseSubnetwork(uint96 subnetwork) external onlyOwner { + subnetworks.pause(getNextEpoch(), uint160(subnetwork)); + } + + /* + * Unpauses a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to unpause. + */ + function unpauseSubnetwork(uint96 subnetwork) external onlyOwner { + subnetworks.unpause(getNextEpoch(), SLASHING_WINDOW, uint160(subnetwork)); + } + + /* + * Unregisters a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to unregister. + */ + function unregisterSubnetwork(uint96 subnetwork) external onlyOwner { + subnetworks.unregister(getNextEpoch(), SLASHING_WINDOW, uint160(subnetwork)); + } +} diff --git a/src/KeyManager.sol b/src/KeyManager.sol index 1a0f138..2234b7c 100644 --- a/src/KeyManager.sol +++ b/src/KeyManager.sol @@ -1,51 +1,74 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {BaseMiddleware} from "./BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; -import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {EnumerableSetWithTimeData} from "./libraries/EnumerableSetWithTimeData.sol"; - -abstract contract KeyManager is MiddlewareStorage { - using EnumerableSetWithTimeData for EnumerableSetWithTimeData.Inner; +abstract contract KeyManager is BaseMiddleware { + using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateKey(); - error FreshKey(); - mapping(address => bytes32) public keys; - mapping(address => bytes32) public prevKeys; - mapping(bytes32 => EnumerableSetWithTimeData.Inner) internal keyData; + bytes32 private constant ZERO_BYTES32 = bytes32(0); + + mapping(address => bytes32) public keys; // Mapping from operator addresses to their current keys + mapping(address => bytes32) public prevKeys; // Mapping from operator addresses to their previous keys + mapping(bytes32 => PauseableEnumerableSet.Inner) internal keyData; // Mapping from keys to their associated data + /* + * Returns the operator address associated with a given key. + * @param key The key for which to find the associated operator. + * @return The address of the operator linked to the specified key. + */ function operatorByKey(bytes32 key) public view returns (address) { return keyData[key].getAddress(); } + /* + * Returns the current key for a given operator. + * If the key has changed in the current epoch, returns the previous key. + * @param operator The address of the operator. + * @return The key associated with the specified operator. + */ function operatorKey(address operator) public view returns (bytes32) { - if (keyData[keys[operator]].enabled == Time.timestamp()) { + if (keyData[keys[operator]].enabledEpoch == getCurrentEpoch()) { return prevKeys[operator]; } return keys[operator]; } - function keyWasActiveAt(bytes32 key, uint48 timestamp) public view returns (bool) { - return keyData[key].wasActiveAt(timestamp); + /* + * Checks if a given key was active at a specified epoch. + * @param epoch The epoch to check for key activity. + * @param key The key to check. + * @return A boolean indicating whether the key was active at the specified epoch. + */ + function keyWasActiveAt(uint48 epoch, bytes32 key) public view returns (bool) { + return keyData[key].wasActiveAt(epoch); } + /* + * Updates the key associated with an operator. + * If the new key already exists, a DuplicateKey error is thrown. + * @param operator The address of the operator whose key is to be updated. + * @param key The new key to associate with the operator. + */ function updateKey(address operator, bytes32 key) external onlyOwner { + uint48 epoch = getCurrentEpoch(); + if (keyData[key].getAddress() != address(0)) { revert DuplicateKey(); } - if (keys[operator] != bytes32(0)) { - if (keyData[keys[operator]].enabled == Time.timestamp()) { - revert FreshKey(); - } - + if (keys[operator] != ZERO_BYTES32 && keyData[keys[operator]].enabledEpoch != epoch) { prevKeys[operator] = keys[operator]; } keys[operator] = key; - keyData[key].set(operator); + + if (key != ZERO_BYTES32) { + keyData[key].set(epoch, operator); + } } } diff --git a/src/MiddlewareStorage.sol b/src/MiddlewareStorage.sol deleted file mode 100644 index a6c47db..0000000 --- a/src/MiddlewareStorage.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -abstract contract MiddlewareStorage is Ownable { - address public immutable NETWORK; - uint48 public immutable SLASHING_WINDOW; - address public immutable VAULT_REGISTRY; - address public immutable OPERATOR_REGISTRY; - address public immutable OPERATOR_NET_OPTIN; - - uint64 public constant INSTANT_SLASHER_TYPE = 0; - uint64 public constant VETO_SLASHER_TYPE = 1; - uint160 public constant DEFAULT_SUBNETWORK = 0; - - constructor( - address owner, - address network, - uint48 slashingWindow, - address vaultRegistry, - address operatorRegistry, - address operatorNetOptIn - ) Ownable(owner) { - NETWORK = network; - SLASHING_WINDOW = slashingWindow; - VAULT_REGISTRY = vaultRegistry; - OPERATOR_REGISTRY = operatorRegistry; - OPERATOR_NET_OPTIN = operatorNetOptIn; - } -} diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index 1b31240..5ef5871 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -8,31 +8,48 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {EnumerableSetWithTimeData} from "./libraries/EnumerableSetWithTimeData.sol"; +import {BaseMiddleware} from "./BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; -abstract contract OperatorManager is MiddlewareStorage { - using EnumerableSetWithTimeData for EnumerableSetWithTimeData.AddressSet; +abstract contract OperatorManager is BaseMiddleware { + using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; error NotOperator(); error OperatorNotOptedIn(); error OperatorNotRegistered(); error OperatorAlreadyRegistred(); - EnumerableSetWithTimeData.AddressSet internal operators; + PauseableEnumerableSet.AddressSet internal operators; + /* + * Returns the length of the operators list. + * @return The number of registered operators. + */ function operatorsLength() external view returns (uint256) { return operators.length(); } + /* + * Returns the operator and their associated enabled and disabled times at a specific position. + * @param pos The index position in the operators array. + * @return The address, enabled epoch, and disabled epoch of the operator. + */ function operatorWithTimesAt(uint256 pos) external view returns (address, uint48, uint48) { return operators.at(pos); } - function activeOperators(uint48 timestamp) public view returns (address[] memory) { - return operators.getActive(timestamp); + /* + * Returns a list of active operators. + * @return An array of addresses representing the active operators. + */ + function activeOperators() public view returns (address[] memory) { + return operators.getActive(getCurrentEpoch()); } + /* + * Registers a new operator. + * @param operator The address of the operator to register. + */ function registerOperator(address operator) external onlyOwner { if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { revert NotOperator(); @@ -42,18 +59,30 @@ abstract contract OperatorManager is MiddlewareStorage { revert OperatorNotOptedIn(); } - operators.register(operator); + operators.register(getNextEpoch(), operator); } + /* + * Pauses a registered operator. + * @param operator The address of the operator to pause. + */ function pauseOperator(address operator) external onlyOwner { - operators.pause(operator); + operators.pause(getNextEpoch(), operator); } + /* + * Unpauses a paused operator. + * @param operator The address of the operator to unpause. + */ function unpauseOperator(address operator) external onlyOwner { - operators.unpause(operator, SLASHING_WINDOW); + operators.unpause(getNextEpoch(), SLASHING_WINDOW, operator); } + /* + * Unregisters an operator. + * @param operator The address of the operator to unregister. + */ function unregisterOperator(address operator) external onlyOwner { - operators.unregister(operator, SLASHING_WINDOW); + operators.unregister(getNextEpoch(), SLASHING_WINDOW, operator); } } diff --git a/src/VaultManager.sol b/src/VaultManager.sol index 063ba14..a245c2c 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -13,13 +13,12 @@ import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {MiddlewareStorage} from "./MiddlewareStorage.sol"; -import {EnumerableSetWithTimeData} from "./libraries/EnumerableSetWithTimeData.sol"; - -abstract contract VaultManager is MiddlewareStorage { - using EnumerableSetWithTimeData for EnumerableSetWithTimeData.AddressSet; - using EnumerableSetWithTimeData for EnumerableSetWithTimeData.Uint160Set; +import {BaseMiddleware} from "./BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; +abstract contract VaultManager is BaseMiddleware { + using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; + using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; using Subnetwork for address; error NotVault(); @@ -27,19 +26,14 @@ abstract contract VaultManager is MiddlewareStorage { error VaultNotRegistered(); error VaultAlreadyRegistred(); error VaultEpochTooShort(); - error TooOldEpoch(); error InvalidEpoch(); - error InvalidSubnetworksCnt(); - error UnknownSlasherType(); error NonVetoSlasher(); - EnumerableSetWithTimeData.Uint160Set subnetworks; - - EnumerableSetWithTimeData.AddressSet internal sharedVaults; - mapping(address => EnumerableSetWithTimeData.AddressSet) internal operatorVaults; + PauseableEnumerableSet.AddressSet internal sharedVaults; + mapping(address => PauseableEnumerableSet.AddressSet) internal operatorVaults; struct SlashResponse { address vault; @@ -48,46 +42,61 @@ abstract contract VaultManager is MiddlewareStorage { uint256 response; // if instant slashed amount else slash index } - constructor() { - subnetworks.register(DEFAULT_SUBNETWORK); - } - - function subnetworksLength() external view returns (uint256) { - return subnetworks.length(); - } - - function subnetworkWithTimesAt(uint256 pos) external view returns (uint160, uint48, uint48) { - return subnetworks.at(pos); - } - + /* + * Returns the length of shared vaults. + * @return The number of shared vaults. + */ function sharedVaultsLength() external view returns (uint256) { return sharedVaults.length(); } - function sharedVaultWithTimesAt(uint256 pos) external view returns (address, uint48, uint48) { + /* + * Returns the address and epoch information of a shared vault at a specific position. + * @param pos The index position in the shared vaults array. + * @return The address, enabled epoch, and disabled epoch of the vault. + */ + function sharedVaultWithEpochsAt(uint256 pos) external view returns (address, uint48, uint48) { return sharedVaults.at(pos); } + /* + * Returns the length of operator vaults for a specific operator. + * @param operator The address of the operator. + * @return The number of vaults associated with the operator. + */ function operatorVaultsLength(address operator) external view returns (uint256) { return operatorVaults[operator].length(); } - function opeartorVaultWithTimesAt(address operator, uint256 pos) external view returns (address, uint48, uint48) { + /* + * Returns the address and epoch information of an operator vault at a specific position. + * @param operator The address of the operator. + * @param pos The index position in the operator vaults array. + * @return The address, enabled epoch, and disabled epoch of the vault. + */ + function operatorVaultWithEpochsAt(address operator, uint256 pos) external view returns (address, uint48, uint48) { return operatorVaults[operator].at(pos); } - // useful when vaults have different assets + /* + * Converts stake to power for a vault. + * @param vault The address of the vault. + * @param stake The amount of stake to convert. + * @return The power calculated from the stake. + */ function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256) { return stake; } - function activeSubnetworks(uint48 timestamp) public view returns (uint160[] memory) { - return subnetworks.getActive(timestamp); - } - - function activeVaults(address operator, uint48 timestamp) public view returns (address[] memory) { - address[] memory activeSharedVaults = sharedVaults.getActive(timestamp); - address[] memory activeOperatorVaults = operatorVaults[operator].getActive(timestamp); + /* + * Returns the list of active vaults for a specific operator. + * @param operator The address of the operator. + * @return An array of addresses representing the active vaults. + */ + function activeVaults(address operator) public view returns (address[] memory) { + uint48 epoch = getCurrentEpoch(); + address[] memory activeSharedVaults = sharedVaults.getActive(epoch); + address[] memory activeOperatorVaults = operatorVaults[operator].getActive(epoch); uint256 activeSharedVaultsLen = activeSharedVaults.length; address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults.length); @@ -101,43 +110,86 @@ abstract contract VaultManager is MiddlewareStorage { return vaults; } + /* + * Registers a new shared vault. + * @param vault The address of the vault to register. + */ function registerSharedVault(address vault) external onlyOwner { _validateVault(vault); - sharedVaults.register(vault); + sharedVaults.register(getNextEpoch(), vault); } + /* + * Registers a new operator vault. + * @param vault The address of the vault to register. + * @param operator The address of the operator. + */ function registerOperatorVault(address vault, address operator) external onlyOwner { _validateVault(vault); - operatorVaults[operator].register(vault); + operatorVaults[operator].register(getNextEpoch(), vault); } + /* + * Pauses a shared vault. + * @param vault The address of the vault to pause. + */ function pauseSharedVault(address vault) external onlyOwner { - sharedVaults.pause(vault); + sharedVaults.pause(getNextEpoch(), vault); } + /* + * Unpauses a shared vault. + * @param vault The address of the vault to unpause. + */ function unpauseSharedVault(address vault) external onlyOwner { - sharedVaults.unpause(vault, SLASHING_WINDOW); + sharedVaults.unpause(getNextEpoch(), SLASHING_WINDOW, vault); } + /* + * Pauses an operator vault. + * @param operator The address of the operator. + * @param vault The address of the vault to pause. + */ function pauseOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].pause(vault); + operatorVaults[operator].pause(getNextEpoch(), vault); } + /* + * Unpauses an operator vault. + * @param operator The address of the operator. + * @param vault The address of the vault to unpause. + */ function unpauseOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].unpause(vault, SLASHING_WINDOW); + operatorVaults[operator].unpause(getNextEpoch(), SLASHING_WINDOW, vault); } + /* + * Unregisters a shared vault. + * @param vault The address of the vault to unregister. + */ function unregisterSharedVault(address vault) external onlyOwner { - sharedVaults.unregister(vault, SLASHING_WINDOW); + sharedVaults.unregister(getNextEpoch(), SLASHING_WINDOW, vault); } + /* + * Unregisters an operator vault. + * @param operator The address of the operator. + * @param vault The address of the vault to unregister. + */ function unregisterOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].unregister(vault, SLASHING_WINDOW); + operatorVaults[operator].unregister(getNextEpoch(), SLASHING_WINDOW, vault); } - function getOperatorStake(address operator, uint48 timestamp) public view returns (uint256 stake) { - address[] memory vaults = activeVaults(operator, timestamp); - uint160[] memory _subnetworks = activeSubnetworks(timestamp); + /* + * Returns the stake of an operator at a specific epoch. + * @param epoch The epoch to check. + * @param operator The address of the operator. + * @return The stake of the operator. + */ + function getOperatorStake(uint48 epoch, address operator) public view returns (uint256 stake) { + uint48 timestamp = getEpochStart(epoch); + address[] memory vaults = activeVaults(operator); + uint160[] memory _subnetworks = activeSubnetworks(); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; @@ -150,9 +202,16 @@ abstract contract VaultManager is MiddlewareStorage { return stake; } - function getOperatorPower(address operator, uint48 timestamp) public view returns (uint256 power) { - address[] memory vaults = activeVaults(operator, timestamp); - uint160[] memory _subnetworks = activeSubnetworks(timestamp); + /* + * Returns the power of an operator at a specific epoch. + * @param epoch The epoch to check. + * @param operator The address of the operator. + * @return The power of the operator. + */ + function getOperatorPower(uint48 epoch, address operator) public view returns (uint256 power) { + uint48 timestamp = getEpochStart(epoch); + address[] memory vaults = activeVaults(operator); + uint160[] memory _subnetworks = activeSubnetworks(); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; @@ -166,23 +225,31 @@ abstract contract VaultManager is MiddlewareStorage { return power; } - function totalStake(uint48 timestamp, address[] memory operators) internal view returns (uint256 stake) { - if (timestamp < Time.timestamp() - SLASHING_WINDOW) { - revert TooOldEpoch(); - } - - if (timestamp > Time.timestamp()) { - revert InvalidEpoch(); - } - + /* + * Returns the total stake of multiple operators at a specific epoch. + * @param epoch The epoch to check. + * @param operators The list of operator addresses. + * @return The total stake of the operators. + */ + function totalStake(uint48 epoch, address[] memory operators) internal view returns (uint256 stake) { for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = getOperatorStake(operators[i], timestamp); + uint256 operatorStake = getOperatorStake(epoch, operators[i]); stake += operatorStake; } return stake; } + /* + * Slashes a vault based on provided conditions. + * @param timestamp The timestamp when the slash occurs. + * @param vault The address of the vault. + * @param subnetwork The subnetwork identifier. + * @param operator The operator to slash. + * @param amount The amount to slash. + * @param hints Additional data for the slasher. + * @return A struct containing information about the slash response. + */ function slashVault( uint48 timestamp, address vault, @@ -209,12 +276,20 @@ abstract contract VaultManager is MiddlewareStorage { } } - function executeSlash(address vault, uint256 slashIndex, bytes calldata hints) + /* + * Executes a veto-based slash for a vault. + * @param vault The address of the vault. + * @param operator The address of the operator. + * @param slashIndex The index of the slash to execute. + * @param hints Additional data for the veto slasher. + * @return The amount that was slashed. + */ + function executeSlash(address vault, address operator, uint256 slashIndex, bytes calldata hints) external onlyOwner returns (uint256 slashedAmount) { - if (!sharedVaults.contains(vault)) { + if (!(sharedVaults.contains(vault) || operatorVaults[operator].contains(vault))) { revert NotVault(); } @@ -227,6 +302,10 @@ abstract contract VaultManager is MiddlewareStorage { return IVetoSlasher(slasher).executeSlash(slashIndex, hints); } + /* + * Validates if the vault is properly initialized and registered. + * @param vault The address of the vault to validate. + */ function _validateVault(address vault) private view { if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { revert NotVault(); diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index 518874d..93848eb 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -12,24 +12,31 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {VaultManager} from "../../VaultManager.sol"; import {OperatorManager} from "../../OperatorManager.sol"; import {KeyManager} from "../../KeyManager.sol"; -import {MiddlewareStorage} from "../..//MiddlewareStorage.sol"; +import {BaseMiddleware} from "../..//BaseMiddleware.sol"; contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { using Subnetwork for address; - error SlashingWindowTooShort(); - error InactiveKeySlash(); - error NotExistKeySlash(); - error InvalidHints(); + error InactiveKeySlash(); // Error thrown when trying to slash an inactive key + error NotExistKeySlash(); // Error thrown when the key does not exist for slashing + error InvalidHints(); // Error thrown for invalid hints provided struct ValidatorData { - uint256 power; - bytes32 key; + uint256 power; // Power of the validator + bytes32 key; // Key associated with the validator } - uint48 public immutable EPOCH_DURATION; - uint48 public immutable START_TIME; - + /* + * Constructor for initializing the SimpleMiddleware contract. + * @param network The address of the network. + * @param operatorRegistry The address of the operator registry. + * @param vaultRegistry The address of the vault registry. + * @param operatorNetOptin The address of the operator network opt-in service. + * @param owner The address of the contract owner. + * @param epochDuration The duration of each epoch. + * @param slashingWindow The duration of the slashing window. It's an epochDuration multuplier + * slashingWindowDuration = slashingWindow * epochDuration + */ constructor( address network, address operatorRegistry, @@ -38,65 +45,58 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { address owner, uint48 epochDuration, uint48 slashingWindow - ) MiddlewareStorage(owner, network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) { - if (slashingWindow < epochDuration) { - revert SlashingWindowTooShort(); - } - - EPOCH_DURATION = epochDuration; - SLASHING_WINDOW = slashingWindow; - } - - function getEpochStart(uint48 epoch) public view returns (uint48 timestamp) { - return START_TIME + epoch * EPOCH_DURATION; - } - - function getEpochAt(uint48 timestamp) public view returns (uint48 epoch) { - return (timestamp - START_TIME) / EPOCH_DURATION; - } - - function getCurrentEpoch() public view returns (uint48 epoch) { - return getEpochAt(Time.timestamp()); - } - - function getCurrentEpochStart() public view returns (uint48 timestamp) { - return START_TIME + getCurrentEpoch() * EPOCH_DURATION; - } - + ) + BaseMiddleware(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) + {} + + /* + * Returns the total stake for the active operators in the current epoch. + * @return The total stake amount. + */ function getTotalStake() public view returns (uint256) { - uint48 epochStart = getCurrentEpochStart(); - address[] memory operators = activeOperators(epochStart); - - return totalStake(epochStart, operators); + address[] memory operators = activeOperators(); // Get the list of active operators + return totalStake(getCurrentEpoch(), operators); // Return the total stake for the current epoch } + /* + * Returns the current validator set as an array of ValidatorData. + * @return An array of ValidatorData containing the power and key of each validator. + */ function getValidatorSet() public view returns (ValidatorData[] memory validatorSet) { - uint48 epochStart = getCurrentEpochStart(); - - address[] memory operators = activeOperators(epochStart); - validatorSet = new ValidatorData[](operators.length); - uint256 len = 0; + uint48 epoch = getCurrentEpoch(); // Get the current epoch + address[] memory operators = activeOperators(); // Get the list of active operators + validatorSet = new ValidatorData[](operators.length); // Initialize the validator set + uint256 len = 0; // Length counter for (uint256 i; i < operators.length; ++i) { - address operator = operators[i]; + address operator = operators[i]; // Get the operator address - bytes32 key = operatorKey(operator); - if (key == bytes32(0) || !keyWasActiveAt(key, epochStart)) { - continue; + bytes32 key = operatorKey(operator); // Get the key for the operator + if (key == bytes32(0) || !keyWasActiveAt(epoch, key)) { + continue; // Skip if the key is inactive } - uint256 power = getOperatorPower(operator, epochStart); - validatorSet[len++] = ValidatorData(power, key); + uint256 power = getOperatorPower(epoch, operator); // Get the operator's power + validatorSet[len++] = ValidatorData(power, key); // Store the validator data } assembly ("memory-safe") { - mstore(validatorSet, len) + mstore(validatorSet, len) // Update the length of the array } } - // Here are the hints getter - // https://github.com/symbioticfi/core/blob/main/src/contracts/hints/SlasherHints.sol - // https://github.com/symbioticfi/core/blob/main/src/contracts/hints/DelegatorHints.sol + /* + * Slashes a validator based on the provided parameters. + * Here are the hints getter + * https://github.com/symbioticfi/core/blob/main/src/contracts/hints/SlasherHints.sol + * https://github.com/symbioticfi/core/blob/main/src/contracts/hints/DelegatorHints.sol + * @param epoch The epoch for which the slashing occurs. + * @param key The key of the operator to slash. + * @param amount The amount to slash. + * @param stakeHints Hints for determining stakes. + * @param slashHints Hints for the slashing process. + * @return An array of SlashResponse indicating the results of the slashing. + */ function slash( uint48 epoch, bytes32 key, @@ -104,51 +104,54 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { bytes[][] calldata stakeHints, bytes[] calldata slashHints ) public onlyOwner returns (SlashResponse[] memory slashResponses) { - uint48 epochStart = getEpochStart(epoch); - address operator = operatorByKey(key); + uint48 epochStart = getEpochStart(epoch); // Get the start timestamp for the epoch + address operator = operatorByKey(key); // Get the operator associated with the key if (operator == address(0)) { - revert NotExistKeySlash(); + revert NotExistKeySlash(); // Revert if the operator does not exist } - if (!keyWasActiveAt(key, epochStart)) { - revert InactiveKeySlash(); + if (!keyWasActiveAt(epoch, key)) { + revert InactiveKeySlash(); // Revert if the key is inactive } - uint256 totalStake = getOperatorStake(operator, epochStart); - address[] memory vaults = activeVaults(operator, epochStart); - uint160[] memory _subnetworks = activeSubnetworks(epochStart); + uint256 totalStake = getOperatorStake(epoch, operator); // Get the total stake for the operator + address[] memory vaults = activeVaults(operator); // Get active vaults for the operator + uint160[] memory _subnetworks = activeSubnetworks(); // Get active subnetworks - slashResponses = new SlashResponse[](vaults.length * _subnetworks.length); - uint256 len = 0; + slashResponses = new SlashResponse[](vaults.length * _subnetworks.length); // Initialize the array for slash responses + uint256 len = 0; // Length counter if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { - revert InvalidHints(); + revert InvalidHints(); // Revert if the hints do not match in length } for (uint256 i; i < vaults.length; ++i) { if (stakeHints[i].length != _subnetworks.length) { - revert InvalidHints(); + revert InvalidHints(); // Revert if the stake hints do not match the subnetworks } - address vault = vaults[i]; + address vault = vaults[i]; // Get the vault address for (uint256 j = 0; j < _subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); + bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); // Get the subnetwork uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( - subnetwork, operator, epochStart, stakeHints[i][j] + subnetwork, + operator, + epochStart, + stakeHints[i][j] // Get the stake at the specified subnetwork ); - uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); + uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); // Calculate the slashing amount if (slashAmount == 0) { - continue; + continue; // Skip if the slashing amount is zero } - slashResponses[len++] = slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); + slashResponses[len++] = slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); // Execute the slashing } } assembly ("memory-safe") { - mstore(slashResponses, len) + mstore(slashResponses, len) // Update the length of the slash responses } } } diff --git a/src/libraries/EnumerableSetWithTimeData.sol b/src/libraries/EnumerableSetWithTimeData.sol deleted file mode 100644 index 8cb4def..0000000 --- a/src/libraries/EnumerableSetWithTimeData.sol +++ /dev/null @@ -1,200 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; - -library EnumerableSetWithTimeData { - using EnumerableSetWithTimeData for Inner; - using EnumerableSetWithTimeData for Uint160Set; - - struct Uint160Set { - Inner[] array; - mapping(uint160 => uint256) positions; - } - - struct AddressSet { - Uint160Set set; - } - - struct Inner { - uint160 value; - uint48 enabled; - uint48 disabled; - } - - error AlreadyRegistered(); - error NotRegistered(); - error AlreadyEnabled(); - error NotEnabled(); - error ImmutablePeriodNotPassed(); - - function length(AddressSet storage self) internal view returns (uint256) { - return self.set.length(); - } - - function at(AddressSet storage self, uint256 pos) internal view returns (address, uint48, uint48) { - (uint160 value, uint48 enabled, uint48 disabled) = self.set.at(pos); - return (address(value), enabled, disabled); - } - - function getActive(AddressSet storage self, uint48 timestamp) internal view returns (address[] memory array) { - uint160[] memory uint160Array = self.set.getActive(timestamp); - - assembly ("memory-safe") { - array := uint160Array - } - - return array; - } - - function register(AddressSet storage self, address addr) internal { - self.set.register(uint160(addr)); - } - - function pause(AddressSet storage self, address addr) internal { - self.set.pause(uint160(addr)); - } - - function unpause(AddressSet storage self, address addr, uint48 immutablePeriod) internal { - self.set.unpause(uint160(addr), immutablePeriod); - } - - function unregister(AddressSet storage self, address addr, uint48 immutablePeriod) internal { - self.set.unregister(uint160(addr), immutablePeriod); - } - - function contains(AddressSet storage self, address addr) internal view returns (bool) { - return self.set.contains(uint160(addr)); - } - - function length(Uint160Set storage self) internal view returns (uint256) { - return self.array.length; - } - - function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint48, uint48) { - return self.array[pos].get(); - } - - function getActive(Uint160Set storage self, uint48 timestamp) internal view returns (uint160[] memory) { - uint160[] memory array = new uint160[](self.array.length); - uint256 len = 0; - for (uint256 i; i < self.array.length; ++i) { - if (!self.array[i].wasActiveAt(timestamp)) { - continue; - } - - array[len++] = self.array[i].getValue(); - } - - // shrink array to skip unused slots - /// @solidity memory-safe-assembly - assembly { - mstore(array, len) - } - - return array; - } - - function register(Uint160Set storage self, uint160 value) internal { - if (self.positions[value] != 0) { - revert AlreadyRegistered(); - } - - uint256 pos = self.array.length; - Inner storage element = self.array.push(); - element.set(value); - self.positions[value] = pos + 1; - } - - function pause(Uint160Set storage self, uint160 value) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } - - self.array[self.positions[value] - 1].disable(); - } - - function unpause(Uint160Set storage self, uint160 value, uint48 immutablePeriod) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } - - self.array[self.positions[value] - 1].validateUnpause(immutablePeriod); - self.array[self.positions[value] - 1].enable(); - } - - function unregister(Uint160Set storage self, uint160 value, uint48 immutablePeriod) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } - - uint256 pos = self.positions[value] - 1; - self.array[pos].validateUnregister(immutablePeriod); - self.array[pos] = self.array[self.array.length - 1]; - self.array.pop(); - - delete self.positions[value]; - self.positions[self.array[pos].getValue()] = pos + 1; - } - - function contains(Uint160Set storage self, uint160 value) internal view returns (bool) { - return self.positions[value] != 0; - } - - function getValue(Inner storage self) internal view returns (uint160) { - return self.value; - } - - function getAddress(Inner storage self) internal view returns (address) { - return address(self.value); - } - - function get(Inner storage self) internal view returns (uint160, uint48, uint48) { - return (self.value, self.enabled, self.disabled); - } - - function set(Inner storage self, uint160 value) internal { - self.value = value; - self.enabled = Time.timestamp(); - self.disabled = 0; - } - - function set(Inner storage self, address addr) internal { - self.value = uint160(addr); - self.enabled = Time.timestamp(); - self.disabled = 0; - } - - function enable(Inner storage self) internal { - if (self.enabled != 0 && self.disabled == 0) { - revert AlreadyEnabled(); - } - - self.enabled = Time.timestamp(); - self.disabled = 0; - } - - function disable(Inner storage self) internal { - if (self.enabled == 0 || self.disabled != 0) { - revert NotEnabled(); - } - - self.disabled = Time.timestamp(); - } - - function wasActiveAt(Inner storage self, uint48 timestamp) internal view returns (bool) { - return self.enabled != 0 && self.enabled < timestamp && (self.disabled == 0 || self.disabled >= timestamp); - } - - function validateUnpause(Inner storage self, uint48 slashingWindow) internal view { - if (self.disabled + slashingWindow >= Time.timestamp()) { - revert ImmutablePeriodNotPassed(); - } - } - - function validateUnregister(Inner storage self, uint48 slashingWindow) internal view { - if (self.disabled == 0 || self.disabled + slashingWindow >= Time.timestamp()) { - revert ImmutablePeriodNotPassed(); - } - } -} diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol new file mode 100644 index 0000000..3a4df9e --- /dev/null +++ b/src/libraries/PauseableEnumerableSet.sol @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +library PauseableEnumerableSet { + using PauseableEnumerableSet for Inner; + using PauseableEnumerableSet for Uint160Set; + + /* + * Struct for managing a set of Uint160 values. + */ + struct Uint160Set { + Inner[] array; + mapping(uint160 => uint256) positions; // Maps value to its index + 1. + } + + /* + * Struct for managing a set of addresses. + */ + struct AddressSet { + Uint160Set set; + } + + /* + * Struct for managing value and its active status. + */ + struct Inner { + uint160 value; // The actual value. + uint48 enabledEpoch; // Epoch when the value was enabled. + uint48 disabledEpoch; // Epoch when the value was disabled. + } + + // Custom error messages + error AlreadyRegistered(); // Thrown when trying to register an already registered value. + error NotRegistered(); // Thrown when trying to modify a value that's not registered. + error AlreadyEnabled(); // Thrown when enabling an already enabled value. + error NotEnabled(); // Thrown when disabling a value that's not enabled. + error ImmutablePeriodNotPassed(); // Thrown when an action is attempted before immutable period passes. + + /* + * Returns the length of the AddressSet. + * @param self The AddressSet storage. + * @return The number of elements in the set. + */ + function length(AddressSet storage self) internal view returns (uint256) { + return self.set.length(); + } + + /* + * Returns the address and its active period at a given position in the AddressSet. + * @param self The AddressSet storage. + * @param pos The position in the set. + * @return The address, enabled epoch, and disabled epoch at the position. + */ + function at(AddressSet storage self, uint256 pos) internal view returns (address, uint48, uint48) { + (uint160 value, uint48 enabledEpoch, uint48 disabledEpoch) = self.set.at(pos); + return (address(value), enabledEpoch, disabledEpoch); + } + + /* + * Retrieves all active addresses at a given epoch. + * @param self The AddressSet storage. + * @param epoch The epoch to check. + * @return An array of active addresses. + */ + function getActive(AddressSet storage self, uint48 epoch) internal view returns (address[] memory array) { + uint160[] memory uint160Array = self.set.getActive(epoch); + + assembly ("memory-safe") { + array := uint160Array + } + + return array; + } + + /* + * Registers a new address at a given epoch. + * @param self The AddressSet storage. + * @param epoch The epoch when the address is added. + * @param addr The address to register. + */ + function register(AddressSet storage self, uint48 epoch, address addr) internal { + self.set.register(epoch, uint160(addr)); + } + + /* + * Pauses an address at a given epoch. + * @param self The AddressSet storage. + * @param epoch The epoch when the address is paused. + * @param addr The address to pause. + */ + function pause(AddressSet storage self, uint48 epoch, address addr) internal { + self.set.pause(epoch, uint160(addr)); + } + + /* + * Unpauses an address, re-enabling it after the immutable period. + * @param self The AddressSet storage. + * @param epoch The current epoch. + * @param immutableEpochs The required immutable period before unpausing. + * @param addr The address to unpause. + */ + function unpause(AddressSet storage self, uint48 epoch, uint48 immutableEpochs, address addr) internal { + self.set.unpause(epoch, immutableEpochs, uint160(addr)); + } + + /* + * Unregisters an address, removing it from the set. + * @param self The AddressSet storage. + * @param epoch The current epoch. + * @param immutableEpochs The required immutable period before unregistering. + * @param addr The address to unregister. + */ + function unregister(AddressSet storage self, uint48 epoch, uint48 immutableEpochs, address addr) internal { + self.set.unregister(epoch, immutableEpochs, uint160(addr)); + } + + /* + * Checks if an address is contained in the AddressSet. + * @param self The AddressSet storage. + * @param addr The address to check. + * @return True if the address is in the set, false otherwise. + */ + function contains(AddressSet storage self, address addr) internal view returns (bool) { + return self.set.contains(uint160(addr)); + } + + /* + * Returns the number of elements in the Uint160Set. + * @param self The Uint160Set storage. + * @return The number of elements. + */ + function length(Uint160Set storage self) internal view returns (uint256) { + return self.array.length; + } + + /* + * Returns the value and its active period at a given position in the Uint160Set. + * @param self The Uint160Set storage. + * @param pos The position in the set. + * @return The value, enabled epoch, and disabled epoch at the position. + */ + function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint48, uint48) { + return self.array[pos].get(); + } + + /* + * Retrieves all active values at a given epoch. + * @param self The Uint160Set storage. + * @param epoch The epoch to check. + * @return An array of active values. + */ + function getActive(Uint160Set storage self, uint48 epoch) internal view returns (uint160[] memory) { + uint160[] memory array = new uint160[](self.array.length); + uint256 len = 0; + for (uint256 i; i < self.array.length; ++i) { + if (!self.array[i].wasActiveAt(epoch)) { + continue; + } + array[len++] = self.array[i].value; + } + + assembly ("memory-safe") { + mstore(array, len) + } + + return array; + } + + /* + * Registers a new Uint160 value at a given epoch. + * @param self The Uint160Set storage. + * @param epoch The epoch when the value is added. + * @param value The Uint160 value to register. + */ + function register(Uint160Set storage self, uint48 epoch, uint160 value) internal { + if (self.positions[value] != 0) { + revert AlreadyRegistered(); + } + + uint256 pos = self.array.length; + Inner storage element = self.array.push(); + element.set(epoch, value); + self.positions[value] = pos + 1; + } + + /* + * Pauses a Uint160 value at a given epoch. + * @param self The Uint160Set storage. + * @param epoch The epoch when the value is paused. + * @param value The Uint160 value to pause. + */ + function pause(Uint160Set storage self, uint48 epoch, uint160 value) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + self.array[self.positions[value] - 1].disable(epoch); + } + + /* + * Unpauses a Uint160 value after the immutable period. + * @param self The Uint160Set storage. + * @param epoch The current epoch. + * @param immutableEpochs The required immutable period before unpausing. + * @param value The Uint160 value to unpause. + */ + function unpause(Uint160Set storage self, uint48 epoch, uint48 immutableEpochs, uint160 value) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + self.array[self.positions[value] - 1].validateUnpause(epoch, immutableEpochs); + self.array[self.positions[value] - 1].enable(epoch); + } + + /* + * Unregisters a Uint160 value from the set. + * @param self The Uint160Set storage. + * @param epoch The current epoch. + * @param immutableEpochs The required immutable period before unregistering. + * @param value The Uint160 value to unregister. + */ + function unregister(Uint160Set storage self, uint48 epoch, uint48 immutableEpochs, uint160 value) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + uint256 pos = self.positions[value] - 1; + self.array[pos].validateUnregister(epoch, immutableEpochs); + self.array[pos] = self.array[self.array.length - 1]; + self.array.pop(); + + delete self.positions[value]; + self.positions[self.array[pos].value] = pos + 1; + } + + /* + * Checks if a Uint160 value is contained in the Uint160Set. + * @param self The Uint160Set storage. + * @param value The Uint160 value to check. + * @return True if the value is in the set, false otherwise. + */ + function contains(Uint160Set storage self, uint160 value) internal view returns (bool) { + return self.positions[value] != 0; + } + + /* + * Returns the address stored in the Inner struct. + * @param self The Inner struct + * @return The stored Uint160 as address + */ + function getAddress(Inner storage self) internal view returns (address) { + return address(self.value); + } + + /* + * Returns the value and its active period from the Inner struct. + * @param self The Inner struct. + * @return The value, enabled epoch, and disabled epoch. + */ + function get(Inner storage self) internal view returns (uint160, uint48, uint48) { + return (self.value, self.enabledEpoch, self.disabledEpoch); + } + + /* + * Sets the value and marks it as enabled at a given epoch. + * @param self The Inner struct. + * @param epoch The epoch when the value is set. + * @param value The Uint160 value to store. + */ + function set(Inner storage self, uint48 epoch, uint160 value) internal { + self.value = value; + self.enabledEpoch = epoch; + self.disabledEpoch = 0; + } + + /* + * Sets the address and marks it as enabled at a given epoch. + * @param self The Inner struct. + * @param epoch The epoch when the address is set. + * @param addr The address to store. + */ + function set(Inner storage self, uint48 epoch, address addr) internal { + self.value = uint160(addr); + self.enabledEpoch = epoch; + self.disabledEpoch = 0; + } + + /* + * Enables the value at a given epoch. + * @param self The Inner struct. + * @param epoch The epoch when the value is enabled. + */ + function enable(Inner storage self, uint48 epoch) internal { + if (self.enabledEpoch != 0 && self.disabledEpoch == 0) { + revert AlreadyEnabled(); + } + + self.enabledEpoch = epoch; + self.disabledEpoch = 0; + } + + /* + * Disables the value at a given epoch. + * @param self The Inner struct. + * @param epoch The epoch when the value is disabled. + */ + function disable(Inner storage self, uint48 epoch) internal { + if (self.enabledEpoch == 0 || self.disabledEpoch != 0) { + revert NotEnabled(); + } + + self.disabledEpoch = epoch; + } + + /* + * Checks if the value was active at a given epoch. + * @param self The Inner struct. + * @param epoch The epoch to check. + * @return True if the value was active at the epoch, false otherwise. + */ + function wasActiveAt(Inner storage self, uint48 epoch) internal view returns (bool) { + return self.enabledEpoch != 0 && self.enabledEpoch <= epoch + && (self.disabledEpoch == 0 || self.disabledEpoch >= epoch); + } + + /* + * Validates whether the value can be unpaused at a given epoch. + * @param self The Inner struct. + * @param epoch The current epoch. + * @param immutableEpochs The immutable period that must pass before unpausing. + */ + function validateUnpause(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { + if (self.disabledEpoch + immutableEpochs >= epoch) { + revert ImmutablePeriodNotPassed(); + } + } + + /* + * Validates whether the value can be unregistered at a given epoch. + * @param self The Inner struct. + * @param epoch The current epoch. + * @param immutableEpochs The immutable period that must pass before unregistering. + */ + function validateUnregister(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { + if (self.disabledEpoch == 0 || self.disabledEpoch + immutableEpochs >= epoch) { + revert ImmutablePeriodNotPassed(); + } + } +} From 9ba23ec721a5c25aff63d741cd84241268580f12 Mon Sep 17 00:00:00 2001 From: Kresh Date: Fri, 18 Oct 2024 11:56:40 +0400 Subject: [PATCH 007/115] feat: new example --- lib/core | 2 +- lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- src/BLSKeyManager.sol | 16 +- src/BaseMiddleware.sol | 12 +- src/KeyManager.sol | 16 +- src/OperatorManager.sol | 28 +-- src/VaultManager.sol | 67 +++---- .../simple-network/SimpleMiddleware.sol | 4 +- .../simple-network/SqrtTaskMiddleware.sol | 172 ++++++++++++++++++ 10 files changed, 247 insertions(+), 74 deletions(-) create mode 100644 src/examples/simple-network/SqrtTaskMiddleware.sol diff --git a/lib/core b/lib/core index 5996d26..bdbb05e 160000 --- a/lib/core +++ b/lib/core @@ -1 +1 @@ -Subproject commit 5996d2676d57b56b4568e9378461dfb0e0bdd42d +Subproject commit bdbb05ebd9ea6d96c671b672562ae23afcb2123b diff --git a/lib/forge-std b/lib/forge-std index 035de35..1de6eec 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 035de35f5e366c8d6ed142aec4ccb57fe2dd87d4 +Subproject commit 1de6eecf821de7fe2c908cc48d3ab3dced20717f diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 6325009..0034c30 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 632500967504310a07f9d2c70ad378cf53be0109 +Subproject commit 0034c302241c4b1a1685272d4df42ca5d64b8c34 diff --git a/src/BLSKeyManager.sol b/src/BLSKeyManager.sol index c98fc0f..afb2ab1 100644 --- a/src/BLSKeyManager.sol +++ b/src/BLSKeyManager.sol @@ -13,7 +13,7 @@ abstract contract BLSKeyManager is BaseMiddleware { mapping(address => bytes) public blsKeys; // Mapping from operator addresses to their BLS keys mapping(address => bytes) public prevBLSKeys; // Mapping from operator addresses to their previous BLS keys - mapping(bytes => PauseableEnumerableSet.Inner) internal blsKeyData; // Mapping from BLS keys to their associated data + mapping(bytes => PauseableEnumerableSet.Inner) internal _blsKeyData; // Mapping from BLS keys to their associated data /* * Returns the operator address associated with a given BLS key. @@ -21,7 +21,7 @@ abstract contract BLSKeyManager is BaseMiddleware { * @return The address of the operator linked to the specified BLS key. */ function operatorByBLSKey(bytes memory key) public view returns (address) { - return blsKeyData[key].getAddress(); + return _blsKeyData[key].getAddress(); } /* @@ -31,7 +31,7 @@ abstract contract BLSKeyManager is BaseMiddleware { * @return The BLS key associated with the specified operator. */ function operatorBLSKey(address operator) public view returns (bytes memory) { - if (blsKeyData[blsKeys[operator]].enabledEpoch == getCurrentEpoch()) { + if (_blsKeyData[blsKeys[operator]].enabledEpoch == getCurrentEpoch()) { return prevBLSKeys[operator]; } @@ -45,7 +45,7 @@ abstract contract BLSKeyManager is BaseMiddleware { * @return A boolean indicating whether the BLS key was active at the specified epoch. */ function blsKeyWasActiveAt(uint48 epoch, bytes memory key) public view returns (bool) { - return blsKeyData[key].wasActiveAt(epoch); + return _blsKeyData[key].wasActiveAt(epoch); } /* @@ -54,21 +54,21 @@ abstract contract BLSKeyManager is BaseMiddleware { * @param operator The address of the operator whose BLS key is to be updated. * @param key The new BLS key to associate with the operator. */ - function updateBLSKey(address operator, bytes memory key) external onlyOwner { + function updateBLSKey(address operator, bytes memory key) public virtual onlyOwner { uint48 epoch = getCurrentEpoch(); - if (blsKeyData[key].getAddress() != address(0)) { + if (_blsKeyData[key].getAddress() != address(0)) { revert DuplicateBLSKey(); } - if (keccak256(blsKeys[operator]) != ZERO_BYTES_HASH && blsKeyData[blsKeys[operator]].enabledEpoch != epoch) { + if (keccak256(blsKeys[operator]) != ZERO_BYTES_HASH && _blsKeyData[blsKeys[operator]].enabledEpoch != epoch) { prevBLSKeys[operator] = blsKeys[operator]; } blsKeys[operator] = key; if (keccak256(key) != ZERO_BYTES_HASH) { - blsKeyData[key].set(epoch, operator); + _blsKeyData[key].set(epoch, operator); } } } diff --git a/src/BaseMiddleware.sol b/src/BaseMiddleware.sol index 5244167..85126d5 100644 --- a/src/BaseMiddleware.sol +++ b/src/BaseMiddleware.sol @@ -105,7 +105,7 @@ abstract contract BaseMiddleware is Ownable { * Returns the number of subnetworks registered. * @return The count of registered subnetworks. */ - function subnetworksLength() external view returns (uint256) { + function subnetworksLength() public view returns (uint256) { return subnetworks.length(); } @@ -114,7 +114,7 @@ abstract contract BaseMiddleware is Ownable { * @param pos The index of the subnetwork. * @return The subnetwork details including address, enabled epoch, and disabled epoch. */ - function subnetworkWithTimesAt(uint256 pos) external view returns (uint160, uint48, uint48) { + function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint48, uint48) { return subnetworks.at(pos); } @@ -130,7 +130,7 @@ abstract contract BaseMiddleware is Ownable { * Registers a new subnetwork. * @param subnetwork The identifier of the subnetwork to register. */ - function registerSubnetwork(uint96 subnetwork) external onlyOwner { + function registerSubnetwork(uint96 subnetwork) public virtual onlyOwner { subnetworks.register(getNextEpoch(), uint160(subnetwork)); } @@ -138,7 +138,7 @@ abstract contract BaseMiddleware is Ownable { * Pauses a specified subnetwork. * @param subnetwork The identifier of the subnetwork to pause. */ - function pauseSubnetwork(uint96 subnetwork) external onlyOwner { + function pauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { subnetworks.pause(getNextEpoch(), uint160(subnetwork)); } @@ -146,7 +146,7 @@ abstract contract BaseMiddleware is Ownable { * Unpauses a specified subnetwork. * @param subnetwork The identifier of the subnetwork to unpause. */ - function unpauseSubnetwork(uint96 subnetwork) external onlyOwner { + function unpauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { subnetworks.unpause(getNextEpoch(), SLASHING_WINDOW, uint160(subnetwork)); } @@ -154,7 +154,7 @@ abstract contract BaseMiddleware is Ownable { * Unregisters a specified subnetwork. * @param subnetwork The identifier of the subnetwork to unregister. */ - function unregisterSubnetwork(uint96 subnetwork) external onlyOwner { + function unregisterSubnetwork(uint96 subnetwork) public virtual onlyOwner { subnetworks.unregister(getNextEpoch(), SLASHING_WINDOW, uint160(subnetwork)); } } diff --git a/src/KeyManager.sol b/src/KeyManager.sol index 2234b7c..c04863b 100644 --- a/src/KeyManager.sol +++ b/src/KeyManager.sol @@ -13,7 +13,7 @@ abstract contract KeyManager is BaseMiddleware { mapping(address => bytes32) public keys; // Mapping from operator addresses to their current keys mapping(address => bytes32) public prevKeys; // Mapping from operator addresses to their previous keys - mapping(bytes32 => PauseableEnumerableSet.Inner) internal keyData; // Mapping from keys to their associated data + mapping(bytes32 => PauseableEnumerableSet.Inner) internal _keyData; // Mapping from keys to their associated data /* * Returns the operator address associated with a given key. @@ -21,7 +21,7 @@ abstract contract KeyManager is BaseMiddleware { * @return The address of the operator linked to the specified key. */ function operatorByKey(bytes32 key) public view returns (address) { - return keyData[key].getAddress(); + return _keyData[key].getAddress(); } /* @@ -31,7 +31,7 @@ abstract contract KeyManager is BaseMiddleware { * @return The key associated with the specified operator. */ function operatorKey(address operator) public view returns (bytes32) { - if (keyData[keys[operator]].enabledEpoch == getCurrentEpoch()) { + if (_keyData[keys[operator]].enabledEpoch == getCurrentEpoch()) { return prevKeys[operator]; } @@ -45,7 +45,7 @@ abstract contract KeyManager is BaseMiddleware { * @return A boolean indicating whether the key was active at the specified epoch. */ function keyWasActiveAt(uint48 epoch, bytes32 key) public view returns (bool) { - return keyData[key].wasActiveAt(epoch); + return _keyData[key].wasActiveAt(epoch); } /* @@ -54,21 +54,21 @@ abstract contract KeyManager is BaseMiddleware { * @param operator The address of the operator whose key is to be updated. * @param key The new key to associate with the operator. */ - function updateKey(address operator, bytes32 key) external onlyOwner { + function updateKey(address operator, bytes32 key) public virtual onlyOwner { uint48 epoch = getCurrentEpoch(); - if (keyData[key].getAddress() != address(0)) { + if (_keyData[key].getAddress() != address(0)) { revert DuplicateKey(); } - if (keys[operator] != ZERO_BYTES32 && keyData[keys[operator]].enabledEpoch != epoch) { + if (keys[operator] != ZERO_BYTES32 && _keyData[keys[operator]].enabledEpoch != epoch) { prevKeys[operator] = keys[operator]; } keys[operator] = key; if (key != ZERO_BYTES32) { - keyData[key].set(epoch, operator); + _keyData[key].set(epoch, operator); } } } diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index 5ef5871..20f0427 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -19,14 +19,14 @@ abstract contract OperatorManager is BaseMiddleware { error OperatorNotRegistered(); error OperatorAlreadyRegistred(); - PauseableEnumerableSet.AddressSet internal operators; + PauseableEnumerableSet.AddressSet internal _operators; /* * Returns the length of the operators list. * @return The number of registered operators. */ - function operatorsLength() external view returns (uint256) { - return operators.length(); + function operatorsLength() public view returns (uint256) { + return _operators.length(); } /* @@ -34,8 +34,8 @@ abstract contract OperatorManager is BaseMiddleware { * @param pos The index position in the operators array. * @return The address, enabled epoch, and disabled epoch of the operator. */ - function operatorWithTimesAt(uint256 pos) external view returns (address, uint48, uint48) { - return operators.at(pos); + function operatorWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { + return _operators.at(pos); } /* @@ -43,14 +43,14 @@ abstract contract OperatorManager is BaseMiddleware { * @return An array of addresses representing the active operators. */ function activeOperators() public view returns (address[] memory) { - return operators.getActive(getCurrentEpoch()); + return _operators.getActive(getCurrentEpoch()); } /* * Registers a new operator. * @param operator The address of the operator to register. */ - function registerOperator(address operator) external onlyOwner { + function registerOperator(address operator) public virtual onlyOwner { if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { revert NotOperator(); } @@ -59,30 +59,30 @@ abstract contract OperatorManager is BaseMiddleware { revert OperatorNotOptedIn(); } - operators.register(getNextEpoch(), operator); + _operators.register(getNextEpoch(), operator); } /* * Pauses a registered operator. * @param operator The address of the operator to pause. */ - function pauseOperator(address operator) external onlyOwner { - operators.pause(getNextEpoch(), operator); + function pauseOperator(address operator) public virtual onlyOwner { + _operators.pause(getNextEpoch(), operator); } /* * Unpauses a paused operator. * @param operator The address of the operator to unpause. */ - function unpauseOperator(address operator) external onlyOwner { - operators.unpause(getNextEpoch(), SLASHING_WINDOW, operator); + function unpauseOperator(address operator) public virtual onlyOwner { + _operators.unpause(getNextEpoch(), SLASHING_WINDOW, operator); } /* * Unregisters an operator. * @param operator The address of the operator to unregister. */ - function unregisterOperator(address operator) external onlyOwner { - operators.unregister(getNextEpoch(), SLASHING_WINDOW, operator); + function unregisterOperator(address operator) public virtual onlyOwner { + _operators.unregister(getNextEpoch(), SLASHING_WINDOW, operator); } } diff --git a/src/VaultManager.sol b/src/VaultManager.sol index a245c2c..84c604b 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -32,8 +32,8 @@ abstract contract VaultManager is BaseMiddleware { error UnknownSlasherType(); error NonVetoSlasher(); - PauseableEnumerableSet.AddressSet internal sharedVaults; - mapping(address => PauseableEnumerableSet.AddressSet) internal operatorVaults; + PauseableEnumerableSet.AddressSet internal _sharedVaults; + mapping(address => PauseableEnumerableSet.AddressSet) internal _operatorVaults; struct SlashResponse { address vault; @@ -46,8 +46,8 @@ abstract contract VaultManager is BaseMiddleware { * Returns the length of shared vaults. * @return The number of shared vaults. */ - function sharedVaultsLength() external view returns (uint256) { - return sharedVaults.length(); + function sharedVaultsLength() public view returns (uint256) { + return _sharedVaults.length(); } /* @@ -55,8 +55,8 @@ abstract contract VaultManager is BaseMiddleware { * @param pos The index position in the shared vaults array. * @return The address, enabled epoch, and disabled epoch of the vault. */ - function sharedVaultWithEpochsAt(uint256 pos) external view returns (address, uint48, uint48) { - return sharedVaults.at(pos); + function sharedVaultWithEpochsAt(uint256 pos) public view returns (address, uint48, uint48) { + return _sharedVaults.at(pos); } /* @@ -64,8 +64,8 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @return The number of vaults associated with the operator. */ - function operatorVaultsLength(address operator) external view returns (uint256) { - return operatorVaults[operator].length(); + function operatorVaultsLength(address operator) public view returns (uint256) { + return _operatorVaults[operator].length(); } /* @@ -74,8 +74,8 @@ abstract contract VaultManager is BaseMiddleware { * @param pos The index position in the operator vaults array. * @return The address, enabled epoch, and disabled epoch of the vault. */ - function operatorVaultWithEpochsAt(address operator, uint256 pos) external view returns (address, uint48, uint48) { - return operatorVaults[operator].at(pos); + function operatorVaultWithEpochsAt(address operator, uint256 pos) public view returns (address, uint48, uint48) { + return _operatorVaults[operator].at(pos); } /* @@ -95,8 +95,8 @@ abstract contract VaultManager is BaseMiddleware { */ function activeVaults(address operator) public view returns (address[] memory) { uint48 epoch = getCurrentEpoch(); - address[] memory activeSharedVaults = sharedVaults.getActive(epoch); - address[] memory activeOperatorVaults = operatorVaults[operator].getActive(epoch); + address[] memory activeSharedVaults = _sharedVaults.getActive(epoch); + address[] memory activeOperatorVaults = _operatorVaults[operator].getActive(epoch); uint256 activeSharedVaultsLen = activeSharedVaults.length; address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults.length); @@ -114,9 +114,9 @@ abstract contract VaultManager is BaseMiddleware { * Registers a new shared vault. * @param vault The address of the vault to register. */ - function registerSharedVault(address vault) external onlyOwner { + function registerSharedVault(address vault) public virtual onlyOwner { _validateVault(vault); - sharedVaults.register(getNextEpoch(), vault); + _sharedVaults.register(getNextEpoch(), vault); } /* @@ -124,25 +124,25 @@ abstract contract VaultManager is BaseMiddleware { * @param vault The address of the vault to register. * @param operator The address of the operator. */ - function registerOperatorVault(address vault, address operator) external onlyOwner { + function registerOperatorVault(address vault, address operator) public virtual onlyOwner { _validateVault(vault); - operatorVaults[operator].register(getNextEpoch(), vault); + _operatorVaults[operator].register(getNextEpoch(), vault); } /* * Pauses a shared vault. * @param vault The address of the vault to pause. */ - function pauseSharedVault(address vault) external onlyOwner { - sharedVaults.pause(getNextEpoch(), vault); + function pauseSharedVault(address vault) public virtual onlyOwner { + _sharedVaults.pause(getNextEpoch(), vault); } /* * Unpauses a shared vault. * @param vault The address of the vault to unpause. */ - function unpauseSharedVault(address vault) external onlyOwner { - sharedVaults.unpause(getNextEpoch(), SLASHING_WINDOW, vault); + function unpauseSharedVault(address vault) public virtual onlyOwner { + _sharedVaults.unpause(getNextEpoch(), SLASHING_WINDOW, vault); } /* @@ -150,8 +150,8 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @param vault The address of the vault to pause. */ - function pauseOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].pause(getNextEpoch(), vault); + function pauseOperatorVault(address operator, address vault) public virtual onlyOwner { + _operatorVaults[operator].pause(getNextEpoch(), vault); } /* @@ -159,16 +159,16 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @param vault The address of the vault to unpause. */ - function unpauseOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].unpause(getNextEpoch(), SLASHING_WINDOW, vault); + function unpauseOperatorVault(address operator, address vault) public virtual onlyOwner { + _operatorVaults[operator].unpause(getNextEpoch(), SLASHING_WINDOW, vault); } /* * Unregisters a shared vault. * @param vault The address of the vault to unregister. */ - function unregisterSharedVault(address vault) external onlyOwner { - sharedVaults.unregister(getNextEpoch(), SLASHING_WINDOW, vault); + function unregisterSharedVault(address vault) public virtual onlyOwner { + _sharedVaults.unregister(getNextEpoch(), SLASHING_WINDOW, vault); } /* @@ -176,8 +176,8 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @param vault The address of the vault to unregister. */ - function unregisterOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].unregister(getNextEpoch(), SLASHING_WINDOW, vault); + function unregisterOperatorVault(address operator, address vault) public virtual onlyOwner { + _operatorVaults[operator].unregister(getNextEpoch(), SLASHING_WINDOW, vault); } /* @@ -231,7 +231,7 @@ abstract contract VaultManager is BaseMiddleware { * @param operators The list of operator addresses. * @return The total stake of the operators. */ - function totalStake(uint48 epoch, address[] memory operators) internal view returns (uint256 stake) { + function _totalStake(uint48 epoch, address[] memory operators) internal view returns (uint256 stake) { for (uint256 i; i < operators.length; ++i) { uint256 operatorStake = getOperatorStake(epoch, operators[i]); stake += operatorStake; @@ -250,7 +250,7 @@ abstract contract VaultManager is BaseMiddleware { * @param hints Additional data for the slasher. * @return A struct containing information about the slash response. */ - function slashVault( + function _slashVault( uint48 timestamp, address vault, bytes32 subnetwork, @@ -258,7 +258,7 @@ abstract contract VaultManager is BaseMiddleware { uint256 amount, bytes calldata hints ) internal returns (SlashResponse memory resp) { - if (!sharedVaults.contains(vault)) { + if (!_sharedVaults.contains(vault)) { revert NotVault(); } @@ -285,11 +285,12 @@ abstract contract VaultManager is BaseMiddleware { * @return The amount that was slashed. */ function executeSlash(address vault, address operator, uint256 slashIndex, bytes calldata hints) - external + public + virtual onlyOwner returns (uint256 slashedAmount) { - if (!(sharedVaults.contains(vault) || operatorVaults[operator].contains(vault))) { + if (!(_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault))) { revert NotVault(); } diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index 93848eb..395d9ca 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -55,7 +55,7 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { */ function getTotalStake() public view returns (uint256) { address[] memory operators = activeOperators(); // Get the list of active operators - return totalStake(getCurrentEpoch(), operators); // Return the total stake for the current epoch + return _totalStake(getCurrentEpoch(), operators); // Return the total stake for the current epoch } /* @@ -146,7 +146,7 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { continue; // Skip if the slashing amount is zero } - slashResponses[len++] = slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); // Execute the slashing + slashResponses[len++] = _slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); // Execute the slashing } } diff --git a/src/examples/simple-network/SqrtTaskMiddleware.sol b/src/examples/simple-network/SqrtTaskMiddleware.sol new file mode 100644 index 0000000..5874a89 --- /dev/null +++ b/src/examples/simple-network/SqrtTaskMiddleware.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; + +import {VaultManager} from "../../VaultManager.sol"; +import {OperatorManager} from "../../OperatorManager.sol"; +import {KeyManager} from "../../KeyManager.sol"; +import {BaseMiddleware} from "../../BaseMiddleware.sol"; + +contract SqrtTaskMiddleware is VaultManager, OperatorManager, KeyManager, EIP712 { + using Subnetwork for address; + using Math for uint256; + + error InvalidHints(); + error InvalidSignature(); + error TaskCompleted(); + + event CreateTask(uint256 indexed taskIndex); + event CompleteTask(uint256 indexed taskIndex, bool isValidAnswer); + + struct Task { + uint48 captureTimestamp; + uint256 value; + address operator; + bool completed; + } + + bytes32 private constant COMPLETE_TASK_TYPEHASH = keccak256("CompleteTask(uint256 taskIndex,uint256 answer)"); + + Task[] public tasks; + + constructor( + address network, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + address owner, + uint48 epochDuration, + uint48 slashingWindow + ) + BaseMiddleware(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) + EIP712("SqrtTaskMiddleware", "1") + {} + + function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { + taskIndex = tasks.length; + tasks.push(Task({captureTimestamp: Time.timestamp() - 1, value: value, operator: operator, completed: false})); + + emit CreateTask(taskIndex); + } + + function completeTask( + uint256 taskIndex, + uint256 answer, + bytes calldata signature, + bytes[] calldata stakeHints, + bytes[] calldata slashHints + ) external returns (bool isValidAnswer) { + isValidAnswer = _verify(taskIndex, answer, signature); + + tasks[taskIndex].completed = true; + + if (!isValidAnswer) { + _slash(taskIndex, stakeHints, slashHints); + } + + emit CompleteTask(taskIndex, isValidAnswer); + } + + function _verify(uint256 taskIndex, uint256 answer, bytes calldata signature) private returns (bool) { + if (tasks[taskIndex].completed) { + revert TaskCompleted(); + } + _verifySignature(taskIndex, answer, signature); + return _verifyAnswer(taskIndex, answer); + } + + function _verifySignature(uint256 taskIndex, uint256 answer, bytes calldata signature) private { + Task storage task = tasks[taskIndex]; + + bytes32 hash_ = _hashTypedDataV4(keccak256(abi.encode(COMPLETE_TASK_TYPEHASH, taskIndex, answer))); + + if (!SignatureChecker.isValidSignatureNow(task.operator, hash_, signature)) { + revert InvalidSignature(); + } + } + + function _verifyAnswer(uint256 taskIndex, uint256 answer) private returns (bool) { + uint256 value = tasks[taskIndex].value; + uint256 square = answer ** 2; + if (square == value) { + return true; + } + + if (square < value) { + uint256 difference = value - square; + uint256 nextSquare = (answer + 1) ** 2; + uint256 nextDifference = nextSquare > value ? nextSquare - value : value - nextSquare; + if (difference <= nextDifference) { + return true; + } + } else { + uint256 difference = square - value; + uint256 prevSquare = (answer - 1) ** 2; + uint256 prevDifference = prevSquare > value ? prevSquare - value : value - prevSquare; + if (difference <= prevDifference) { + return true; + } + } + + return false; + } + + function _slash(uint256 taskIndex, bytes[] calldata stakeHints, bytes[] calldata slashHints) private { + Task storage task = tasks[taskIndex]; + address[] memory vaults = activeVaults(task.operator); + + if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { + revert InvalidHints(); + } + + bytes32 subnetwork = NETWORK.subnetwork(0); + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + uint256 slashAmount = IBaseDelegator(IVault(vault).delegator()).stakeAt( + subnetwork, task.operator, task.captureTimestamp, stakeHints[i] + ); + + if (slashAmount == 0) { + continue; + } + + _slashVault(task.captureTimestamp, vault, subnetwork, task.operator, slashAmount, slashHints[i]); + } + } + + /* + * inheritdoc BaseMiddleware + */ + function registerSubnetwork(uint96 subnetwork) public override { + revert(); + } + + /* + * inheritdoc BaseMiddleware + */ + function pauseSubnetwork(uint96 subnetwork) public override { + revert(); + } + + /* + * inheritdoc BaseMiddleware + */ + function unpauseSubnetwork(uint96 subnetwork) public override { + revert(); + } + + /* + * inheritdoc BaseMiddleware + */ + function unregisterSubnetwork(uint96 subnetwork) public override { + revert(); + } +} From 7d8c5d947b797c9e64a847604fd4513609f44978 Mon Sep 17 00:00:00 2001 From: Kresh Date: Fri, 18 Oct 2024 11:57:49 +0400 Subject: [PATCH 008/115] chore: set openzeppelin@v5.0.2 --- lib/openzeppelin-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 0034c30..dbb6104 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 0034c302241c4b1a1685272d4df42ca5d64b8c34 +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From f4d1117dff154eb94d344848baa14ac4ca9df6e6 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Wed, 23 Oct 2024 17:55:03 +0400 Subject: [PATCH 009/115] test: add simple middleware tests --- src/BLSKeyManager.sol | 8 +- src/BaseMiddleware.sol | 59 +- src/KeyManager.sol | 8 +- src/OperatorManager.sol | 22 +- src/VaultManager.sol | 61 +- .../simple-network/SimpleMiddleware.sol | 11 +- src/libraries/PauseableEnumerableSet.sol | 58 +- test/SimpleMiddleware.t.sol | 805 ++++++++++-------- 8 files changed, 558 insertions(+), 474 deletions(-) diff --git a/src/BLSKeyManager.sol b/src/BLSKeyManager.sol index c98fc0f..683d8a4 100644 --- a/src/BLSKeyManager.sol +++ b/src/BLSKeyManager.sol @@ -16,7 +16,7 @@ abstract contract BLSKeyManager is BaseMiddleware { mapping(bytes => PauseableEnumerableSet.Inner) internal blsKeyData; // Mapping from BLS keys to their associated data /* - * Returns the operator address associated with a given BLS key. + * @notice Returns the operator address associated with a given BLS key. * @param key The BLS key for which to find the associated operator. * @return The address of the operator linked to the specified BLS key. */ @@ -25,7 +25,7 @@ abstract contract BLSKeyManager is BaseMiddleware { } /* - * Returns the current BLS key for a given operator. + * @notice Returns the current BLS key for a given operator. * If the key has changed in the current epoch, returns the previous key. * @param operator The address of the operator. * @return The BLS key associated with the specified operator. @@ -39,7 +39,7 @@ abstract contract BLSKeyManager is BaseMiddleware { } /* - * Checks if a given BLS key was active at a specified epoch. + * @notice Checks if a given BLS key was active at a specified epoch. * @param epoch The epoch to check for key activity. * @param key The BLS key to check. * @return A boolean indicating whether the BLS key was active at the specified epoch. @@ -49,7 +49,7 @@ abstract contract BLSKeyManager is BaseMiddleware { } /* - * Updates the BLS key associated with an operator. + * @notice Updates the BLS key associated with an operator. * If the new key already exists, a DuplicateBLSKey error is thrown. * @param operator The address of the operator whose BLS key is to be updated. * @param key The new BLS key to associate with the operator. diff --git a/src/BaseMiddleware.sol b/src/BaseMiddleware.sol index 5244167..c1f1030 100644 --- a/src/BaseMiddleware.sol +++ b/src/BaseMiddleware.sol @@ -9,10 +9,13 @@ import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; abstract contract BaseMiddleware is Ownable { using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; - error ZeroSlashingWindow(); // Error thrown when the slashing window is set to zero + error SlashingWindowTooShort(); // Error thrown when the slashing window is lower than epoch address public immutable NETWORK; // Address of the network + uint48 public immutable EPOCH_DURATION; // Duration of each epoch + uint48 public immutable START_TIME; // Start time of the epoch uint48 public immutable SLASHING_WINDOW; // Duration of the slashing window + uint48 public immutable IMMUTABLE_EPOCHS; // Duration of the state immutability in epochs address public immutable VAULT_REGISTRY; // Address of the vault registry address public immutable OPERATOR_REGISTRY; // Address of the operator registry address public immutable OPERATOR_NET_OPTIN; // Address of the operator network opt-in service @@ -21,13 +24,10 @@ abstract contract BaseMiddleware is Ownable { uint64 public constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type uint96 public constant DEFAULT_SUBNETWORK = 0; // Default subnetwork identifier - uint48 public immutable EPOCH_DURATION; // Duration of each epoch - uint48 public immutable START_TIME; // Start time of the epoch - PauseableEnumerableSet.Uint160Set subnetworks; // Set of active subnetworks /* - * Constructor for initializing the BaseMiddleware contract. + * @notice Constructor for initializing the BaseMiddleware contract. * @param owner The address of the contract owner. * @param network The address of the network. * @param epochDuration The duration of each epoch. @@ -45,13 +45,14 @@ abstract contract BaseMiddleware is Ownable { address operatorRegistry, address operatorNetOptIn ) Ownable(owner) { - if (SLASHING_WINDOW == 0) { - revert ZeroSlashingWindow(); + if (slashingWindow < epochDuration) { + revert SlashingWindowTooShort(); } NETWORK = network; EPOCH_DURATION = epochDuration; SLASHING_WINDOW = slashingWindow; + IMMUTABLE_EPOCHS = (slashingWindow + epochDuration - 1) / epochDuration; VAULT_REGISTRY = vaultRegistry; OPERATOR_REGISTRY = operatorRegistry; OPERATOR_NET_OPTIN = operatorNetOptIn; @@ -60,25 +61,25 @@ abstract contract BaseMiddleware is Ownable { } /* - * Returns the start timestamp of a given epoch. + * @notice Returns the start timestamp of a given epoch. * @param epoch The epoch number. * @return The start timestamp of the specified epoch. */ function getEpochStart(uint48 epoch) public view returns (uint48 timestamp) { - return START_TIME + epoch * EPOCH_DURATION; + return START_TIME + (epoch - 1) * EPOCH_DURATION; } /* - * Returns the epoch number corresponding to a given timestamp. + * @notice Returns the epoch number corresponding to a given timestamp. * @param timestamp The timestamp to convert to an epoch number. * @return The epoch number associated with the specified timestamp. */ function getEpochAt(uint48 timestamp) public view returns (uint48 epoch) { - return (timestamp - START_TIME) / EPOCH_DURATION; + return (timestamp - START_TIME) / EPOCH_DURATION + 1; } /* - * Returns the current epoch number based on the current timestamp. + * @notice Returns the current epoch number based on the current timestamp. * @return The current epoch number. */ function getCurrentEpoch() public view returns (uint48 epoch) { @@ -86,23 +87,15 @@ abstract contract BaseMiddleware is Ownable { } /* - * Returns the next epoch number. - * @return The next epoch number. - */ - function getNextEpoch() public view returns (uint48 epoch) { - return getCurrentEpoch() + 1; - } - - /* - * Returns the start timestamp of the current epoch. + * @notice Returns the start timestamp of the current epoch. * @return The start timestamp of the current epoch. */ function getCurrentEpochStart() public view returns (uint48 timestamp) { - return START_TIME + getCurrentEpoch() * EPOCH_DURATION; + return START_TIME + (getCurrentEpoch() - 1) * EPOCH_DURATION; } /* - * Returns the number of subnetworks registered. + * @notice Returns the number of subnetworks registered. * @return The count of registered subnetworks. */ function subnetworksLength() external view returns (uint256) { @@ -110,7 +103,7 @@ abstract contract BaseMiddleware is Ownable { } /* - * Returns the subnetwork information at a specified position. + * @notice Returns the subnetwork information at a specified position. * @param pos The index of the subnetwork. * @return The subnetwork details including address, enabled epoch, and disabled epoch. */ @@ -119,7 +112,7 @@ abstract contract BaseMiddleware is Ownable { } /* - * Returns an array of active subnetworks for the current epoch. + * @notice Returns an array of active subnetworks for the current epoch. * @return An array of active subnetwork addresses. */ function activeSubnetworks() public view returns (uint160[] memory) { @@ -127,34 +120,34 @@ abstract contract BaseMiddleware is Ownable { } /* - * Registers a new subnetwork. + * @notice Registers a new subnetwork. * @param subnetwork The identifier of the subnetwork to register. */ function registerSubnetwork(uint96 subnetwork) external onlyOwner { - subnetworks.register(getNextEpoch(), uint160(subnetwork)); + subnetworks.register(getCurrentEpoch(), uint160(subnetwork)); } /* - * Pauses a specified subnetwork. + * @notice Pauses a specified subnetwork. * @param subnetwork The identifier of the subnetwork to pause. */ function pauseSubnetwork(uint96 subnetwork) external onlyOwner { - subnetworks.pause(getNextEpoch(), uint160(subnetwork)); + subnetworks.pause(getCurrentEpoch(), uint160(subnetwork)); } /* - * Unpauses a specified subnetwork. + * @notice Unpauses a specified subnetwork. * @param subnetwork The identifier of the subnetwork to unpause. */ function unpauseSubnetwork(uint96 subnetwork) external onlyOwner { - subnetworks.unpause(getNextEpoch(), SLASHING_WINDOW, uint160(subnetwork)); + subnetworks.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); } /* - * Unregisters a specified subnetwork. + * @notice Unregisters a specified subnetwork. * @param subnetwork The identifier of the subnetwork to unregister. */ function unregisterSubnetwork(uint96 subnetwork) external onlyOwner { - subnetworks.unregister(getNextEpoch(), SLASHING_WINDOW, uint160(subnetwork)); + subnetworks.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); } } diff --git a/src/KeyManager.sol b/src/KeyManager.sol index 2234b7c..fc4ea00 100644 --- a/src/KeyManager.sol +++ b/src/KeyManager.sol @@ -16,7 +16,7 @@ abstract contract KeyManager is BaseMiddleware { mapping(bytes32 => PauseableEnumerableSet.Inner) internal keyData; // Mapping from keys to their associated data /* - * Returns the operator address associated with a given key. + * @notice Returns the operator address associated with a given key. * @param key The key for which to find the associated operator. * @return The address of the operator linked to the specified key. */ @@ -25,7 +25,7 @@ abstract contract KeyManager is BaseMiddleware { } /* - * Returns the current key for a given operator. + * @notice Returns the current key for a given operator. * If the key has changed in the current epoch, returns the previous key. * @param operator The address of the operator. * @return The key associated with the specified operator. @@ -39,7 +39,7 @@ abstract contract KeyManager is BaseMiddleware { } /* - * Checks if a given key was active at a specified epoch. + * @notice Checks if a given key was active at a specified epoch. * @param epoch The epoch to check for key activity. * @param key The key to check. * @return A boolean indicating whether the key was active at the specified epoch. @@ -49,7 +49,7 @@ abstract contract KeyManager is BaseMiddleware { } /* - * Updates the key associated with an operator. + * @notice Updates the key associated with an operator. * If the new key already exists, a DuplicateKey error is thrown. * @param operator The address of the operator whose key is to be updated. * @param key The new key to associate with the operator. diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index 5ef5871..6a12918 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -22,7 +22,7 @@ abstract contract OperatorManager is BaseMiddleware { PauseableEnumerableSet.AddressSet internal operators; /* - * Returns the length of the operators list. + * @notice Returns the length of the operators list. * @return The number of registered operators. */ function operatorsLength() external view returns (uint256) { @@ -30,7 +30,7 @@ abstract contract OperatorManager is BaseMiddleware { } /* - * Returns the operator and their associated enabled and disabled times at a specific position. + * @notice Returns the operator and their associated enabled and disabled times at a specific position. * @param pos The index position in the operators array. * @return The address, enabled epoch, and disabled epoch of the operator. */ @@ -39,7 +39,7 @@ abstract contract OperatorManager is BaseMiddleware { } /* - * Returns a list of active operators. + * @notice Returns a list of active operators. * @return An array of addresses representing the active operators. */ function activeOperators() public view returns (address[] memory) { @@ -47,7 +47,7 @@ abstract contract OperatorManager is BaseMiddleware { } /* - * Registers a new operator. + * @notice Registers a new operator. * @param operator The address of the operator to register. */ function registerOperator(address operator) external onlyOwner { @@ -59,30 +59,30 @@ abstract contract OperatorManager is BaseMiddleware { revert OperatorNotOptedIn(); } - operators.register(getNextEpoch(), operator); + operators.register(getCurrentEpoch(), operator); } /* - * Pauses a registered operator. + * @notice Pauses a registered operator. * @param operator The address of the operator to pause. */ function pauseOperator(address operator) external onlyOwner { - operators.pause(getNextEpoch(), operator); + operators.pause(getCurrentEpoch(), operator); } /* - * Unpauses a paused operator. + * @notice Unpauses a paused operator. * @param operator The address of the operator to unpause. */ function unpauseOperator(address operator) external onlyOwner { - operators.unpause(getNextEpoch(), SLASHING_WINDOW, operator); + operators.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, operator); } /* - * Unregisters an operator. + * @notice Unregisters an operator. * @param operator The address of the operator to unregister. */ function unregisterOperator(address operator) external onlyOwner { - operators.unregister(getNextEpoch(), SLASHING_WINDOW, operator); + operators.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, operator); } } diff --git a/src/VaultManager.sol b/src/VaultManager.sol index a245c2c..761ddb4 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -31,6 +31,7 @@ abstract contract VaultManager is BaseMiddleware { error InvalidSubnetworksCnt(); error UnknownSlasherType(); error NonVetoSlasher(); + error TooOldTimestampSlash(); PauseableEnumerableSet.AddressSet internal sharedVaults; mapping(address => PauseableEnumerableSet.AddressSet) internal operatorVaults; @@ -43,7 +44,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Returns the length of shared vaults. + * @notice Returns the length of shared vaults. * @return The number of shared vaults. */ function sharedVaultsLength() external view returns (uint256) { @@ -51,7 +52,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Returns the address and epoch information of a shared vault at a specific position. + * @notice Returns the address and epoch information of a shared vault at a specific position. * @param pos The index position in the shared vaults array. * @return The address, enabled epoch, and disabled epoch of the vault. */ @@ -60,7 +61,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Returns the length of operator vaults for a specific operator. + * @notice Returns the length of operator vaults for a specific operator. * @param operator The address of the operator. * @return The number of vaults associated with the operator. */ @@ -69,7 +70,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Returns the address and epoch information of an operator vault at a specific position. + * @notice Returns the address and epoch information of an operator vault at a specific position. * @param operator The address of the operator. * @param pos The index position in the operator vaults array. * @return The address, enabled epoch, and disabled epoch of the vault. @@ -79,7 +80,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Converts stake to power for a vault. + * @notice Converts stake to power for a vault. * @param vault The address of the vault. * @param stake The amount of stake to convert. * @return The power calculated from the stake. @@ -89,7 +90,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Returns the list of active vaults for a specific operator. + * @notice Returns the list of active vaults for a specific operator. * @param operator The address of the operator. * @return An array of addresses representing the active vaults. */ @@ -111,77 +112,77 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Registers a new shared vault. + * @notice Registers a new shared vault. * @param vault The address of the vault to register. */ function registerSharedVault(address vault) external onlyOwner { _validateVault(vault); - sharedVaults.register(getNextEpoch(), vault); + sharedVaults.register(getCurrentEpoch(), vault); } /* - * Registers a new operator vault. + * @notice Registers a new operator vault. * @param vault The address of the vault to register. * @param operator The address of the operator. */ function registerOperatorVault(address vault, address operator) external onlyOwner { _validateVault(vault); - operatorVaults[operator].register(getNextEpoch(), vault); + operatorVaults[operator].register(getCurrentEpoch(), vault); } /* - * Pauses a shared vault. + * @notice Pauses a shared vault. * @param vault The address of the vault to pause. */ function pauseSharedVault(address vault) external onlyOwner { - sharedVaults.pause(getNextEpoch(), vault); + sharedVaults.pause(getCurrentEpoch(), vault); } /* - * Unpauses a shared vault. + * @notice Unpauses a shared vault. * @param vault The address of the vault to unpause. */ function unpauseSharedVault(address vault) external onlyOwner { - sharedVaults.unpause(getNextEpoch(), SLASHING_WINDOW, vault); + sharedVaults.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); } /* - * Pauses an operator vault. + * @notice Pauses an operator vault. * @param operator The address of the operator. * @param vault The address of the vault to pause. */ function pauseOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].pause(getNextEpoch(), vault); + operatorVaults[operator].pause(getCurrentEpoch(), vault); } /* - * Unpauses an operator vault. + * @notice Unpauses an operator vault. * @param operator The address of the operator. * @param vault The address of the vault to unpause. */ function unpauseOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].unpause(getNextEpoch(), SLASHING_WINDOW, vault); + operatorVaults[operator].unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); } /* - * Unregisters a shared vault. + * @notice Unregisters a shared vault. * @param vault The address of the vault to unregister. */ function unregisterSharedVault(address vault) external onlyOwner { - sharedVaults.unregister(getNextEpoch(), SLASHING_WINDOW, vault); + sharedVaults.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); } /* - * Unregisters an operator vault. + * @notice Unregisters an operator vault. * @param operator The address of the operator. * @param vault The address of the vault to unregister. */ function unregisterOperatorVault(address operator, address vault) external onlyOwner { - operatorVaults[operator].unregister(getNextEpoch(), SLASHING_WINDOW, vault); + operatorVaults[operator].unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); } /* - * Returns the stake of an operator at a specific epoch. + * @notice Returns the stake of an operator at a specific epoch. * @param epoch The epoch to check. * @param operator The address of the operator. * @return The stake of the operator. @@ -203,7 +204,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Returns the power of an operator at a specific epoch. + * @notice Returns the power of an operator at a specific epoch. * @param epoch The epoch to check. * @param operator The address of the operator. * @return The power of the operator. @@ -226,7 +227,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Returns the total stake of multiple operators at a specific epoch. + * @notice Returns the total stake of multiple operators at a specific epoch. * @param epoch The epoch to check. * @param operators The list of operator addresses. * @return The total stake of the operators. @@ -241,7 +242,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Slashes a vault based on provided conditions. + * @notice Slashes a vault based on provided conditions. * @param timestamp The timestamp when the slash occurs. * @param vault The address of the vault. * @param subnetwork The subnetwork identifier. @@ -262,6 +263,10 @@ abstract contract VaultManager is BaseMiddleware { revert NotVault(); } + if (timestamp + SLASHING_WINDOW < Time.timestamp()) { + revert TooOldTimestampSlash(); + } + address slasher = IVault(vault).slasher(); uint64 slasherType = IEntity(slasher).TYPE(); resp.vault = vault; @@ -277,7 +282,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Executes a veto-based slash for a vault. + * @notice Executes a veto-based slash for a vault. * @param vault The address of the vault. * @param operator The address of the operator. * @param slashIndex The index of the slash to execute. @@ -303,7 +308,7 @@ abstract contract VaultManager is BaseMiddleware { } /* - * Validates if the vault is properly initialized and registered. + * @notice Validates if the vault is properly initialized and registered. * @param vault The address of the vault to validate. */ function _validateVault(address vault) private view { diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index 93848eb..b15e2bc 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -27,15 +27,14 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { } /* - * Constructor for initializing the SimpleMiddleware contract. + * @notice Constructor for initializing the SimpleMiddleware contract. * @param network The address of the network. * @param operatorRegistry The address of the operator registry. * @param vaultRegistry The address of the vault registry. * @param operatorNetOptin The address of the operator network opt-in service. * @param owner The address of the contract owner. * @param epochDuration The duration of each epoch. - * @param slashingWindow The duration of the slashing window. It's an epochDuration multuplier - * slashingWindowDuration = slashingWindow * epochDuration + * @param slashingWindow The duration of the slashing window */ constructor( address network, @@ -50,7 +49,7 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { {} /* - * Returns the total stake for the active operators in the current epoch. + * @notice Returns the total stake for the active operators in the current epoch. * @return The total stake amount. */ function getTotalStake() public view returns (uint256) { @@ -59,7 +58,7 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { } /* - * Returns the current validator set as an array of ValidatorData. + * @notice Returns the current validator set as an array of ValidatorData. * @return An array of ValidatorData containing the power and key of each validator. */ function getValidatorSet() public view returns (ValidatorData[] memory validatorSet) { @@ -86,7 +85,7 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { } /* - * Slashes a validator based on the provided parameters. + * @notice Slashes a validator based on the provided parameters. * Here are the hints getter * https://github.com/symbioticfi/core/blob/main/src/contracts/hints/SlasherHints.sol * https://github.com/symbioticfi/core/blob/main/src/contracts/hints/DelegatorHints.sol diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 3a4df9e..40c58f9 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -37,7 +37,7 @@ library PauseableEnumerableSet { error ImmutablePeriodNotPassed(); // Thrown when an action is attempted before immutable period passes. /* - * Returns the length of the AddressSet. + * @notice Returns the length of the AddressSet. * @param self The AddressSet storage. * @return The number of elements in the set. */ @@ -46,7 +46,7 @@ library PauseableEnumerableSet { } /* - * Returns the address and its active period at a given position in the AddressSet. + * @notice Returns the address and its active period at a given position in the AddressSet. * @param self The AddressSet storage. * @param pos The position in the set. * @return The address, enabled epoch, and disabled epoch at the position. @@ -57,7 +57,7 @@ library PauseableEnumerableSet { } /* - * Retrieves all active addresses at a given epoch. + * @notice Retrieves all active addresses at a given epoch. * @param self The AddressSet storage. * @param epoch The epoch to check. * @return An array of active addresses. @@ -73,7 +73,7 @@ library PauseableEnumerableSet { } /* - * Registers a new address at a given epoch. + * @notice Registers a new address at a given epoch. * @param self The AddressSet storage. * @param epoch The epoch when the address is added. * @param addr The address to register. @@ -83,7 +83,7 @@ library PauseableEnumerableSet { } /* - * Pauses an address at a given epoch. + * @notice Pauses an address at a given epoch. * @param self The AddressSet storage. * @param epoch The epoch when the address is paused. * @param addr The address to pause. @@ -93,7 +93,7 @@ library PauseableEnumerableSet { } /* - * Unpauses an address, re-enabling it after the immutable period. + * @notice Unpauses an address, re-enabling it after the immutable period. * @param self The AddressSet storage. * @param epoch The current epoch. * @param immutableEpochs The required immutable period before unpausing. @@ -104,7 +104,7 @@ library PauseableEnumerableSet { } /* - * Unregisters an address, removing it from the set. + * @notice Unregisters an address, removing it from the set. * @param self The AddressSet storage. * @param epoch The current epoch. * @param immutableEpochs The required immutable period before unregistering. @@ -115,7 +115,7 @@ library PauseableEnumerableSet { } /* - * Checks if an address is contained in the AddressSet. + * @notice Checks if an address is contained in the AddressSet. * @param self The AddressSet storage. * @param addr The address to check. * @return True if the address is in the set, false otherwise. @@ -125,7 +125,7 @@ library PauseableEnumerableSet { } /* - * Returns the number of elements in the Uint160Set. + * @notice Returns the number of elements in the Uint160Set. * @param self The Uint160Set storage. * @return The number of elements. */ @@ -134,7 +134,7 @@ library PauseableEnumerableSet { } /* - * Returns the value and its active period at a given position in the Uint160Set. + * @notice Returns the value and its active period at a given position in the Uint160Set. * @param self The Uint160Set storage. * @param pos The position in the set. * @return The value, enabled epoch, and disabled epoch at the position. @@ -144,7 +144,7 @@ library PauseableEnumerableSet { } /* - * Retrieves all active values at a given epoch. + * @notice Retrieves all active values at a given epoch. * @param self The Uint160Set storage. * @param epoch The epoch to check. * @return An array of active values. @@ -167,7 +167,7 @@ library PauseableEnumerableSet { } /* - * Registers a new Uint160 value at a given epoch. + * @notice Registers a new Uint160 value at a given epoch. * @param self The Uint160Set storage. * @param epoch The epoch when the value is added. * @param value The Uint160 value to register. @@ -184,7 +184,7 @@ library PauseableEnumerableSet { } /* - * Pauses a Uint160 value at a given epoch. + * @notice Pauses a Uint160 value at a given epoch. * @param self The Uint160Set storage. * @param epoch The epoch when the value is paused. * @param value The Uint160 value to pause. @@ -198,7 +198,7 @@ library PauseableEnumerableSet { } /* - * Unpauses a Uint160 value after the immutable period. + * @notice Unpauses a Uint160 value after the immutable period. * @param self The Uint160Set storage. * @param epoch The current epoch. * @param immutableEpochs The required immutable period before unpausing. @@ -214,7 +214,7 @@ library PauseableEnumerableSet { } /* - * Unregisters a Uint160 value from the set. + * @notice Unregisters a Uint160 value from the set. * @param self The Uint160Set storage. * @param epoch The current epoch. * @param immutableEpochs The required immutable period before unregistering. @@ -225,6 +225,12 @@ library PauseableEnumerableSet { revert NotRegistered(); } + if (self.array.length == 1 || self.array.length == self.positions[value]) { + delete self.positions[value]; + self.array.pop(); + return; + } + uint256 pos = self.positions[value] - 1; self.array[pos].validateUnregister(epoch, immutableEpochs); self.array[pos] = self.array[self.array.length - 1]; @@ -235,7 +241,7 @@ library PauseableEnumerableSet { } /* - * Checks if a Uint160 value is contained in the Uint160Set. + * @notice Checks if a Uint160 value is contained in the Uint160Set. * @param self The Uint160Set storage. * @param value The Uint160 value to check. * @return True if the value is in the set, false otherwise. @@ -245,7 +251,7 @@ library PauseableEnumerableSet { } /* - * Returns the address stored in the Inner struct. + * @notice Returns the address stored in the Inner struct. * @param self The Inner struct * @return The stored Uint160 as address */ @@ -254,7 +260,7 @@ library PauseableEnumerableSet { } /* - * Returns the value and its active period from the Inner struct. + * @notice @notice Returns the value and its active period from the Inner struct. * @param self The Inner struct. * @return The value, enabled epoch, and disabled epoch. */ @@ -263,7 +269,7 @@ library PauseableEnumerableSet { } /* - * Sets the value and marks it as enabled at a given epoch. + * @notice Sets the value and marks it as enabled at a given epoch. * @param self The Inner struct. * @param epoch The epoch when the value is set. * @param value The Uint160 value to store. @@ -275,7 +281,7 @@ library PauseableEnumerableSet { } /* - * Sets the address and marks it as enabled at a given epoch. + * @notice Sets the address and marks it as enabled at a given epoch. * @param self The Inner struct. * @param epoch The epoch when the address is set. * @param addr The address to store. @@ -287,7 +293,7 @@ library PauseableEnumerableSet { } /* - * Enables the value at a given epoch. + * @notice Enables the value at a given epoch. * @param self The Inner struct. * @param epoch The epoch when the value is enabled. */ @@ -301,7 +307,7 @@ library PauseableEnumerableSet { } /* - * Disables the value at a given epoch. + * @notice Disables the value at a given epoch. * @param self The Inner struct. * @param epoch The epoch when the value is disabled. */ @@ -314,18 +320,18 @@ library PauseableEnumerableSet { } /* - * Checks if the value was active at a given epoch. + * @notice Checks if the value was active at a given epoch. * @param self The Inner struct. * @param epoch The epoch to check. * @return True if the value was active at the epoch, false otherwise. */ function wasActiveAt(Inner storage self, uint48 epoch) internal view returns (bool) { - return self.enabledEpoch != 0 && self.enabledEpoch <= epoch + return (self.enabledEpoch != 0 && self.enabledEpoch < epoch) && (self.disabledEpoch == 0 || self.disabledEpoch >= epoch); } /* - * Validates whether the value can be unpaused at a given epoch. + * @notice Validates whether the value can be unpaused at a given epoch. * @param self The Inner struct. * @param epoch The current epoch. * @param immutableEpochs The immutable period that must pass before unpausing. @@ -337,7 +343,7 @@ library PauseableEnumerableSet { } /* - * Validates whether the value can be unregistered at a given epoch. + * @notice Validates whether the value can be unregistered at a given epoch. * @param self The Inner struct. * @param epoch The current epoch. * @param immutableEpochs The immutable period that must pass before unregistering. diff --git a/test/SimpleMiddleware.t.sol b/test/SimpleMiddleware.t.sol index f9f2271..cbd7324 100644 --- a/test/SimpleMiddleware.t.sol +++ b/test/SimpleMiddleware.t.sol @@ -1,362 +1,443 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity 0.8.25; - -// import {Test, console2} from "forge-std/Test.sol"; - -// import {SimpleMiddleware} from "src/examples/simple-network/SimpleMiddleware.sol"; - -// import {VaultFactory} from "@symbiotic/contracts/VaultFactory.sol"; -// import {DelegatorFactory} from "@symbiotic/contracts/DelegatorFactory.sol"; -// import {SlasherFactory} from "@symbiotic/contracts/SlasherFactory.sol"; -// import {NetworkRegistry} from "@symbiotic/contracts/NetworkRegistry.sol"; -// import {OperatorRegistry} from "@symbiotic/contracts/OperatorRegistry.sol"; -// import {MetadataService} from "@symbiotic/contracts/service/MetadataService.sol"; -// import {NetworkMiddlewareService} from "@symbiotic/contracts/service/NetworkMiddlewareService.sol"; -// import {OptInService} from "@symbiotic/contracts/service/OptInService.sol"; - -// import {Vault} from "@symbiotic/contracts/vault/Vault.sol"; -// import {NetworkRestakeDelegator} from "@symbiotic/contracts/delegator/NetworkRestakeDelegator.sol"; -// import {FullRestakeDelegator} from "@symbiotic/contracts/delegator/FullRestakeDelegator.sol"; -// import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; -// import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; - -// import {Token} from "@symbiotic-test/mocks/Token.sol"; -// import {VaultConfigurator, IVaultConfigurator} from "@symbiotic/contracts/VaultConfigurator.sol"; -// import {IVault} from "@symbiotic/interfaces/IVaultConfigurator.sol"; -// import {INetworkRestakeDelegator} from "@symbiotic/interfaces/delegator/INetworkRestakeDelegator.sol"; -// import {IFullRestakeDelegator, IBaseDelegator} from "@symbiotic/interfaces/delegator/IFullRestakeDelegator.sol"; -// import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; - -// import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -// import {IVaultStorage} from "@symbiotic/interfaces/vault/IVaultStorage.sol"; -// import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; -// import {IBaseSlasher} from "@symbiotic/interfaces/slasher/IBaseSlasher.sol"; -// import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - -// contract SimpleMiddlewareTest is Test { -// using Subnetwork for address; - -// address owner; -// address alice; -// uint256 alicePrivateKey; -// address bob; -// uint256 bobPrivateKey; - -// SimpleMiddleware simpleMiddleware; - -// VaultFactory vaultFactory; -// DelegatorFactory delegatorFactory; -// SlasherFactory slasherFactory; -// NetworkRegistry networkRegistry; -// OperatorRegistry operatorRegistry; -// MetadataService operatorMetadataService; -// MetadataService networkMetadataService; -// NetworkMiddlewareService networkMiddlewareService; -// OptInService networkVaultOptInService; -// OptInService operatorVaultOptInService; -// OptInService operatorNetworkOptInService; - -// Token collateral; -// VaultConfigurator vaultConfigurator; - -// function setUp() public { -// owner = address(this); -// (alice, alicePrivateKey) = makeAddrAndKey("alice"); -// (bob, bobPrivateKey) = makeAddrAndKey("bob"); - -// vaultFactory = new VaultFactory(owner); -// delegatorFactory = new DelegatorFactory(owner); -// slasherFactory = new SlasherFactory(owner); -// networkRegistry = new NetworkRegistry(); -// operatorRegistry = new OperatorRegistry(); -// operatorMetadataService = new MetadataService(address(operatorRegistry)); -// networkMetadataService = new MetadataService(address(networkRegistry)); -// networkMiddlewareService = new NetworkMiddlewareService(address(networkRegistry)); -// operatorVaultOptInService = new OptInService(address(operatorRegistry), address(vaultFactory)); -// operatorNetworkOptInService = new OptInService(address(operatorRegistry), address(networkRegistry)); - -// address vaultImpl = -// address(new Vault(address(delegatorFactory), address(slasherFactory), address(vaultFactory))); -// vaultFactory.whitelist(vaultImpl); - -// address networkRestakeDelegatorImpl = address( -// new NetworkRestakeDelegator( -// address(networkRegistry), -// address(vaultFactory), -// address(operatorVaultOptInService), -// address(operatorNetworkOptInService), -// address(delegatorFactory), -// delegatorFactory.totalTypes() -// ) -// ); -// delegatorFactory.whitelist(networkRestakeDelegatorImpl); - -// address fullRestakeDelegatorImpl = address( -// new FullRestakeDelegator( -// address(networkRegistry), -// address(vaultFactory), -// address(operatorVaultOptInService), -// address(operatorNetworkOptInService), -// address(delegatorFactory), -// delegatorFactory.totalTypes() -// ) -// ); -// delegatorFactory.whitelist(fullRestakeDelegatorImpl); - -// address slasherImpl = address( -// new Slasher( -// address(vaultFactory), -// address(networkMiddlewareService), -// address(slasherFactory), -// slasherFactory.totalTypes() -// ) -// ); -// slasherFactory.whitelist(slasherImpl); - -// address vetoSlasherImpl = address( -// new VetoSlasher( -// address(vaultFactory), -// address(networkMiddlewareService), -// address(networkRegistry), -// address(slasherFactory), -// slasherFactory.totalTypes() -// ) -// ); -// slasherFactory.whitelist(vetoSlasherImpl); - -// vaultConfigurator = -// new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); - -// collateral = new Token("Token"); -// } - -// struct Data { -// uint256[3] deposits; -// uint256[3][2] networkLimits; -// uint256[3][3][2] operatorLimits; -// } - -// function test_All(Data memory data) public { -// uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; -// blockTimestamp = blockTimestamp + 1_720_700_948; -// vm.warp(blockTimestamp); - -// address network = address(11_111); -// simpleMiddleware = new SimpleMiddleware( -// network, address(operatorRegistry), address(vaultFactory), address(vaultFactory), alice, 1 days, 3 days -// ); - -// _registerNetwork(network, address(simpleMiddleware)); - -// uint256 subnetworksN = 2; -// vm.startPrank(alice); -// simpleMiddleware.setSubnetworks(subnetworksN); -// vm.stopPrank(); - -// uint256 vaultsN = 3; -// Vault[] memory vaults = new Vault[](vaultsN); -// address[] memory _vaults = new address[](vaultsN); -// for (uint256 i; i < vaultsN; ++i) { -// (Vault vault,,) = _getVaultAndDelegatorAndSlasher(7 days, 1 days); -// vaults[i] = vault; -// _vaults[i] = address(vault); - -// vm.startPrank(alice); -// simpleMiddleware.registerVault(address(vault)); -// vm.stopPrank(); - -// data.deposits[i] = bound(data.deposits[i], 1, 100 ether); -// _deposit(alice, vault, data.deposits[i]); - -// for (uint96 j; j < subnetworksN; ++j) { -// data.networkLimits[j][i] = bound(data.networkLimits[j][i], 1, type(uint256).max); -// _setMaxNetworkLimit(network, FullRestakeDelegator(vault.delegator()), j, data.networkLimits[j][i]); -// _setNetworkLimit( -// alice, FullRestakeDelegator(vault.delegator()), network.subnetwork(j), data.networkLimits[j][i] -// ); -// } -// } - -// simpleMiddleware.enableSharedVaults(_vaults); - -// uint256 operatorsN = 3; -// address[] memory operators = new address[](operatorsN); -// for (uint256 i; i < operatorsN; ++i) { -// address operator = address(uint160(111 + i)); -// operators[i] = operator; - -// _registerOperator(operator); - -// _optInOperatorNetwork(operator, network); - -// vm.startPrank(alice); -// simpleMiddleware.registerOperator(operator, bytes32(uint256(uint160(operator)))); -// vm.stopPrank(); - -// for (uint256 j; j < vaultsN; ++j) { -// Vault vault = vaults[j]; - -// _optInOperatorVault(operator, vault); -// } - -// for (uint96 j; j < subnetworksN; ++j) { -// for (uint256 k; k < vaultsN; ++k) { -// Vault vault = vaults[k]; - -// data.operatorLimits[j][k][i] = bound(data.operatorLimits[j][k][i], 1, type(uint256).max); -// _setOperatorNetworkLimit( -// alice, -// FullRestakeDelegator(vault.delegator()), -// network.subnetwork(j), -// operator, -// data.operatorLimits[j][k][i] -// ); -// } -// } -// } - -// blockTimestamp = blockTimestamp + 1; -// vm.warp(blockTimestamp); - -// for (uint256 i; i < operatorsN; ++i) { -// address operator = operators[i]; - -// uint256 operatorStake; -// for (uint256 j; j < vaultsN; ++j) { -// Vault vault = Vault(vaults[j]); - -// uint256 vaultStake; -// for (uint96 k; k < subnetworksN; ++k) { -// bytes32 subnetwork = network.subnetwork(k); - -// vaultStake += -// Math.min(Math.min(data.operatorLimits[k][j][i], data.networkLimits[k][j]), data.deposits[j]); -// } - -// operatorStake += Math.min(vaultStake, data.deposits[j]); -// } - -// assertEq(operatorStake, simpleMiddleware.getOperatorStake(operator, 0)); -// } -// } - -// function _getVaultAndDelegatorAndSlasher(uint48 epochDuration, uint48 vetoDuration) -// internal -// returns (Vault, FullRestakeDelegator, VetoSlasher) -// { -// address[] memory networkLimitSetRoleHolders = new address[](1); -// networkLimitSetRoleHolders[0] = alice; -// address[] memory operatorNetworkLimitSetRoleHolders = new address[](1); -// operatorNetworkLimitSetRoleHolders[0] = alice; -// (address vault_, address delegator_, address slasher_) = vaultConfigurator.create( -// IVaultConfigurator.InitParams({ -// version: vaultFactory.lastVersion(), -// owner: alice, -// vaultParams: IVault.InitParams({ -// collateral: address(collateral), -// delegator: address(0), -// slasher: address(0), -// burner: address(0xdEaD), -// epochDuration: epochDuration, -// depositWhitelist: false, -// defaultAdminRoleHolder: alice, -// depositWhitelistSetRoleHolder: alice, -// depositorWhitelistRoleHolder: alice -// }), -// delegatorIndex: 1, -// delegatorParams: abi.encode( -// IFullRestakeDelegator.InitParams({ -// baseParams: IBaseDelegator.BaseParams({ -// defaultAdminRoleHolder: alice, -// hook: address(0), -// hookSetRoleHolder: alice -// }), -// networkLimitSetRoleHolders: networkLimitSetRoleHolders, -// operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders -// }) -// ), -// withSlasher: true, -// slasherIndex: 1, -// slasherParams: abi.encode(IVetoSlasher.InitParams({vetoDuration: vetoDuration, resolverSetEpochsDelay: 3})) -// }) -// ); - -// return (Vault(vault_), FullRestakeDelegator(delegator_), VetoSlasher(slasher_)); -// } - -// function _deposit(address user, Vault vault, uint256 amount) -// internal -// returns (uint256 depositedAmount, uint256 mintedShares) -// { -// collateral.transfer(user, amount); -// vm.startPrank(user); -// collateral.approve(address(vault), amount); -// (depositedAmount, mintedShares) = vault.deposit(user, amount); -// vm.stopPrank(); -// } - -// function _setNetworkLimit(address user, FullRestakeDelegator delegator, bytes32 subnetwork, uint256 amount) -// internal -// { -// vm.startPrank(user); -// delegator.setNetworkLimit(subnetwork, amount); -// vm.stopPrank(); -// } - -// function _setOperatorNetworkLimit( -// address user, -// FullRestakeDelegator delegator, -// bytes32 subnetwork, -// address operator, -// uint256 amount -// ) internal { -// vm.startPrank(user); -// delegator.setOperatorNetworkLimit(subnetwork, operator, amount); -// vm.stopPrank(); -// } - -// function _setMaxNetworkLimit(address user, FullRestakeDelegator delegator, uint96 identifier, uint256 amount) -// internal -// { -// vm.startPrank(user); -// delegator.setMaxNetworkLimit(identifier, amount); -// vm.stopPrank(); -// } - -// function _registerOperator(address user) internal { -// vm.startPrank(user); -// operatorRegistry.registerOperator(); -// vm.stopPrank(); -// } - -// function _registerNetwork(address user, address middleware) internal { -// vm.startPrank(user); -// networkRegistry.registerNetwork(); -// networkMiddlewareService.setMiddleware(middleware); -// vm.stopPrank(); -// } - -// function _optInOperatorVault(address user, Vault vault) internal { -// vm.startPrank(user); -// operatorVaultOptInService.optIn(address(vault)); -// vm.stopPrank(); -// } - -// function _optOutOperatorVault(address user, Vault vault) internal { -// vm.startPrank(user); -// operatorVaultOptInService.optOut(address(vault)); -// vm.stopPrank(); -// } - -// function _optInOperatorNetwork(address user, address network) internal { -// vm.startPrank(user); -// operatorNetworkOptInService.optIn(network); -// vm.stopPrank(); -// } - -// function _optOutOperatorNetwork(address user, address network) internal { -// vm.startPrank(user); -// operatorNetworkOptInService.optOut(network); -// vm.stopPrank(); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; + +import {SimpleMiddleware} from "../src/examples/simple-network/SimpleMiddleware.sol"; +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {BaseMiddleware} from "../src/BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "../src/libraries/PauseableEnumerableSet.sol"; +import {KeyManager} from "../src/KeyManager.sol"; +import {OperatorManager} from "../src/OperatorManager.sol"; +import {VaultManager} from "../src/VaultManager.sol"; +import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; +import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; + +contract SimpleMiddlewareTest is POCBaseTest { + using Subnetwork for bytes32; + using Subnetwork for address; + using Math for uint256; + + address network = address(0x123); + + SimpleMiddleware internal middleware; + + uint48 internal epochDuration = 600; // 10 minutes + uint48 internal slashingWindow = 1200; // 20 minutes + + address internal operator1; + address internal operator2; + address internal operator3; + + bytes32 internal key1 = keccak256("key1"); + bytes32 internal key2 = keccak256("key2"); + bytes32 internal key3 = keccak256("key3"); + + uint96 internal subnetwork1 = 0; + uint96 internal subnetwork2 = 1; + + function setUp() public override { + vm.warp(1729690309); + + super.setUp(); + + vm.prank(network); + networkRegistry.registerNetwork(); + + // Set operators + operator1 = alice; + operator2 = bob; + operator3 = address(0x3); // A third operator + + // Register operator1 + vm.startPrank(operator1); + operatorRegistry.registerOperator(); + operatorNetworkOptInService.optIn(address(network)); + vm.stopPrank(); + + // Register operator2 + vm.startPrank(operator2); + operatorRegistry.registerOperator(); + operatorNetworkOptInService.optIn(address(network)); + vm.stopPrank(); + + // Register operator3 + vm.startPrank(operator3); + operatorRegistry.registerOperator(); + operatorNetworkOptInService.optIn(address(network)); + vm.stopPrank(); + + // Opt-in operators to the vault + _optInOperatorVault(vault1, operator1); + _optInOperatorVault(vault1, operator2); + _optInOperatorVault(vault2, operator3); + _optInOperatorVault(vault2, operator1); + + // Set network limit and operator shares in the delegator + _setMaxNetworkLimit(address(delegator1), network, subnetwork1, type(uint256).max); + _setMaxNetworkLimit(address(delegator2), network, subnetwork1, type(uint256).max); + + _deposit(vault1, alice, 550 ether); + _deposit(vault2, alice, 500 ether); + + _setNetworkLimitNetwork(delegator1, alice, address(network), 550 ether); + _setNetworkLimitFull(delegator2, alice, address(network), 450 ether); + + _setOperatorNetworkShares(delegator1, alice, address(network), operator1, 250 ether); + _setOperatorNetworkShares(delegator1, alice, address(network), operator2, 300 ether); + _setOperatorNetworkLimit(delegator2, alice, address(network), operator1, 250 ether); + _setOperatorNetworkLimit(delegator2, alice, address(network), operator3, 200 ether); + + // Initialize middleware contract + middleware = new SimpleMiddleware( + address(network), + address(operatorRegistry), + address(vaultFactory), + address(operatorNetworkOptInService), + owner, + epochDuration, + slashingWindow + ); + + // Register network middleware + vm.prank(network); + networkMiddlewareService.setMiddleware(address(middleware)); + + // Register operators in the middleware + vm.startPrank(owner); + middleware.registerOperator(operator1); + middleware.registerOperator(operator2); + middleware.registerOperator(operator3); + + // Update keys for operators + middleware.updateKey(operator1, key1); + middleware.updateKey(operator2, key2); + middleware.updateKey(operator3, key3); + + // Register subnetworks + middleware.registerSubnetwork(subnetwork2); + + // Register vaults + middleware.registerSharedVault(address(vault1)); + middleware.registerSharedVault(address(vault2)); + + vm.stopPrank(); + + skipEpoch(); + } + + function testUpdateKeys() public { + // Update operator1's key + bytes32 newKey1 = keccak256("newKey1"); + vm.prank(owner); + middleware.updateKey(operator1, newKey1); + + skipEpoch(); + + // Verify that the key is updated + bytes32 currentKey1 = middleware.operatorKey(operator1); + assertEq(currentKey1, newKey1, "Operator1's key was not updated correctly"); + } + + function testPauseUnpauseOperator() public { + // Pause operator2 + vm.prank(owner); + middleware.pauseOperator(operator2); + skipEpoch(); + + // Verify operator2 is paused + address[] memory activeOperators = middleware.activeOperators(); + bool foundOperator2 = false; + for (uint256 i = 0; i < activeOperators.length; i++) { + if (activeOperators[i] == operator2) { + foundOperator2 = true; + } + } + assertFalse(foundOperator2, "Operator2 should be paused"); + + // Unpause operator2 + vm.prank(owner); + skipImmutablePeriod(); + middleware.unpauseOperator(operator2); + skipEpoch(); + + // Verify operator2 is active again + activeOperators = middleware.activeOperators(); + foundOperator2 = false; + for (uint256 i = 0; i < activeOperators.length; i++) { + if (activeOperators[i] == operator2) { + foundOperator2 = true; + break; + } + } + assertTrue(foundOperator2, "Operator2 should be active after unpausing"); + } + + function testPauseUnpauseVault() public { + // Pause the vault + vm.prank(owner); + middleware.pauseSharedVault(address(vault1)); + skipEpoch(); + + // Verify the vault is paused + address[] memory vaults = middleware.activeVaults(operator1); + bool foundVault = false; + for (uint256 i = 0; i < vaults.length; i++) { + if (vaults[i] == address(vault1)) { + foundVault = true; + break; + } + } + assertFalse(foundVault, "Vault should be paused"); + + // Unpause the vault + vm.prank(owner); + skipImmutablePeriod(); + middleware.unpauseSharedVault(address(vault1)); + skipEpoch(); + + // Verify the vault is active again + vaults = middleware.activeVaults(operator1); + foundVault = false; + for (uint256 i = 0; i < vaults.length; i++) { + if (vaults[i] == address(vault1)) { + foundVault = true; + break; + } + } + assertTrue(foundVault, "Vault should be active after unpausing"); + } + + function testPauseUnpauseSubnetwork() public { + // Pause subnetwork2 + vm.prank(owner); + middleware.pauseSubnetwork(subnetwork2); + skipEpoch(); + + // Verify subnetwork2 is paused + uint160[] memory activeSubnetworks = middleware.activeSubnetworks(); + bool foundSubnetwork2 = false; + for (uint256 i = 0; i < activeSubnetworks.length; i++) { + if (activeSubnetworks[i] == uint160(subnetwork2)) { + foundSubnetwork2 = true; + break; + } + } + assertFalse(foundSubnetwork2, "Subnetwork2 should be paused"); + + // Unpause subnetwork2 + vm.prank(owner); + skipImmutablePeriod(); + middleware.unpauseSubnetwork(subnetwork2); + skipEpoch(); + + // Verify subnetwork2 is active again + activeSubnetworks = middleware.activeSubnetworks(); + foundSubnetwork2 = false; + for (uint256 i = 0; i < activeSubnetworks.length; i++) { + if (activeSubnetworks[i] == uint160(subnetwork2)) { + foundSubnetwork2 = true; + break; + } + } + assertTrue(foundSubnetwork2, "Subnetwork2 should be active after unpausing"); + } + + function testSlashOperator() public { + // Prepare hints (empty in this context) + uint256 vaultsLen = middleware.activeVaults(operator1).length; + bytes[][] memory stakeHints = new bytes[][](vaultsLen); + for (uint256 i; i < vaultsLen; i++) { + stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); + for (uint256 j; j < stakeHints[i].length; j++) { + stakeHints[i][j] = ""; + } + } + + bytes[] memory slashHints = new bytes[](stakeHints.length); + + skipEpoch(); + uint48 epoch = middleware.getCurrentEpoch(); + uint256 amount = 100 ether; + + // Perform slash on operator1 + vm.prank(owner); + SimpleMiddleware.SlashResponse[] memory responses = + middleware.slash(epoch, key1, amount, stakeHints, slashHints); + + // Check that the slashing occurred + assertEq(responses.length, 2, "Should have one slash response"); + assertEq(responses[0].vault, address(vault1), "Incorrect vault in slash response"); + assertEq(responses[0].slasherType, slasher1.TYPE(), "Incorrect slasher type"); + assertEq(responses[0].response, amount / 2, "Incorrect slashed amount"); + assertEq(responses[1].vault, address(vault2), "Incorrect vault in slash response"); + assertEq(responses[1].slasherType, slasher2.TYPE(), "Incorrect slasher type"); + assertEq(responses[1].response, amount / 2, "Incorrect slashed amount"); + + // Verify that the operator's stake has decreased + uint256 remainingStake = + delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); + assertEq(remainingStake, 227272727272727272727, "Operator1 stake not reduced correctly"); + } + + function testUnregisterOperator() public { + // Unregister operator3 + vm.startPrank(owner); + middleware.pauseOperator(operator3); + skipEpoch(); + skipImmutablePeriod(); + middleware.unregisterOperator(operator3); + vm.stopPrank(); + skipEpoch(); + + // Verify operator3 is unregistered + address[] memory activeOperators = middleware.activeOperators(); + bool foundOperator3 = false; + for (uint256 i = 0; i < activeOperators.length; i++) { + if (activeOperators[i] == operator3) { + foundOperator3 = true; + break; + } + } + assertFalse(foundOperator3, "Operator3 should be unregistered"); + } + + function testUnregisterSubnetwork() public { + // Unregister subnetwork1 + vm.startPrank(owner); + middleware.pauseSubnetwork(subnetwork1); + skipEpoch(); + skipImmutablePeriod(); + middleware.unregisterSubnetwork(subnetwork1); + vm.stopPrank(); + skipEpoch(); + + // Verify subnetwork1 is unregistered + uint160[] memory activeSubnetworks = middleware.activeSubnetworks(); + bool foundSubnetwork1 = false; + for (uint256 i = 0; i < activeSubnetworks.length; i++) { + if (activeSubnetworks[i] == uint160(subnetwork1)) { + foundSubnetwork1 = true; + break; + } + } + assertFalse(foundSubnetwork1, "Subnetwork1 should be unregistered"); + } + + function testUnregisterVault() public { + // Unregister the vault + vm.startPrank(owner); + middleware.pauseSharedVault(address(vault1)); + skipEpoch(); + skipImmutablePeriod(); + middleware.unregisterSharedVault(address(vault1)); + vm.stopPrank(); + skipEpoch(); + + // Verify the vault is unregistered + address[] memory vaults = middleware.activeVaults(operator1); + bool foundVault = false; + for (uint256 i = 0; i < vaults.length; i++) { + if (vaults[i] == address(vault1)) { + foundVault = true; + break; + } + } + assertFalse(foundVault, "Vault should be unregistered"); + } + + function testValidatorSetWithMultipleSubnetworks() public { + skipEpoch(); + // Get validator set + SimpleMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); + + // Expected validator set length is 3 + assertEq(validatorSet.length, 3, "Validator set length should be 3"); + + // Verify each validator's power + for (uint256 i = 0; i < validatorSet.length; i++) { + SimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + if (validator.key == key1) { + assertEq(validator.power, 500 ether, "Operator1 power mismatch"); + } else if (validator.key == key2) { + assertEq(validator.power, 300 ether, "Operator2 power mismatch"); + } else if (validator.key == key3) { + assertEq(validator.power, 200 ether, "Operator3 power mismatch"); + } else { + assert(false); + } + } + } + + function testOperatorStakeAfterSlash() public { + // Prepare hints + uint256 vaultsLen = middleware.activeVaults(operator1).length; + bytes[][] memory stakeHints = new bytes[][](vaultsLen); + for (uint256 i; i < vaultsLen; i++) { + stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); + for (uint256 j; j < stakeHints[i].length; j++) { + stakeHints[i][j] = ""; + } + } + + bytes[] memory slashHints = new bytes[](stakeHints.length); + slashHints[0] = ""; + + uint48 epoch = middleware.getCurrentEpoch(); + uint256 amount = 100 ether; + + // Perform a slash on operator1 + vm.prank(owner); + middleware.slash(epoch, key1, amount, stakeHints, slashHints); + + // Verify operator1's stake is reduced + uint256 remainingStake = + delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); + assertEq(remainingStake, 227272727272727272727, "Operator1 stake not reduced correctly"); + + // Verify total stake is updated + skipEpoch(); + uint256 totalStake = middleware.getTotalStake(); + uint256 expectedTotalStake = 950 ether - 1; // 1000 ether - 100 / 2 ether + assertEq(totalStake, expectedTotalStake, "Total stake not updated correctly"); + } + + function testRevertOnUnregisteredOperator() public { + // Attempt to register an operator not in the registry + address unregisteredOperator = address(0x4); + vm.expectRevert(OperatorManager.NotOperator.selector); + vm.prank(owner); + middleware.registerOperator(unregisteredOperator); + } + + function testModifyStake() public { + // Increase operator1's stake + _deposit(vault1, operator1, 50 ether); + + _setNetworkLimitNetwork(delegator1, operator1, address(network), 600 ether); + _setOperatorNetworkShares(delegator1, alice, address(network), operator1, 600 ether); + + // Verify the stake is updated + uint256 newStake = delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); + assertEq(newStake, 400 ether, "Operator1's stake not updated correctly"); + + skipEpoch(); + // Verify total stake is updated + uint256 totalStake = middleware.getTotalStake(); + uint256 expectedTotalStake = 1050 ether; // Previous total + 50 ether + assertEq(totalStake, expectedTotalStake, "Total stake not updated correctly"); + } + + function skipEpoch() private { + vm.warp(block.timestamp + epochDuration); + } + + function skipImmutablePeriod() private { + vm.warp(block.timestamp + slashingWindow); + } +} From e7c382b5d6e575da5a18381de717ee012c74a5c7 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 13:07:03 +0400 Subject: [PATCH 010/115] chore: Readme and fixes --- README.md | 231 ++++++++++++++++++++++++++++++++-------- src/OperatorManager.sol | 2 +- src/VaultManager.sol | 2 +- 3 files changed, 191 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 9265b45..f0df12b 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,213 @@ -## Foundry +# Middleware SDK + +The Symbiotic Middleware SDK is a collection of basic contracts and libraries that handle common tasks such as operator management, key handling, vault interactions, subnetwork operations, and slashing mechanisms. It enables developers to easily create network middleware for the Symbiotic platform. + +## Table of Contents + +- [Features](#features) +- [Architecture](#architecture) +- [Usage](#usage) + - [Libraries](#libraries) + - [PauseableEnumerableSet Library](#pauseableenumerableset-library) + - [Contracts](#contracts) + - [BaseMiddleware Contract](#basemiddleware-contract) + - [OperatorManager Contract](#operatormanager-contract) + - [VaultManager Contract](#vaultmanager-contract) + - [KeyManager Contract](#keymanager-contract) + - [BLSKeyManager Contract](#blskeymanager-contract) + - [Examples](#examples) + - [SimpleMiddleware Example](#simplemiddleware-example) + - [SqrtTaskMiddleware Example](#sqrttaskmiddleware-example) +- [License](#license) + +## Features + +- **Operator Management**: Register, pause, unpause, and manage operators within the network. +- **Vault Management**: Interact with vaults for staking, power calculation, and slashing mechanisms. +- **Key Management**: Handle operator keys (both standard and BLS keys), including updates and activity checks. +- **Subnetwork Support**: Manage multiple subnetworks within the main network, including registration and pausing. +- **Epoch Management**: Utilities for handling epochs, including start times and durations. +- **Slashing Mechanisms**: Implement instant and veto-based slashing for misbehaving operators. +- **Example Implementations**: Includes example contracts demonstrating how to extend and use the SDK. + +## Architecture + +The SDK is organized into the following components: + +- **Libraries**: Reusable code segments like `PauseableEnumerableSet` for managing enumerable sets with pause functionality. +- **SDK Contracts**: Core contracts such as `BaseMiddleware`, `OperatorManager`, `VaultManager`, and key managers that provide essential middleware functionalities. +- **Examples**: Sample implementations like `SimpleMiddleware` and `SqrtTaskMiddleware` to illustrate how to build upon the SDK. -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +## Usage -Foundry consists of: +### Libraries -- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). -- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. -- **Chisel**: Fast, utilitarian, and verbose solidity REPL. +#### PauseableEnumerableSet Library -## Documentation +The `PauseableEnumerableSet` library extends the functionality of enumerable sets by adding pause and unpause capabilities to individual elements within the set. -https://book.getfoundry.sh/ +**Features:** -## Usage +- Manage sets of `address` or `uint160` values. +- Pause and unpause individual elements. +- Track enabled and disabled epochs for each element. +- Prevent operations on paused elements. -### Build +### SDK Contracts -```shell -$ forge build -``` +#### BaseMiddleware Contract -### Test +The `BaseMiddleware` contract is an abstract base contract that provides foundational middleware functionalities, including epoch management, subnetwork handling, and immutable period configurations. -```shell -$ forge test -``` +**Key Features:** -### Format +- **Epoch Management**: Calculate current epoch, epoch start times, and manage epoch durations. +- **Subnetwork Management**: Register, pause, unpause, and unregister subnetworks. +- **Immutable Epochs**: Enforce immutable periods before certain actions can be performed. -```shell -$ forge fmt -``` +**Key Functions:** -### Gas Snapshots +- `getCurrentEpoch()`: Returns the current epoch based on the timestamp. +- `getEpochStart(uint48 epoch)`: Returns the start timestamp of a given epoch. +- `registerSubnetwork(uint96 subnetwork)`: Registers a new subnetwork. +- `pauseSubnetwork(uint96 subnetwork)`: Pauses a subnetwork. +- `unpauseSubnetwork(uint96 subnetwork)`: Unpauses a subnetwork after the immutable period. +- `unregisterSubnetwork(uint96 subnetwork)`: Unregisters a subnetwork. -```shell -$ forge snapshot -``` +#### OperatorManager Contract -### Anvil +The `OperatorManager` contract manages operators within the network. It allows for registering, pausing, unpausing, and unregistering operators. -```shell -$ anvil -``` +**Key Features:** -### Deploy +- **Operator Registration**: Register new operators who are part of the network. +- **Operator State Management**: Pause and unpause operators. +- **Operator Activity Checks**: Retrieve active operators for the current epoch. -```shell -$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key -``` +**Key Functions:** + +- `registerOperator(address operator)`: Registers a new operator. Requires that the operator is a valid entity in the operator registry and has opted into the network. +- `pauseOperator(address operator)`: Pauses an operator, making them inactive. +- `unpauseOperator(address operator)`: Unpauses an operator after the immutable period has passed. +- `unregisterOperator(address operator)`: Unregisters an operator from the middleware. +- `activeOperators()`: Returns a list of active operators for the current epoch. +- `operatorsLength()`: Returns the total number of registered operators. + +#### VaultManager Contract + +The `VaultManager` contract handles interactions with vaults, including registering vaults, calculating operator stakes and power, and implementing slashing mechanisms. + +**Key Features:** -### Cast +- **Vault Registration**: Register shared and operator-specific vaults. +- **Stake and Power Calculation**: Calculate the stake and power of operators at specific epochs. +- **Slashing Mechanisms**: Implement slashing logic for misbehaving operators. -```shell -$ cast +**Key Functions:** + +- `registerSharedVault(address vault)`: Registers a shared vault accessible by all operators. +- `registerOperatorVault(address vault, address operator)`: Registers a vault specific to an operator. +- `pauseSharedVault(address vault)`: Pauses a shared vault. +- `unpauseSharedVault(address vault)`: Unpauses a shared vault after the immutable period. +- `pauseOperatorVault(address operator, address vault)`: Pauses an operator's vault. +- `unpauseOperatorVault(address operator, address vault)`: Unpauses an operator's vault after the immutable period. +- `getOperatorStake(uint48 epoch, address operator)`: Retrieves the total stake of an operator at a specific epoch. +- `getOperatorPower(uint48 epoch, address operator)`: Calculates the power of an operator based on their stake. This can be overridden to implement custom stake-to-power logic. + +**Implementing `stakeToPower`:** + +```solidity +function stakeToPower(address vault, uint256 stake) public view virtual override returns (uint256) { + // Custom logic to convert stake to power + return stake; // Simple 1:1 mapping in this example +} ``` -### Help +#### KeyManager Contract + +The `KeyManager` contract manages operator keys, including updating keys, retrieving current and previous keys, and checking if keys were active at specific epochs. + +**Key Features:** + +- **Key Updates**: Update the keys associated with operators. +- **Key Retrieval**: Get the current or previous key of an operator. +- **Key Activity Checks**: Determine if a key was active during a particular epoch. + +**Key Functions:** + +- `updateKey(address operator, bytes32 key)`: Updates the key associated with an operator. Throws an error if the key is already in use by another operator. +- `operatorKey(address operator)`: Retrieves the current key of an operator. If the key was updated in the current epoch, it returns the previous key. +- `operatorByKey(bytes32 key)`: Returns the operator associated with a specific key. +- `keyWasActiveAt(uint48 epoch, bytes32 key)`: Checks if a key was active during a specific epoch. + +#### BLSKeyManager Contract + +The `BLSKeyManager` is similar to `KeyManager` but specifically handles BLS (Boneh-Lynn-Shacham) keys, which are often used in threshold signature schemes. + +**Key Features:** -```shell -$ forge --help -$ anvil --help -$ cast --help +- **BLS Key Management**: Update and retrieve BLS keys for operators. +- **Key Activity Checks**: Check if a BLS key was active during a specific epoch. + +**Key Functions:** + +- `updateBLSKey(address operator, bytes memory key)`: Updates the BLS key associated with an operator. Throws an error if the key is already in use. +- `operatorBLSKey(address operator)`: Retrieves the current BLS key of an operator. If the key was updated in the current epoch, it returns the previous key. +- `operatorByBLSKey(bytes memory key)`: Returns the operator associated with a specific BLS key. +- `blsKeyWasActiveAt(uint48 epoch, bytes memory key)`: Checks if a BLS key was active during a specific epoch. + +### Examples + +#### SimpleMiddleware Example + +The `SimpleMiddleware` contract is an example implementation that demonstrates how to use the SDK to build a middleware contract that manages validators and handles slashing. + +**Features:** + +- **Validator Set Management**: Maintains a set of validators with their power and keys. +- **Slashing Mechanism**: Implements a `slash` function to penalize misbehaving operators. +- **Integration**: Utilizes `OperatorManager`, `VaultManager`, and `KeyManager` for comprehensive management. + +**Key Structures and Functions:** + +- **ValidatorData**: Struct containing `power` and `key` of a validator. +- `getTotalStake()`: Returns the total stake for active operators in the current epoch. +- `getValidatorSet()`: Retrieves the current validator set with their power and keys. +- `slash(...)`: Slashes a validator based on provided parameters. + +#### SqrtTaskMiddleware Example + +The `SqrtTaskMiddleware` contract is an advanced example that extends the SDK to create computational tasks requiring operators to compute square roots. It includes signature verification and custom slashing logic. + +**Features:** + +- **Task Creation and Completion**: Allows creating tasks and operators to submit computed answers. +- **Signature Verification**: Uses EIP712 standard for verifying operator signatures. +- **Custom Slashing**: Implements slashing if the operator provides an incorrect answer. + +**Key Structures and Functions:** + +- **Task**: Struct containing `captureTimestamp`, `value`, `operator`, and `completed` status. +- `createTask(uint256 value, address operator)`: Creates a new computational task. +- `completeTask(...)`: Operators submit answers to tasks with signature verification. +- `_slash(...)`: Internal function to handle slashing logic if the answer is incorrect. + +**Note:** + +In `SqrtTaskMiddleware`, subnetworks are not used, and attempts to register or manage subnetworks are disabled by overriding the functions and reverting. + +```solidity +function registerSubnetwork(uint96 subnetwork) public override { + revert(); +} + +// Similarly for other subnetwork functions... ``` + +## License + +This project is licensed under the [MIT License](LICENSE). + +--- + +For any questions, issues, or contributions, please open an issue or submit a pull request on the repository. \ No newline at end of file diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index 91b7575..bceaf8a 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -59,7 +59,7 @@ abstract contract OperatorManager is BaseMiddleware { revert OperatorNotOptedIn(); } - _operators.register(getNextEpoch(), operator); + _operators.register(getCurrentEpoch(), operator); } /* diff --git a/src/VaultManager.sol b/src/VaultManager.sol index 87d2f7a..fd715db 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -169,7 +169,7 @@ abstract contract VaultManager is BaseMiddleware { * @param vault The address of the vault to unregister. */ function unregisterSharedVault(address vault) public virtual onlyOwner { - _sharedVaults.unregister(getNextEpoch(), IMMUTABLE_EPOCHS, vault); + _sharedVaults.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); } /* From 685644833d60b1b5ecc15ba36bc6eda86ce68b98 Mon Sep 17 00:00:00 2001 From: Algys Date: Thu, 24 Oct 2024 15:23:10 +0400 Subject: [PATCH 011/115] add LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9138989 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Symbiotic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From a18076d510c1e08dc3b6a7696e52a7541d57ec67 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 15:33:10 +0400 Subject: [PATCH 012/115] fix: add opeartors vault check on slash --- src/VaultManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VaultManager.sol b/src/VaultManager.sol index fd715db..927f9e5 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -259,7 +259,7 @@ abstract contract VaultManager is BaseMiddleware { uint256 amount, bytes calldata hints ) internal returns (SlashResponse memory resp) { - if (!_sharedVaults.contains(vault)) { + if (!_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault))) { revert NotVault(); } From d98f473842a6c4d92c56ade884c2fb3a6c9b9f5a Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 15:35:49 +0400 Subject: [PATCH 013/115] chore: add notice epoch starts from 1 --- src/BaseMiddleware.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/BaseMiddleware.sol b/src/BaseMiddleware.sol index 8684962..b9e811c 100644 --- a/src/BaseMiddleware.sol +++ b/src/BaseMiddleware.sol @@ -27,7 +27,7 @@ abstract contract BaseMiddleware is Ownable { PauseableEnumerableSet.Uint160Set subnetworks; // Set of active subnetworks /* - * @notice Constructor for initializing the BaseMiddleware contract. + * @notice Constructor for initializing the BaseMiddleware contract. Epoch starts from 1. * @param owner The address of the contract owner. * @param network The address of the network. * @param epochDuration The duration of each epoch. @@ -61,7 +61,7 @@ abstract contract BaseMiddleware is Ownable { } /* - * @notice Returns the start timestamp of a given epoch. + * @notice Returns the start timestamp of a given epoch. Epoch starts from 1. * @param epoch The epoch number. * @return The start timestamp of the specified epoch. */ @@ -70,7 +70,7 @@ abstract contract BaseMiddleware is Ownable { } /* - * @notice Returns the epoch number corresponding to a given timestamp. + * @notice Returns the epoch number corresponding to a given timestamp. Epoch starts from 1. * @param timestamp The timestamp to convert to an epoch number. * @return The epoch number associated with the specified timestamp. */ @@ -79,7 +79,7 @@ abstract contract BaseMiddleware is Ownable { } /* - * @notice Returns the current epoch number based on the current timestamp. + * @notice Returns the current epoch number based on the current timestamp. Epoch starts from 1. * @return The current epoch number. */ function getCurrentEpoch() public view returns (uint48 epoch) { @@ -87,7 +87,7 @@ abstract contract BaseMiddleware is Ownable { } /* - * @notice Returns the start timestamp of the current epoch. + * @notice Returns the start timestamp of the current epoch. Epoch starts from 1. * @return The start timestamp of the current epoch. */ function getCurrentEpochStart() public view returns (uint48 timestamp) { From 7f3b5ac6449b21a3793c5eab49b7d1e404e16b3b Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 16:35:20 +0400 Subject: [PATCH 014/115] fix: no additional epoch between pause/unpause --- src/BLSKeyManager.sol | 8 +++++--- src/KeyManager.sol | 7 ++++--- src/VaultManager.sol | 2 +- src/libraries/PauseableEnumerableSet.sol | 12 ++++++------ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/BLSKeyManager.sol b/src/BLSKeyManager.sol index 161adf3..2cfef2a 100644 --- a/src/BLSKeyManager.sol +++ b/src/BLSKeyManager.sol @@ -26,12 +26,12 @@ abstract contract BLSKeyManager is BaseMiddleware { /* * @notice Returns the current BLS key for a given operator. - * If the key has changed in the current epoch, returns the previous key. + * If the key has changed in the previous epoch, returns the previous key. * @param operator The address of the operator. * @return The BLS key associated with the specified operator. */ function operatorBLSKey(address operator) public view returns (bytes memory) { - if (_blsKeyData[blsKeys[operator]].enabledEpoch == getCurrentEpoch()) { + if (_blsKeyData[blsKeys[operator]].enabledEpoch == getCurrentEpoch() - 1) { return prevBLSKeys[operator]; } @@ -56,12 +56,14 @@ abstract contract BLSKeyManager is BaseMiddleware { */ function updateBLSKey(address operator, bytes memory key) public virtual onlyOwner { uint48 epoch = getCurrentEpoch(); + uint48 nextEpoch = epoch + 1; if (_blsKeyData[key].getAddress() != address(0)) { revert DuplicateBLSKey(); } - if (keccak256(blsKeys[operator]) != ZERO_BYTES_HASH && _blsKeyData[blsKeys[operator]].enabledEpoch != epoch) { + if (keccak256(blsKeys[operator]) != ZERO_BYTES_HASH && _blsKeyData[blsKeys[operator]].enabledEpoch != nextEpoch) + { prevBLSKeys[operator] = blsKeys[operator]; } diff --git a/src/KeyManager.sol b/src/KeyManager.sol index d4fd380..0044d24 100644 --- a/src/KeyManager.sol +++ b/src/KeyManager.sol @@ -26,12 +26,12 @@ abstract contract KeyManager is BaseMiddleware { /* * @notice Returns the current key for a given operator. - * If the key has changed in the current epoch, returns the previous key. + * If the key has changed in the previous epoch, returns the previous key. * @param operator The address of the operator. * @return The key associated with the specified operator. */ function operatorKey(address operator) public view returns (bytes32) { - if (_keyData[keys[operator]].enabledEpoch == getCurrentEpoch()) { + if (_keyData[keys[operator]].enabledEpoch == getCurrentEpoch() + 1) { return prevKeys[operator]; } @@ -56,12 +56,13 @@ abstract contract KeyManager is BaseMiddleware { */ function updateKey(address operator, bytes32 key) public virtual onlyOwner { uint48 epoch = getCurrentEpoch(); + uint48 nextEpoch = epoch + 1; if (_keyData[key].getAddress() != address(0)) { revert DuplicateKey(); } - if (keys[operator] != ZERO_BYTES32 && _keyData[keys[operator]].enabledEpoch != epoch) { + if (keys[operator] != ZERO_BYTES32 && _keyData[keys[operator]].enabledEpoch != nextEpoch) { prevKeys[operator] = keys[operator]; } diff --git a/src/VaultManager.sol b/src/VaultManager.sol index 927f9e5..43f24fc 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -259,7 +259,7 @@ abstract contract VaultManager is BaseMiddleware { uint256 amount, bytes calldata hints ) internal returns (SlashResponse memory resp) { - if (!_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault))) { + if (!_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault)) { revert NotVault(); } diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 40c58f9..4bee186 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -276,7 +276,7 @@ library PauseableEnumerableSet { */ function set(Inner storage self, uint48 epoch, uint160 value) internal { self.value = value; - self.enabledEpoch = epoch; + self.enabledEpoch = epoch + 1; self.disabledEpoch = 0; } @@ -288,7 +288,7 @@ library PauseableEnumerableSet { */ function set(Inner storage self, uint48 epoch, address addr) internal { self.value = uint160(addr); - self.enabledEpoch = epoch; + self.enabledEpoch = epoch + 1; self.disabledEpoch = 0; } @@ -326,8 +326,8 @@ library PauseableEnumerableSet { * @return True if the value was active at the epoch, false otherwise. */ function wasActiveAt(Inner storage self, uint48 epoch) internal view returns (bool) { - return (self.enabledEpoch != 0 && self.enabledEpoch < epoch) - && (self.disabledEpoch == 0 || self.disabledEpoch >= epoch); + return (self.enabledEpoch != 0 && self.enabledEpoch <= epoch) + && (self.disabledEpoch == 0 || self.disabledEpoch > epoch); } /* @@ -337,7 +337,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The immutable period that must pass before unpausing. */ function validateUnpause(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { - if (self.disabledEpoch + immutableEpochs >= epoch) { + if (self.disabledEpoch + immutableEpochs > epoch) { revert ImmutablePeriodNotPassed(); } } @@ -349,7 +349,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The immutable period that must pass before unregistering. */ function validateUnregister(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { - if (self.disabledEpoch == 0 || self.disabledEpoch + immutableEpochs >= epoch) { + if (self.disabledEpoch == 0 || self.disabledEpoch + immutableEpochs > epoch) { revert ImmutablePeriodNotPassed(); } } From d63c92a079a1a62fd723ba46514beef9c26ca5be Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 16:56:11 +0400 Subject: [PATCH 015/115] fix: vault slash check and blskeymanager get key check --- src/BLSKeyManager.sol | 2 +- src/VaultManager.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BLSKeyManager.sol b/src/BLSKeyManager.sol index 2cfef2a..eed724b 100644 --- a/src/BLSKeyManager.sol +++ b/src/BLSKeyManager.sol @@ -31,7 +31,7 @@ abstract contract BLSKeyManager is BaseMiddleware { * @return The BLS key associated with the specified operator. */ function operatorBLSKey(address operator) public view returns (bytes memory) { - if (_blsKeyData[blsKeys[operator]].enabledEpoch == getCurrentEpoch() - 1) { + if (_blsKeyData[blsKeys[operator]].enabledEpoch == getCurrentEpoch() + 1) { return prevBLSKeys[operator]; } diff --git a/src/VaultManager.sol b/src/VaultManager.sol index 43f24fc..edd509d 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -259,7 +259,7 @@ abstract contract VaultManager is BaseMiddleware { uint256 amount, bytes calldata hints ) internal returns (SlashResponse memory resp) { - if (!_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault)) { + if (!(_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault))) { revert NotVault(); } From 326c24276caeb77963d3062ca8d51ae05d8ce084 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 17:11:29 +0400 Subject: [PATCH 016/115] fix was active at --- src/libraries/PauseableEnumerableSet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 4bee186..4056b18 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -327,7 +327,7 @@ library PauseableEnumerableSet { */ function wasActiveAt(Inner storage self, uint48 epoch) internal view returns (bool) { return (self.enabledEpoch != 0 && self.enabledEpoch <= epoch) - && (self.disabledEpoch == 0 || self.disabledEpoch > epoch); + && (self.disabledEpoch == 0 || self.disabledEpoch >= epoch); } /* From 7aa21a2359d47e229937f85558a57eae6040463e Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 17:28:52 +0400 Subject: [PATCH 017/115] fix: wasActiveAt bug and validateUnregister/Unpause --- src/libraries/PauseableEnumerableSet.sol | 6 +++--- test/SimpleMiddleware.t.sol | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 4056b18..03ca170 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -302,7 +302,7 @@ library PauseableEnumerableSet { revert AlreadyEnabled(); } - self.enabledEpoch = epoch; + self.enabledEpoch = epoch + 1; self.disabledEpoch = 0; } @@ -337,7 +337,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The immutable period that must pass before unpausing. */ function validateUnpause(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { - if (self.disabledEpoch + immutableEpochs > epoch) { + if (self.disabledEpoch + immutableEpochs >= epoch) { revert ImmutablePeriodNotPassed(); } } @@ -349,7 +349,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The immutable period that must pass before unregistering. */ function validateUnregister(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { - if (self.disabledEpoch == 0 || self.disabledEpoch + immutableEpochs > epoch) { + if (self.disabledEpoch == 0 || self.disabledEpoch + immutableEpochs >= epoch) { revert ImmutablePeriodNotPassed(); } } diff --git a/test/SimpleMiddleware.t.sol b/test/SimpleMiddleware.t.sol index cbd7324..79e7bfd 100644 --- a/test/SimpleMiddleware.t.sol +++ b/test/SimpleMiddleware.t.sol @@ -136,6 +136,7 @@ contract SimpleMiddlewareTest is POCBaseTest { bytes32 newKey1 = keccak256("newKey1"); vm.prank(owner); middleware.updateKey(operator1, newKey1); + assertEq(middleware.operatorKey(operator1), key1, "Operator1's key should not be updated yet"); skipEpoch(); @@ -351,8 +352,7 @@ contract SimpleMiddlewareTest is POCBaseTest { assertFalse(foundVault, "Vault should be unregistered"); } - function testValidatorSetWithMultipleSubnetworks() public { - skipEpoch(); + function testValidatorSet() public { // Get validator set SimpleMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); From b5488cdef6b0a57e4f6d49879e89e871af94f468 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 17:39:58 +0400 Subject: [PATCH 018/115] fix: unpause/unregister validation --- src/libraries/PauseableEnumerableSet.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 03ca170..7795c99 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -337,7 +337,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The immutable period that must pass before unpausing. */ function validateUnpause(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { - if (self.disabledEpoch + immutableEpochs >= epoch) { + if (self.disabledEpoch + immutableEpochs > epoch) { revert ImmutablePeriodNotPassed(); } } @@ -349,7 +349,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The immutable period that must pass before unregistering. */ function validateUnregister(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { - if (self.disabledEpoch == 0 || self.disabledEpoch + immutableEpochs >= epoch) { + if (self.disabledEpoch == 0 || self.disabledEpoch + immutableEpochs > epoch) { revert ImmutablePeriodNotPassed(); } } From 4af8fed3be15217a1f1bc1a3386abc746475c546 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 24 Oct 2024 17:45:15 +0400 Subject: [PATCH 019/115] chore: start epoch from 0 --- src/BaseMiddleware.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/BaseMiddleware.sol b/src/BaseMiddleware.sol index b9e811c..203041c 100644 --- a/src/BaseMiddleware.sol +++ b/src/BaseMiddleware.sol @@ -27,7 +27,7 @@ abstract contract BaseMiddleware is Ownable { PauseableEnumerableSet.Uint160Set subnetworks; // Set of active subnetworks /* - * @notice Constructor for initializing the BaseMiddleware contract. Epoch starts from 1. + * @notice Constructor for initializing the BaseMiddleware contract. * @param owner The address of the contract owner. * @param network The address of the network. * @param epochDuration The duration of each epoch. @@ -61,25 +61,25 @@ abstract contract BaseMiddleware is Ownable { } /* - * @notice Returns the start timestamp of a given epoch. Epoch starts from 1. + * @notice Returns the start timestamp of a given epoch. * @param epoch The epoch number. * @return The start timestamp of the specified epoch. */ function getEpochStart(uint48 epoch) public view returns (uint48 timestamp) { - return START_TIME + (epoch - 1) * EPOCH_DURATION; + return START_TIME + epoch * EPOCH_DURATION; } /* - * @notice Returns the epoch number corresponding to a given timestamp. Epoch starts from 1. + * @notice Returns the epoch number corresponding to a given timestamp. * @param timestamp The timestamp to convert to an epoch number. * @return The epoch number associated with the specified timestamp. */ function getEpochAt(uint48 timestamp) public view returns (uint48 epoch) { - return (timestamp - START_TIME) / EPOCH_DURATION + 1; + return (timestamp - START_TIME) / EPOCH_DURATION; } /* - * @notice Returns the current epoch number based on the current timestamp. Epoch starts from 1. + * @notice Returns the current epoch number based on the current timestamp. * @return The current epoch number. */ function getCurrentEpoch() public view returns (uint48 epoch) { @@ -87,11 +87,11 @@ abstract contract BaseMiddleware is Ownable { } /* - * @notice Returns the start timestamp of the current epoch. Epoch starts from 1. + * @notice Returns the start timestamp of the current epoch. * @return The start timestamp of the current epoch. */ function getCurrentEpochStart() public view returns (uint48 timestamp) { - return START_TIME + (getCurrentEpoch() - 1) * EPOCH_DURATION; + return START_TIME + getCurrentEpoch() * EPOCH_DURATION; } /* From 03dbdf2087593d211d64fc3d5cf587688fe26dd9 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 31 Oct 2024 18:00:02 +0400 Subject: [PATCH 020/115] fix: sdk fixes with tests --- src/BLSKeyManager.sol | 27 +++--- src/BaseMiddleware.sol | 16 ++-- src/KeyManager.sol | 26 +++--- src/OperatorManager.sol | 4 +- src/VaultManager.sol | 45 ++++++---- .../simple-network/SimpleMiddleware.sol | 14 ++-- src/libraries/PauseableEnumerableSet.sol | 84 ++++++++++--------- 7 files changed, 120 insertions(+), 96 deletions(-) diff --git a/src/BLSKeyManager.sol b/src/BLSKeyManager.sol index eed724b..5ef7058 100644 --- a/src/BLSKeyManager.sol +++ b/src/BLSKeyManager.sol @@ -8,11 +8,13 @@ abstract contract BLSKeyManager is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateBLSKey(); + error BLSKeyAlreadyEnabled(); bytes32 private constant ZERO_BYTES_HASH = keccak256(""); // Constant representing an empty hash mapping(address => bytes) public blsKeys; // Mapping from operator addresses to their BLS keys mapping(address => bytes) public prevBLSKeys; // Mapping from operator addresses to their previous BLS keys + mapping(address => uint32) public blsKeyUpdateEpoch; // Mapping from operator addresses to the epoch of the last BLS key update mapping(bytes => PauseableEnumerableSet.Inner) internal _blsKeyData; // Mapping from BLS keys to their associated data /* @@ -31,7 +33,7 @@ abstract contract BLSKeyManager is BaseMiddleware { * @return The BLS key associated with the specified operator. */ function operatorBLSKey(address operator) public view returns (bytes memory) { - if (_blsKeyData[blsKeys[operator]].enabledEpoch == getCurrentEpoch() + 1) { + if (blsKeyUpdateEpoch[operator] == getCurrentEpoch()) { return prevBLSKeys[operator]; } @@ -44,7 +46,7 @@ abstract contract BLSKeyManager is BaseMiddleware { * @param key The BLS key to check. * @return A boolean indicating whether the BLS key was active at the specified epoch. */ - function blsKeyWasActiveAt(uint48 epoch, bytes memory key) public view returns (bool) { + function blsKeyWasActiveAt(uint32 epoch, bytes memory key) public view returns (bool) { return _blsKeyData[key].wasActiveAt(epoch); } @@ -55,22 +57,25 @@ abstract contract BLSKeyManager is BaseMiddleware { * @param key The new BLS key to associate with the operator. */ function updateBLSKey(address operator, bytes memory key) public virtual onlyOwner { - uint48 epoch = getCurrentEpoch(); - uint48 nextEpoch = epoch + 1; + uint32 epoch = getCurrentEpoch(); - if (_blsKeyData[key].getAddress() != address(0)) { + if (keccak256(blsKeys[operator]) == keccak256(key)) { + revert BLSKeyAlreadyEnabled(); + } + + if (_blsKeyData[key].getAddress() != address(0) && _blsKeyData[key].getAddress() != operator) { revert DuplicateBLSKey(); } - if (keccak256(blsKeys[operator]) != ZERO_BYTES_HASH && _blsKeyData[blsKeys[operator]].enabledEpoch != nextEpoch) - { + if (keccak256(key) != ZERO_BYTES_HASH && _blsKeyData[key].getAddress() == address(0)) { + _blsKeyData[key].set(epoch, operator); + } + + if (blsKeyUpdateEpoch[operator] != epoch) { prevBLSKeys[operator] = blsKeys[operator]; + blsKeyUpdateEpoch[operator] = epoch; } blsKeys[operator] = key; - - if (keccak256(key) != ZERO_BYTES_HASH) { - _blsKeyData[key].set(epoch, operator); - } } } diff --git a/src/BaseMiddleware.sol b/src/BaseMiddleware.sol index 203041c..27b1989 100644 --- a/src/BaseMiddleware.sol +++ b/src/BaseMiddleware.sol @@ -15,7 +15,7 @@ abstract contract BaseMiddleware is Ownable { uint48 public immutable EPOCH_DURATION; // Duration of each epoch uint48 public immutable START_TIME; // Start time of the epoch uint48 public immutable SLASHING_WINDOW; // Duration of the slashing window - uint48 public immutable IMMUTABLE_EPOCHS; // Duration of the state immutability in epochs + uint32 public immutable IMMUTABLE_EPOCHS; // Duration of the state immutability in epochs address public immutable VAULT_REGISTRY; // Address of the vault registry address public immutable OPERATOR_REGISTRY; // Address of the operator registry address public immutable OPERATOR_NET_OPTIN; // Address of the operator network opt-in service @@ -52,7 +52,7 @@ abstract contract BaseMiddleware is Ownable { NETWORK = network; EPOCH_DURATION = epochDuration; SLASHING_WINDOW = slashingWindow; - IMMUTABLE_EPOCHS = (slashingWindow + epochDuration - 1) / epochDuration; + IMMUTABLE_EPOCHS = uint32((slashingWindow + epochDuration - 1) / epochDuration); VAULT_REGISTRY = vaultRegistry; OPERATOR_REGISTRY = operatorRegistry; OPERATOR_NET_OPTIN = operatorNetOptIn; @@ -65,7 +65,7 @@ abstract contract BaseMiddleware is Ownable { * @param epoch The epoch number. * @return The start timestamp of the specified epoch. */ - function getEpochStart(uint48 epoch) public view returns (uint48 timestamp) { + function getEpochStart(uint32 epoch) public view returns (uint48 timestamp) { return START_TIME + epoch * EPOCH_DURATION; } @@ -74,15 +74,15 @@ abstract contract BaseMiddleware is Ownable { * @param timestamp The timestamp to convert to an epoch number. * @return The epoch number associated with the specified timestamp. */ - function getEpochAt(uint48 timestamp) public view returns (uint48 epoch) { - return (timestamp - START_TIME) / EPOCH_DURATION; + function getEpochAt(uint48 timestamp) public view returns (uint32 epoch) { + return uint32((timestamp - START_TIME) / EPOCH_DURATION); } /* * @notice Returns the current epoch number based on the current timestamp. * @return The current epoch number. */ - function getCurrentEpoch() public view returns (uint48 epoch) { + function getCurrentEpoch() public view returns (uint32 epoch) { return getEpochAt(Time.timestamp()); } @@ -105,9 +105,9 @@ abstract contract BaseMiddleware is Ownable { /* * @notice Returns the subnetwork information at a specified position. * @param pos The index of the subnetwork. - * @return The subnetwork details including address, enabled epoch, and disabled epoch. + * @return The subnetwork details including address, enabled epoch, disabled epoch and enabled before disabled epoch. */ - function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint48, uint48) { + function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint32, uint32, uint32) { return subnetworks.at(pos); } diff --git a/src/KeyManager.sol b/src/KeyManager.sol index 0044d24..54e6bd0 100644 --- a/src/KeyManager.sol +++ b/src/KeyManager.sol @@ -8,11 +8,13 @@ abstract contract KeyManager is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateKey(); + error KeyAlreadyEnabled(); bytes32 private constant ZERO_BYTES32 = bytes32(0); mapping(address => bytes32) public keys; // Mapping from operator addresses to their current keys mapping(address => bytes32) public prevKeys; // Mapping from operator addresses to their previous keys + mapping(address => uint32) public keyUpdateEpoch; // Mapping from operator addresses to the epoch of the last key update mapping(bytes32 => PauseableEnumerableSet.Inner) internal _keyData; // Mapping from keys to their associated data /* @@ -31,7 +33,7 @@ abstract contract KeyManager is BaseMiddleware { * @return The key associated with the specified operator. */ function operatorKey(address operator) public view returns (bytes32) { - if (_keyData[keys[operator]].enabledEpoch == getCurrentEpoch() + 1) { + if (keyUpdateEpoch[operator] == getCurrentEpoch()) { return prevKeys[operator]; } @@ -44,7 +46,7 @@ abstract contract KeyManager is BaseMiddleware { * @param key The key to check. * @return A boolean indicating whether the key was active at the specified epoch. */ - function keyWasActiveAt(uint48 epoch, bytes32 key) public view returns (bool) { + function keyWasActiveAt(uint32 epoch, bytes32 key) public view returns (bool) { return _keyData[key].wasActiveAt(epoch); } @@ -55,21 +57,25 @@ abstract contract KeyManager is BaseMiddleware { * @param key The new key to associate with the operator. */ function updateKey(address operator, bytes32 key) public virtual onlyOwner { - uint48 epoch = getCurrentEpoch(); - uint48 nextEpoch = epoch + 1; + uint32 epoch = getCurrentEpoch(); - if (_keyData[key].getAddress() != address(0)) { + if (keys[operator] == key) { + revert KeyAlreadyEnabled(); + } + + if (_keyData[key].getAddress() != address(0) && _keyData[key].getAddress() != operator) { revert DuplicateKey(); } - if (keys[operator] != ZERO_BYTES32 && _keyData[keys[operator]].enabledEpoch != nextEpoch) { + if (key != ZERO_BYTES32 && _keyData[key].getAddress() == address(0)) { + _keyData[key].set(epoch, operator); + } + + if (keyUpdateEpoch[operator] != epoch) { prevKeys[operator] = keys[operator]; + keyUpdateEpoch[operator] = epoch; } keys[operator] = key; - - if (key != ZERO_BYTES32) { - _keyData[key].set(epoch, operator); - } } } diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index bceaf8a..ffb9c1e 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -32,9 +32,9 @@ abstract contract OperatorManager is BaseMiddleware { /* * @notice Returns the operator and their associated enabled and disabled times at a specific position. * @param pos The index position in the operators array. - * @return The address, enabled epoch, and disabled epoch of the operator. + * @return The address, enabled epoch, disabled epoch and enabled before disabled epoch of the operator. */ - function operatorWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { + function operatorWithTimesAt(uint256 pos) public view returns (address, uint32, uint32, uint32) { return _operators.at(pos); } diff --git a/src/VaultManager.sol b/src/VaultManager.sol index edd509d..dad124e 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -22,6 +22,7 @@ abstract contract VaultManager is BaseMiddleware { using Subnetwork for address; error NotVault(); + error NotOperatorVault(); error VaultNotInitialized(); error VaultNotRegistered(); error VaultAlreadyRegistred(); @@ -35,6 +36,7 @@ abstract contract VaultManager is BaseMiddleware { PauseableEnumerableSet.AddressSet internal _sharedVaults; mapping(address => PauseableEnumerableSet.AddressSet) internal _operatorVaults; + mapping(address => bool) public vaultExists; struct SlashResponse { address vault; @@ -54,9 +56,9 @@ abstract contract VaultManager is BaseMiddleware { /* * @notice Returns the address and epoch information of a shared vault at a specific position. * @param pos The index position in the shared vaults array. - * @return The address, enabled epoch, and disabled epoch of the vault. + * @return The address, enabled epoch, disabled epoch and enabled before disabled epoch of the vault. */ - function sharedVaultWithEpochsAt(uint256 pos) public view returns (address, uint48, uint48) { + function sharedVaultWithEpochsAt(uint256 pos) public view returns (address, uint32, uint32, uint32) { return _sharedVaults.at(pos); } @@ -73,9 +75,13 @@ abstract contract VaultManager is BaseMiddleware { * @notice Returns the address and epoch information of an operator vault at a specific position. * @param operator The address of the operator. * @param pos The index position in the operator vaults array. - * @return The address, enabled epoch, and disabled epoch of the vault. + * @return The address, enabled epoch, disabled epoch and enabled before disabled of the vault. */ - function operatorVaultWithEpochsAt(address operator, uint256 pos) public view returns (address, uint48, uint48) { + function operatorVaultWithEpochsAt(address operator, uint256 pos) + public + view + returns (address, uint32, uint32, uint32) + { return _operatorVaults[operator].at(pos); } @@ -95,7 +101,7 @@ abstract contract VaultManager is BaseMiddleware { * @return An array of addresses representing the active vaults. */ function activeVaults(address operator) public view returns (address[] memory) { - uint48 epoch = getCurrentEpoch(); + uint32 epoch = getCurrentEpoch(); address[] memory activeSharedVaults = _sharedVaults.getActive(epoch); address[] memory activeOperatorVaults = _operatorVaults[operator].getActive(epoch); @@ -117,16 +123,18 @@ abstract contract VaultManager is BaseMiddleware { */ function registerSharedVault(address vault) public virtual onlyOwner { _validateVault(vault); + vaultExists[vault] = true; _sharedVaults.register(getCurrentEpoch(), vault); } /* * @notice Registers a new operator vault. - * @param vault The address of the vault to register. * @param operator The address of the operator. + * @param vault The address of the vault to register. */ - function registerOperatorVault(address vault, address operator) public virtual onlyOwner { + function registerOperatorVault(address operator, address vault) public virtual onlyOwner { _validateVault(vault); + vaultExists[vault] = true; _operatorVaults[operator].register(getCurrentEpoch(), vault); } @@ -170,6 +178,7 @@ abstract contract VaultManager is BaseMiddleware { */ function unregisterSharedVault(address vault) public virtual onlyOwner { _sharedVaults.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); + delete vaultExists[vault]; } /* @@ -179,6 +188,7 @@ abstract contract VaultManager is BaseMiddleware { */ function unregisterOperatorVault(address operator, address vault) public virtual onlyOwner { _operatorVaults[operator].unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); + delete vaultExists[vault]; } /* @@ -187,7 +197,7 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @return The stake of the operator. */ - function getOperatorStake(uint48 epoch, address operator) public view returns (uint256 stake) { + function getOperatorStake(uint32 epoch, address operator) public view returns (uint256 stake) { uint48 timestamp = getEpochStart(epoch); address[] memory vaults = activeVaults(operator); uint160[] memory _subnetworks = activeSubnetworks(); @@ -209,7 +219,7 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @return The power of the operator. */ - function getOperatorPower(uint48 epoch, address operator) public view returns (uint256 power) { + function getOperatorPower(uint32 epoch, address operator) public view returns (uint256 power) { uint48 timestamp = getEpochStart(epoch); address[] memory vaults = activeVaults(operator); uint160[] memory _subnetworks = activeSubnetworks(); @@ -232,7 +242,7 @@ abstract contract VaultManager is BaseMiddleware { * @param operators The list of operator addresses. * @return The total stake of the operators. */ - function _totalStake(uint48 epoch, address[] memory operators) internal view returns (uint256 stake) { + function _totalStake(uint32 epoch, address[] memory operators) internal view returns (uint256 stake) { for (uint256 i; i < operators.length; ++i) { uint256 operatorStake = getOperatorStake(epoch, operators[i]); stake += operatorStake; @@ -257,10 +267,10 @@ abstract contract VaultManager is BaseMiddleware { bytes32 subnetwork, address operator, uint256 amount, - bytes calldata hints + bytes memory hints ) internal returns (SlashResponse memory resp) { if (!(_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault))) { - revert NotVault(); + revert NotOperatorVault(); } if (timestamp + SLASHING_WINDOW < Time.timestamp()) { @@ -284,21 +294,16 @@ abstract contract VaultManager is BaseMiddleware { /* * @notice Executes a veto-based slash for a vault. * @param vault The address of the vault. - * @param operator The address of the operator. * @param slashIndex The index of the slash to execute. * @param hints Additional data for the veto slasher. * @return The amount that was slashed. */ - function executeSlash(address vault, address operator, uint256 slashIndex, bytes calldata hints) + function executeSlash(address vault, uint256 slashIndex, bytes calldata hints) public virtual onlyOwner returns (uint256 slashedAmount) { - if (!(_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault))) { - revert NotVault(); - } - address slasher = IVault(vault).slasher(); uint64 slasherType = IEntity(slasher).TYPE(); if (slasherType != VETO_SLASHER_TYPE) { @@ -313,6 +318,10 @@ abstract contract VaultManager is BaseMiddleware { * @param vault The address of the vault to validate. */ function _validateVault(address vault) private view { + if (vaultExists[vault]) { + revert VaultAlreadyRegistred(); + } + if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { revert NotVault(); } diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index 2a0247e..adc2822 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -62,7 +62,7 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { * @return An array of ValidatorData containing the power and key of each validator. */ function getValidatorSet() public view returns (ValidatorData[] memory validatorSet) { - uint48 epoch = getCurrentEpoch(); // Get the current epoch + uint32 epoch = getCurrentEpoch(); // Get the current epoch address[] memory operators = activeOperators(); // Get the list of active operators validatorSet = new ValidatorData[](operators.length); // Initialize the validator set uint256 len = 0; // Length counter @@ -96,13 +96,11 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { * @param slashHints Hints for the slashing process. * @return An array of SlashResponse indicating the results of the slashing. */ - function slash( - uint48 epoch, - bytes32 key, - uint256 amount, - bytes[][] calldata stakeHints, - bytes[] calldata slashHints - ) public onlyOwner returns (SlashResponse[] memory slashResponses) { + function slash(uint32 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) + public + onlyOwner + returns (SlashResponse[] memory slashResponses) + { uint48 epochStart = getEpochStart(epoch); // Get the start timestamp for the epoch address operator = operatorByKey(key); // Get the operator associated with the key diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 7795c99..0e322e4 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -25,8 +25,9 @@ library PauseableEnumerableSet { */ struct Inner { uint160 value; // The actual value. - uint48 enabledEpoch; // Epoch when the value was enabled. - uint48 disabledEpoch; // Epoch when the value was disabled. + uint32 enabledEpoch; // Epoch when the value was enabled. + uint32 disabledEpoch; // Epoch when the value was disabled. + uint32 prevEnabledEpoch; // Epoch when the value was enabled before disabled. } // Custom error messages @@ -34,6 +35,7 @@ library PauseableEnumerableSet { error NotRegistered(); // Thrown when trying to modify a value that's not registered. error AlreadyEnabled(); // Thrown when enabling an already enabled value. error NotEnabled(); // Thrown when disabling a value that's not enabled. + error Enabled(); // Thrown when trying to disable a value that's enabled. error ImmutablePeriodNotPassed(); // Thrown when an action is attempted before immutable period passes. /* @@ -49,11 +51,11 @@ library PauseableEnumerableSet { * @notice Returns the address and its active period at a given position in the AddressSet. * @param self The AddressSet storage. * @param pos The position in the set. - * @return The address, enabled epoch, and disabled epoch at the position. + * @return The address, enabled epoch, disabled epoch and enabled before dsiabled epoch at the position. */ - function at(AddressSet storage self, uint256 pos) internal view returns (address, uint48, uint48) { - (uint160 value, uint48 enabledEpoch, uint48 disabledEpoch) = self.set.at(pos); - return (address(value), enabledEpoch, disabledEpoch); + function at(AddressSet storage self, uint256 pos) internal view returns (address, uint32, uint32, uint32) { + (uint160 value, uint32 enabledEpoch, uint32 disabledEpoch, uint32 prevEnabled) = self.set.at(pos); + return (address(value), enabledEpoch, disabledEpoch, prevEnabled); } /* @@ -62,7 +64,7 @@ library PauseableEnumerableSet { * @param epoch The epoch to check. * @return An array of active addresses. */ - function getActive(AddressSet storage self, uint48 epoch) internal view returns (address[] memory array) { + function getActive(AddressSet storage self, uint32 epoch) internal view returns (address[] memory array) { uint160[] memory uint160Array = self.set.getActive(epoch); assembly ("memory-safe") { @@ -78,7 +80,7 @@ library PauseableEnumerableSet { * @param epoch The epoch when the address is added. * @param addr The address to register. */ - function register(AddressSet storage self, uint48 epoch, address addr) internal { + function register(AddressSet storage self, uint32 epoch, address addr) internal { self.set.register(epoch, uint160(addr)); } @@ -88,7 +90,7 @@ library PauseableEnumerableSet { * @param epoch The epoch when the address is paused. * @param addr The address to pause. */ - function pause(AddressSet storage self, uint48 epoch, address addr) internal { + function pause(AddressSet storage self, uint32 epoch, address addr) internal { self.set.pause(epoch, uint160(addr)); } @@ -99,7 +101,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The required immutable period before unpausing. * @param addr The address to unpause. */ - function unpause(AddressSet storage self, uint48 epoch, uint48 immutableEpochs, address addr) internal { + function unpause(AddressSet storage self, uint32 epoch, uint32 immutableEpochs, address addr) internal { self.set.unpause(epoch, immutableEpochs, uint160(addr)); } @@ -110,7 +112,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The required immutable period before unregistering. * @param addr The address to unregister. */ - function unregister(AddressSet storage self, uint48 epoch, uint48 immutableEpochs, address addr) internal { + function unregister(AddressSet storage self, uint32 epoch, uint32 immutableEpochs, address addr) internal { self.set.unregister(epoch, immutableEpochs, uint160(addr)); } @@ -137,9 +139,9 @@ library PauseableEnumerableSet { * @notice Returns the value and its active period at a given position in the Uint160Set. * @param self The Uint160Set storage. * @param pos The position in the set. - * @return The value, enabled epoch, and disabled epoch at the position. + * @return The value, enabled epoch, disabled epoch and enabled before disabled epoch at the position. */ - function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint48, uint48) { + function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint32, uint32, uint32) { return self.array[pos].get(); } @@ -149,7 +151,7 @@ library PauseableEnumerableSet { * @param epoch The epoch to check. * @return An array of active values. */ - function getActive(Uint160Set storage self, uint48 epoch) internal view returns (uint160[] memory) { + function getActive(Uint160Set storage self, uint32 epoch) internal view returns (uint160[] memory) { uint160[] memory array = new uint160[](self.array.length); uint256 len = 0; for (uint256 i; i < self.array.length; ++i) { @@ -172,7 +174,7 @@ library PauseableEnumerableSet { * @param epoch The epoch when the value is added. * @param value The Uint160 value to register. */ - function register(Uint160Set storage self, uint48 epoch, uint160 value) internal { + function register(Uint160Set storage self, uint32 epoch, uint160 value) internal { if (self.positions[value] != 0) { revert AlreadyRegistered(); } @@ -189,7 +191,7 @@ library PauseableEnumerableSet { * @param epoch The epoch when the value is paused. * @param value The Uint160 value to pause. */ - function pause(Uint160Set storage self, uint48 epoch, uint160 value) internal { + function pause(Uint160Set storage self, uint32 epoch, uint160 value) internal { if (self.positions[value] == 0) { revert NotRegistered(); } @@ -204,7 +206,7 @@ library PauseableEnumerableSet { * @param immutableEpochs The required immutable period before unpausing. * @param value The Uint160 value to unpause. */ - function unpause(Uint160Set storage self, uint48 epoch, uint48 immutableEpochs, uint160 value) internal { + function unpause(Uint160Set storage self, uint32 epoch, uint32 immutableEpochs, uint160 value) internal { if (self.positions[value] == 0) { revert NotRegistered(); } @@ -220,19 +222,20 @@ library PauseableEnumerableSet { * @param immutableEpochs The required immutable period before unregistering. * @param value The Uint160 value to unregister. */ - function unregister(Uint160Set storage self, uint48 epoch, uint48 immutableEpochs, uint160 value) internal { + function unregister(Uint160Set storage self, uint32 epoch, uint32 immutableEpochs, uint160 value) internal { if (self.positions[value] == 0) { revert NotRegistered(); } - if (self.array.length == 1 || self.array.length == self.positions[value]) { + uint256 pos = self.positions[value] - 1; + self.array[pos].validateUnregister(epoch, immutableEpochs); + + if (self.array.length == 1 || self.array.length == pos + 1) { delete self.positions[value]; self.array.pop(); return; } - uint256 pos = self.positions[value] - 1; - self.array[pos].validateUnregister(epoch, immutableEpochs); self.array[pos] = self.array[self.array.length - 1]; self.array.pop(); @@ -262,10 +265,10 @@ library PauseableEnumerableSet { /* * @notice @notice Returns the value and its active period from the Inner struct. * @param self The Inner struct. - * @return The value, enabled epoch, and disabled epoch. + * @return The value, enabled epoch, disabled epoch and enabled before disabled epoch. */ - function get(Inner storage self) internal view returns (uint160, uint48, uint48) { - return (self.value, self.enabledEpoch, self.disabledEpoch); + function get(Inner storage self) internal view returns (uint160, uint32, uint32, uint32) { + return (self.value, self.enabledEpoch, self.disabledEpoch, self.prevEnabledEpoch); } /* @@ -274,10 +277,9 @@ library PauseableEnumerableSet { * @param epoch The epoch when the value is set. * @param value The Uint160 value to store. */ - function set(Inner storage self, uint48 epoch, uint160 value) internal { + function set(Inner storage self, uint32 epoch, uint160 value) internal { self.value = value; self.enabledEpoch = epoch + 1; - self.disabledEpoch = 0; } /* @@ -286,10 +288,9 @@ library PauseableEnumerableSet { * @param epoch The epoch when the address is set. * @param addr The address to store. */ - function set(Inner storage self, uint48 epoch, address addr) internal { + function set(Inner storage self, uint32 epoch, address addr) internal { self.value = uint160(addr); self.enabledEpoch = epoch + 1; - self.disabledEpoch = 0; } /* @@ -297,13 +298,12 @@ library PauseableEnumerableSet { * @param self The Inner struct. * @param epoch The epoch when the value is enabled. */ - function enable(Inner storage self, uint48 epoch) internal { - if (self.enabledEpoch != 0 && self.disabledEpoch == 0) { + function enable(Inner storage self, uint32 epoch) internal { + if (self.enabledEpoch != 0) { revert AlreadyEnabled(); } self.enabledEpoch = epoch + 1; - self.disabledEpoch = 0; } /* @@ -311,11 +311,13 @@ library PauseableEnumerableSet { * @param self The Inner struct. * @param epoch The epoch when the value is disabled. */ - function disable(Inner storage self, uint48 epoch) internal { - if (self.enabledEpoch == 0 || self.disabledEpoch != 0) { + function disable(Inner storage self, uint32 epoch) internal { + if (self.enabledEpoch == 0) { revert NotEnabled(); } + self.prevEnabledEpoch = self.enabledEpoch; + self.enabledEpoch = 0; self.disabledEpoch = epoch; } @@ -325,9 +327,9 @@ library PauseableEnumerableSet { * @param epoch The epoch to check. * @return True if the value was active at the epoch, false otherwise. */ - function wasActiveAt(Inner storage self, uint48 epoch) internal view returns (bool) { + function wasActiveAt(Inner storage self, uint32 epoch) internal view returns (bool) { return (self.enabledEpoch != 0 && self.enabledEpoch <= epoch) - && (self.disabledEpoch == 0 || self.disabledEpoch >= epoch); + || (self.disabledEpoch >= epoch && self.prevEnabledEpoch <= epoch); } /* @@ -336,8 +338,8 @@ library PauseableEnumerableSet { * @param epoch The current epoch. * @param immutableEpochs The immutable period that must pass before unpausing. */ - function validateUnpause(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { - if (self.disabledEpoch + immutableEpochs > epoch) { + function validateUnpause(Inner storage self, uint32 epoch, uint32 immutableEpochs) internal view { + if (self.disabledEpoch + immutableEpochs - 1 > epoch) { revert ImmutablePeriodNotPassed(); } } @@ -348,8 +350,12 @@ library PauseableEnumerableSet { * @param epoch The current epoch. * @param immutableEpochs The immutable period that must pass before unregistering. */ - function validateUnregister(Inner storage self, uint48 epoch, uint48 immutableEpochs) internal view { - if (self.disabledEpoch == 0 || self.disabledEpoch + immutableEpochs > epoch) { + function validateUnregister(Inner storage self, uint32 epoch, uint32 immutableEpochs) internal view { + if (self.enabledEpoch != 0 || self.disabledEpoch == 0) { + revert Enabled(); + } + + if (self.disabledEpoch + immutableEpochs > epoch) { revert ImmutablePeriodNotPassed(); } } From b048bbe774a8f23ee1f94314ddbfacf5e747b4b7 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 31 Oct 2024 18:02:04 +0400 Subject: [PATCH 021/115] tests: add tests --- test/SDK.t.sol | 415 +++++++++++ test/SimpleMiddleware.t.sol | 912 ++++++++++++------------ test/mocks/ExtendedSimpleMiddleware.sol | 19 + 3 files changed, 903 insertions(+), 443 deletions(-) create mode 100644 test/SDK.t.sol create mode 100644 test/mocks/ExtendedSimpleMiddleware.sol diff --git a/test/SDK.t.sol b/test/SDK.t.sol new file mode 100644 index 0000000..f8f3a9b --- /dev/null +++ b/test/SDK.t.sol @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; + +import {ExtendedSimpleMiddleware} from "./mocks/ExtendedSimpleMiddleware.sol"; +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {BaseMiddleware} from "../src/BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "../src/libraries/PauseableEnumerableSet.sol"; +import {KeyManager} from "../src/KeyManager.sol"; +import {OperatorManager} from "../src/OperatorManager.sol"; +import {VaultManager} from "../src/VaultManager.sol"; +import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; +import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract SDKTest is POCBaseTest { + using Subnetwork for bytes32; + using Subnetwork for address; + using Math for uint256; + + address network = address(0x123); + address notOwner = address(0xdead); + + ExtendedSimpleMiddleware internal middleware; + + uint48 internal epochDuration = 600; // 10 minutes + uint48 internal slashingWindow = 1200; // 20 minutes + + function setUp() public override { + vm.warp(1729690309); + + super.setUp(); + + _deposit(vault1, alice, 550 ether); + _deposit(vault2, alice, 500 ether); + + // Initialize middleware contract + middleware = new ExtendedSimpleMiddleware( + address(network), + address(operatorRegistry), + address(vaultFactory), + address(operatorNetworkOptInService), + owner, + epochDuration, + slashingWindow + ); + + _registerNetwork(network, address(middleware)); + } + + function testOperators() public { + address operator = address(0x1337); + uint256 operatorsLength = middleware.operatorsLength(); + assertEq(operatorsLength, 0, "Operators length should be 0"); + + // can't register without registration + vm.expectRevert(); + middleware.registerOperator(operator); + + _registerOperator(operator); + + // can't register without opt-in + vm.expectRevert(); + middleware.registerOperator(operator); + + _optInOperatorNetwork(operator, network); + middleware.registerOperator(operator); + + operatorsLength = middleware.operatorsLength(); + assertEq(operatorsLength, 1, "Operators length should be 1"); + + // can't register twice + vm.expectRevert(); + middleware.registerOperator(operator); + + // activates on next epoch + address[] memory operators = middleware.activeOperators(); + assertEq(operators.length, 0, "Active operators length should be 0"); + skipEpoch(); + operators = middleware.activeOperators(); + assertEq(operators.length, 1, "Active operators length should be 1"); + + // pause + middleware.pauseOperator(operator); + + // can't pause twice + vm.expectRevert(); + middleware.pauseOperator(operator); + + // pause applies on next epoch + operators = middleware.activeOperators(); + assertEq(operators.length, 1, "Active operators length should be 1"); + + // can't unpause right now, minumum one epoch before immutable period passed + vm.expectRevert(); + middleware.unpauseOperator(operator); + + skipEpoch(); + operators = middleware.activeOperators(); + assertEq(operators.length, 0, "Active operators length should be 0"); + + // unpause + middleware.unpauseOperator(operator); + + // unpause applies on next epoch + operators = middleware.activeOperators(); + assertEq(operators.length, 0, "Active operators length should be 0"); + skipEpoch(); + operators = middleware.activeOperators(); + assertEq(operators.length, 1, "Active operators length should be 1"); + + // pause and unregister + middleware.pauseOperator(operator); + + // can't unregister before immutable period passed + vm.expectRevert(); + middleware.unregisterOperator(operator); + skipEpoch(); + vm.expectRevert(); + middleware.unregisterOperator(operator); + skipEpoch(); + middleware.unregisterOperator(operator); + + operatorsLength = middleware.operatorsLength(); + assertEq(operatorsLength, 0, "Operators length should be 0"); + } + + function testKeys() public { + bytes32 key = keccak256("key"); + address operator = address(0x1337); + + bytes32 operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, bytes32(0), "Operator's key should be empty"); + address keyOperator = middleware.operatorByKey(key); + assertEq(keyOperator, address(0), "Key's operator should be empty"); + + middleware.updateKey(operator, key); + keyOperator = middleware.operatorByKey(key); + assertEq(keyOperator, operator, "Key's operator was not updated correctly"); + + // applies on next epoch + operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, bytes32(0), "Operator's key should be empty"); + + skipEpoch(); + operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, key, "Operator's key was not updated correctly"); + + // update key + bytes32 newKey = keccak256("newKey"); + middleware.updateKey(operator, newKey); + + // can't update already active key twice + vm.expectRevert(); + middleware.updateKey(operator, newKey); + + keyOperator = middleware.operatorByKey(key); + assertEq(keyOperator, operator, "Key's operator was not updated correctly"); + + // applies on next epoch + operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, key, "Operator's key should be previous key"); + + skipEpoch(); + operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, newKey, "Operator's key was not updated correctly"); + + bytes32 zeroKey = bytes32(0); + middleware.updateKey(operator, zeroKey); + operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, newKey, "Operator's key should be previous key"); + + skipEpoch(); + operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, zeroKey, "Operator's key was not updated correctly"); + + // can't set used key to another operator + vm.expectRevert(); + middleware.updateKey(address(0x123123), key); + + // should apply update to latest updated key + bytes32 newKey2 = keccak256("newKey2"); + bytes32 newKey3 = keccak256("newKey3"); + middleware.updateKey(operator, newKey2); + middleware.updateKey(operator, newKey3); + operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, zeroKey, "Operator's key should be previous key"); + + skipEpoch(); + operatorKey = middleware.operatorKey(operator); + assertEq(operatorKey, newKey3, "Operator's key was not updated correctly"); + } + + function testBLSKeys() public { + bytes memory key = "key"; + address operator = address(0x1337); + + bytes memory operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, "", "Operator's BLS key should be empty"); + address keyOperator = middleware.operatorByBLSKey(key); + assertEq(keyOperator, address(0), "BLS key's operator should be empty"); + + middleware.updateBLSKey(operator, key); + keyOperator = middleware.operatorByBLSKey(key); + assertEq(keyOperator, operator, "BLS key's operator was not updated correctly"); + + // applies on next epoch + operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, "", "Operator's BLS key should be empty"); + + skipEpoch(); + operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, key, "Operator's BLS key was not updated correctly"); + + // update key + bytes memory newKey = "newKey"; + middleware.updateBLSKey(operator, newKey); + + // can't update already active bls key twice + vm.expectRevert(); + middleware.updateBLSKey(operator, newKey); + + keyOperator = middleware.operatorByBLSKey(key); + assertEq(keyOperator, operator, "BLS key's operator was not updated correctly"); + + // applies on next epoch + operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, key, "Operator's BLS key should be previous key"); + + skipEpoch(); + operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, newKey, "Operator's BLS key was not updated correctly"); + + bytes memory zeroKey = ""; + middleware.updateBLSKey(operator, zeroKey); + operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, newKey, "Operator's BLS key should be previous key"); + + skipEpoch(); + operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, zeroKey, "Operator's BLS key was not updated correctly"); + + // can't set used bls key to another operator + vm.expectRevert(); + middleware.updateBLSKey(address(0x123123), key); + + // should apply update to latest updated bls key + bytes memory newKey2 = "newKey2"; + bytes memory newKey3 = "newKey3"; + middleware.updateBLSKey(operator, newKey2); + middleware.updateBLSKey(operator, newKey3); + operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, zeroKey, "Operator's BLS key should be previous key"); + + skipEpoch(); + operatorKey = middleware.operatorBLSKey(operator); + assertEq(operatorKey, newKey3, "Operator's BLS key was not updated correctly"); + } + + function testSubnetworks() public { + skipEpoch(); // let first 0 subnetwork activate + + uint96 subnetwork = 1; + uint256 subnetworksLength = middleware.subnetworksLength(); + assertEq(subnetworksLength, 1, "Subnetworks length should be 1"); + + // register + middleware.registerSubnetwork(subnetwork); + + subnetworksLength = middleware.subnetworksLength(); + assertEq(subnetworksLength, 2, "Subnetworks length should be 2"); + + // can't register twice + vm.expectRevert(); + middleware.registerSubnetwork(subnetwork); + + // activates on next epoch + uint160[] memory subnetworks = middleware.activeSubnetworks(); + assertEq(subnetworks.length, 1, "Active subnetworks length should be 1"); + skipEpoch(); + subnetworks = middleware.activeSubnetworks(); + assertEq(subnetworks.length, 2, "Active subnetworks length should be 2"); + + // pause + middleware.pauseSubnetwork(subnetwork); + + // can't pause twice + vm.expectRevert(); + middleware.pauseSubnetwork(subnetwork); + + // pause applies on next epoch + subnetworks = middleware.activeSubnetworks(); + assertEq(subnetworks.length, 2, "Active subnetworks length should be 2"); + + // can't unpause right now, minumum one epoch before immutable period passed + vm.expectRevert(); + middleware.unpauseSubnetwork(subnetwork); + + skipEpoch(); + subnetworks = middleware.activeSubnetworks(); + assertEq(subnetworks.length, 1, "Active subnetworks length should be 1"); + + // unpause + middleware.unpauseSubnetwork(subnetwork); + + // unpause applies on next epoch + subnetworks = middleware.activeSubnetworks(); + assertEq(subnetworks.length, 1, "Active subnetworks length should be 1"); + skipEpoch(); + subnetworks = middleware.activeSubnetworks(); + assertEq(subnetworks.length, 2, "Active subnetworks length should be 2"); + + // pause and unregister + middleware.pauseSubnetwork(subnetwork); + + // can't unregister before immutable period passed + vm.expectRevert(); + middleware.unregisterSubnetwork(subnetwork); + skipEpoch(); + vm.expectRevert(); + middleware.unregisterSubnetwork(subnetwork); + skipEpoch(); + middleware.unregisterSubnetwork(subnetwork); + + subnetworksLength = middleware.subnetworksLength(); + assertEq(subnetworksLength, 1, "Subnetworks length should be 1"); + } + + function testVaults() public { + address operator = address(0x1337); + address operator2 = address(0x1338); + address vault = address(vault1); + + // should register only vault + vm.expectRevert(); + middleware.registerSharedVault(operator); + + // register shared vault + middleware.registerSharedVault(vault); + uint256 sharedVaultLength = middleware.sharedVaultsLength(); + assertEq(sharedVaultLength, 1, "Shared vaults length should be 1"); + + // active vaults should be zero + address[] memory activeVaults = middleware.activeVaults(operator); + assertEq(activeVaults.length, 0, "Active vaults length should be 0"); + + // shared vaults are active for each operator + skipEpoch(); + activeVaults = middleware.activeVaults(operator); + assertEq(activeVaults.length, 1, "Active vaults length should be 1"); + + // can't register twice + vm.expectRevert(); + middleware.registerSharedVault(vault); + + // can't register as operator vault if registered as shared + vm.expectRevert(); + middleware.registerOperatorVault(operator, vault); + + middleware.registerOperatorVault(operator, address(vault2)); + + // activates only for registered operator + skipEpoch(); + activeVaults = middleware.activeVaults(operator); + assertEq(activeVaults.length, 2, "Active vaults length should be 2"); + activeVaults = middleware.activeVaults(operator2); + assertEq(activeVaults.length, 1, "Active vaults length should be 1"); + + // can't register as shared if resgistred as operator's + vm.expectRevert(); + middleware.registerSharedVault(address(vault2)); + + // pause, unpause and unregister same as operators, subnetworks so don't test + middleware.pauseSharedVault(vault); + skipImmutablePeriod(); + middleware.unregisterSharedVault(vault); + + // can register to operator after unregister + middleware.registerOperatorVault(operator, vault); + } + + function testStakes() public { + address operator1 = address(0x1337); + address operator2 = address(0x1338); + + _registerOperator(operator1); + _registerOperator(operator2); + + _optInOperatorNetwork(operator1, network); + _optInOperatorNetwork(operator2, network); + + _optInOperatorVault(vault1, operator1); + _optInOperatorVault(vault1, operator2); + _optInOperatorVault(vault2, operator1); + _optInOperatorVault(vault2, operator2); + } + + // function testSlash() public {} + + // function testEpochs() public {} + + function skipEpoch() private { + vm.warp(block.timestamp + epochDuration); + } + + function skipImmutablePeriod() private { + vm.warp(block.timestamp + slashingWindow); + } +} diff --git a/test/SimpleMiddleware.t.sol b/test/SimpleMiddleware.t.sol index 79e7bfd..3b27388 100644 --- a/test/SimpleMiddleware.t.sol +++ b/test/SimpleMiddleware.t.sol @@ -1,443 +1,469 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; - -import {SimpleMiddleware} from "../src/examples/simple-network/SimpleMiddleware.sol"; -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseMiddleware} from "../src/BaseMiddleware.sol"; -import {PauseableEnumerableSet} from "../src/libraries/PauseableEnumerableSet.sol"; -import {KeyManager} from "../src/KeyManager.sol"; -import {OperatorManager} from "../src/OperatorManager.sol"; -import {VaultManager} from "../src/VaultManager.sol"; -import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; -import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; - -contract SimpleMiddlewareTest is POCBaseTest { - using Subnetwork for bytes32; - using Subnetwork for address; - using Math for uint256; - - address network = address(0x123); - - SimpleMiddleware internal middleware; - - uint48 internal epochDuration = 600; // 10 minutes - uint48 internal slashingWindow = 1200; // 20 minutes - - address internal operator1; - address internal operator2; - address internal operator3; - - bytes32 internal key1 = keccak256("key1"); - bytes32 internal key2 = keccak256("key2"); - bytes32 internal key3 = keccak256("key3"); - - uint96 internal subnetwork1 = 0; - uint96 internal subnetwork2 = 1; - - function setUp() public override { - vm.warp(1729690309); - - super.setUp(); - - vm.prank(network); - networkRegistry.registerNetwork(); - - // Set operators - operator1 = alice; - operator2 = bob; - operator3 = address(0x3); // A third operator - - // Register operator1 - vm.startPrank(operator1); - operatorRegistry.registerOperator(); - operatorNetworkOptInService.optIn(address(network)); - vm.stopPrank(); - - // Register operator2 - vm.startPrank(operator2); - operatorRegistry.registerOperator(); - operatorNetworkOptInService.optIn(address(network)); - vm.stopPrank(); - - // Register operator3 - vm.startPrank(operator3); - operatorRegistry.registerOperator(); - operatorNetworkOptInService.optIn(address(network)); - vm.stopPrank(); - - // Opt-in operators to the vault - _optInOperatorVault(vault1, operator1); - _optInOperatorVault(vault1, operator2); - _optInOperatorVault(vault2, operator3); - _optInOperatorVault(vault2, operator1); - - // Set network limit and operator shares in the delegator - _setMaxNetworkLimit(address(delegator1), network, subnetwork1, type(uint256).max); - _setMaxNetworkLimit(address(delegator2), network, subnetwork1, type(uint256).max); - - _deposit(vault1, alice, 550 ether); - _deposit(vault2, alice, 500 ether); - - _setNetworkLimitNetwork(delegator1, alice, address(network), 550 ether); - _setNetworkLimitFull(delegator2, alice, address(network), 450 ether); - - _setOperatorNetworkShares(delegator1, alice, address(network), operator1, 250 ether); - _setOperatorNetworkShares(delegator1, alice, address(network), operator2, 300 ether); - _setOperatorNetworkLimit(delegator2, alice, address(network), operator1, 250 ether); - _setOperatorNetworkLimit(delegator2, alice, address(network), operator3, 200 ether); - - // Initialize middleware contract - middleware = new SimpleMiddleware( - address(network), - address(operatorRegistry), - address(vaultFactory), - address(operatorNetworkOptInService), - owner, - epochDuration, - slashingWindow - ); - - // Register network middleware - vm.prank(network); - networkMiddlewareService.setMiddleware(address(middleware)); - - // Register operators in the middleware - vm.startPrank(owner); - middleware.registerOperator(operator1); - middleware.registerOperator(operator2); - middleware.registerOperator(operator3); - - // Update keys for operators - middleware.updateKey(operator1, key1); - middleware.updateKey(operator2, key2); - middleware.updateKey(operator3, key3); - - // Register subnetworks - middleware.registerSubnetwork(subnetwork2); - - // Register vaults - middleware.registerSharedVault(address(vault1)); - middleware.registerSharedVault(address(vault2)); - - vm.stopPrank(); - - skipEpoch(); - } - - function testUpdateKeys() public { - // Update operator1's key - bytes32 newKey1 = keccak256("newKey1"); - vm.prank(owner); - middleware.updateKey(operator1, newKey1); - assertEq(middleware.operatorKey(operator1), key1, "Operator1's key should not be updated yet"); - - skipEpoch(); - - // Verify that the key is updated - bytes32 currentKey1 = middleware.operatorKey(operator1); - assertEq(currentKey1, newKey1, "Operator1's key was not updated correctly"); - } - - function testPauseUnpauseOperator() public { - // Pause operator2 - vm.prank(owner); - middleware.pauseOperator(operator2); - skipEpoch(); - - // Verify operator2 is paused - address[] memory activeOperators = middleware.activeOperators(); - bool foundOperator2 = false; - for (uint256 i = 0; i < activeOperators.length; i++) { - if (activeOperators[i] == operator2) { - foundOperator2 = true; - } - } - assertFalse(foundOperator2, "Operator2 should be paused"); - - // Unpause operator2 - vm.prank(owner); - skipImmutablePeriod(); - middleware.unpauseOperator(operator2); - skipEpoch(); - - // Verify operator2 is active again - activeOperators = middleware.activeOperators(); - foundOperator2 = false; - for (uint256 i = 0; i < activeOperators.length; i++) { - if (activeOperators[i] == operator2) { - foundOperator2 = true; - break; - } - } - assertTrue(foundOperator2, "Operator2 should be active after unpausing"); - } - - function testPauseUnpauseVault() public { - // Pause the vault - vm.prank(owner); - middleware.pauseSharedVault(address(vault1)); - skipEpoch(); - - // Verify the vault is paused - address[] memory vaults = middleware.activeVaults(operator1); - bool foundVault = false; - for (uint256 i = 0; i < vaults.length; i++) { - if (vaults[i] == address(vault1)) { - foundVault = true; - break; - } - } - assertFalse(foundVault, "Vault should be paused"); - - // Unpause the vault - vm.prank(owner); - skipImmutablePeriod(); - middleware.unpauseSharedVault(address(vault1)); - skipEpoch(); - - // Verify the vault is active again - vaults = middleware.activeVaults(operator1); - foundVault = false; - for (uint256 i = 0; i < vaults.length; i++) { - if (vaults[i] == address(vault1)) { - foundVault = true; - break; - } - } - assertTrue(foundVault, "Vault should be active after unpausing"); - } - - function testPauseUnpauseSubnetwork() public { - // Pause subnetwork2 - vm.prank(owner); - middleware.pauseSubnetwork(subnetwork2); - skipEpoch(); - - // Verify subnetwork2 is paused - uint160[] memory activeSubnetworks = middleware.activeSubnetworks(); - bool foundSubnetwork2 = false; - for (uint256 i = 0; i < activeSubnetworks.length; i++) { - if (activeSubnetworks[i] == uint160(subnetwork2)) { - foundSubnetwork2 = true; - break; - } - } - assertFalse(foundSubnetwork2, "Subnetwork2 should be paused"); - - // Unpause subnetwork2 - vm.prank(owner); - skipImmutablePeriod(); - middleware.unpauseSubnetwork(subnetwork2); - skipEpoch(); - - // Verify subnetwork2 is active again - activeSubnetworks = middleware.activeSubnetworks(); - foundSubnetwork2 = false; - for (uint256 i = 0; i < activeSubnetworks.length; i++) { - if (activeSubnetworks[i] == uint160(subnetwork2)) { - foundSubnetwork2 = true; - break; - } - } - assertTrue(foundSubnetwork2, "Subnetwork2 should be active after unpausing"); - } - - function testSlashOperator() public { - // Prepare hints (empty in this context) - uint256 vaultsLen = middleware.activeVaults(operator1).length; - bytes[][] memory stakeHints = new bytes[][](vaultsLen); - for (uint256 i; i < vaultsLen; i++) { - stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); - for (uint256 j; j < stakeHints[i].length; j++) { - stakeHints[i][j] = ""; - } - } - - bytes[] memory slashHints = new bytes[](stakeHints.length); - - skipEpoch(); - uint48 epoch = middleware.getCurrentEpoch(); - uint256 amount = 100 ether; - - // Perform slash on operator1 - vm.prank(owner); - SimpleMiddleware.SlashResponse[] memory responses = - middleware.slash(epoch, key1, amount, stakeHints, slashHints); - - // Check that the slashing occurred - assertEq(responses.length, 2, "Should have one slash response"); - assertEq(responses[0].vault, address(vault1), "Incorrect vault in slash response"); - assertEq(responses[0].slasherType, slasher1.TYPE(), "Incorrect slasher type"); - assertEq(responses[0].response, amount / 2, "Incorrect slashed amount"); - assertEq(responses[1].vault, address(vault2), "Incorrect vault in slash response"); - assertEq(responses[1].slasherType, slasher2.TYPE(), "Incorrect slasher type"); - assertEq(responses[1].response, amount / 2, "Incorrect slashed amount"); - - // Verify that the operator's stake has decreased - uint256 remainingStake = - delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); - assertEq(remainingStake, 227272727272727272727, "Operator1 stake not reduced correctly"); - } - - function testUnregisterOperator() public { - // Unregister operator3 - vm.startPrank(owner); - middleware.pauseOperator(operator3); - skipEpoch(); - skipImmutablePeriod(); - middleware.unregisterOperator(operator3); - vm.stopPrank(); - skipEpoch(); - - // Verify operator3 is unregistered - address[] memory activeOperators = middleware.activeOperators(); - bool foundOperator3 = false; - for (uint256 i = 0; i < activeOperators.length; i++) { - if (activeOperators[i] == operator3) { - foundOperator3 = true; - break; - } - } - assertFalse(foundOperator3, "Operator3 should be unregistered"); - } - - function testUnregisterSubnetwork() public { - // Unregister subnetwork1 - vm.startPrank(owner); - middleware.pauseSubnetwork(subnetwork1); - skipEpoch(); - skipImmutablePeriod(); - middleware.unregisterSubnetwork(subnetwork1); - vm.stopPrank(); - skipEpoch(); - - // Verify subnetwork1 is unregistered - uint160[] memory activeSubnetworks = middleware.activeSubnetworks(); - bool foundSubnetwork1 = false; - for (uint256 i = 0; i < activeSubnetworks.length; i++) { - if (activeSubnetworks[i] == uint160(subnetwork1)) { - foundSubnetwork1 = true; - break; - } - } - assertFalse(foundSubnetwork1, "Subnetwork1 should be unregistered"); - } - - function testUnregisterVault() public { - // Unregister the vault - vm.startPrank(owner); - middleware.pauseSharedVault(address(vault1)); - skipEpoch(); - skipImmutablePeriod(); - middleware.unregisterSharedVault(address(vault1)); - vm.stopPrank(); - skipEpoch(); - - // Verify the vault is unregistered - address[] memory vaults = middleware.activeVaults(operator1); - bool foundVault = false; - for (uint256 i = 0; i < vaults.length; i++) { - if (vaults[i] == address(vault1)) { - foundVault = true; - break; - } - } - assertFalse(foundVault, "Vault should be unregistered"); - } - - function testValidatorSet() public { - // Get validator set - SimpleMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); - - // Expected validator set length is 3 - assertEq(validatorSet.length, 3, "Validator set length should be 3"); - - // Verify each validator's power - for (uint256 i = 0; i < validatorSet.length; i++) { - SimpleMiddleware.ValidatorData memory validator = validatorSet[i]; - if (validator.key == key1) { - assertEq(validator.power, 500 ether, "Operator1 power mismatch"); - } else if (validator.key == key2) { - assertEq(validator.power, 300 ether, "Operator2 power mismatch"); - } else if (validator.key == key3) { - assertEq(validator.power, 200 ether, "Operator3 power mismatch"); - } else { - assert(false); - } - } - } - - function testOperatorStakeAfterSlash() public { - // Prepare hints - uint256 vaultsLen = middleware.activeVaults(operator1).length; - bytes[][] memory stakeHints = new bytes[][](vaultsLen); - for (uint256 i; i < vaultsLen; i++) { - stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); - for (uint256 j; j < stakeHints[i].length; j++) { - stakeHints[i][j] = ""; - } - } - - bytes[] memory slashHints = new bytes[](stakeHints.length); - slashHints[0] = ""; - - uint48 epoch = middleware.getCurrentEpoch(); - uint256 amount = 100 ether; - - // Perform a slash on operator1 - vm.prank(owner); - middleware.slash(epoch, key1, amount, stakeHints, slashHints); - - // Verify operator1's stake is reduced - uint256 remainingStake = - delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); - assertEq(remainingStake, 227272727272727272727, "Operator1 stake not reduced correctly"); - - // Verify total stake is updated - skipEpoch(); - uint256 totalStake = middleware.getTotalStake(); - uint256 expectedTotalStake = 950 ether - 1; // 1000 ether - 100 / 2 ether - assertEq(totalStake, expectedTotalStake, "Total stake not updated correctly"); - } - - function testRevertOnUnregisteredOperator() public { - // Attempt to register an operator not in the registry - address unregisteredOperator = address(0x4); - vm.expectRevert(OperatorManager.NotOperator.selector); - vm.prank(owner); - middleware.registerOperator(unregisteredOperator); - } - - function testModifyStake() public { - // Increase operator1's stake - _deposit(vault1, operator1, 50 ether); - - _setNetworkLimitNetwork(delegator1, operator1, address(network), 600 ether); - _setOperatorNetworkShares(delegator1, alice, address(network), operator1, 600 ether); - - // Verify the stake is updated - uint256 newStake = delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); - assertEq(newStake, 400 ether, "Operator1's stake not updated correctly"); - - skipEpoch(); - // Verify total stake is updated - uint256 totalStake = middleware.getTotalStake(); - uint256 expectedTotalStake = 1050 ether; // Previous total + 50 ether - assertEq(totalStake, expectedTotalStake, "Total stake not updated correctly"); - } - - function skipEpoch() private { - vm.warp(block.timestamp + epochDuration); - } - - function skipImmutablePeriod() private { - vm.warp(block.timestamp + slashingWindow); - } -} +// // SPDX-License-Identifier: MIT +// pragma solidity 0.8.25; + +// import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; + +// import {SimpleMiddleware} from "../src/examples/simple-network/SimpleMiddleware.sol"; +// import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +// import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +// import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +// import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +// import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +// import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +// import {BaseMiddleware} from "../src/BaseMiddleware.sol"; +// import {PauseableEnumerableSet} from "../src/libraries/PauseableEnumerableSet.sol"; +// import {KeyManager} from "../src/KeyManager.sol"; +// import {OperatorManager} from "../src/OperatorManager.sol"; +// import {VaultManager} from "../src/VaultManager.sol"; +// import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; +// import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; + +// contract SimpleMiddlewareTest is POCBaseTest { +// using Subnetwork for bytes32; +// using Subnetwork for address; +// using Math for uint256; + +// address network = address(0x123); + +// SimpleMiddleware internal middleware; + +// uint48 internal epochDuration = 600; // 10 minutes +// uint48 internal slashingWindow = 1200; // 20 minutes + +// address internal operator1; +// address internal operator2; +// address internal operator3; + +// bytes32 internal key1 = keccak256("key1"); +// bytes32 internal key2 = keccak256("key2"); +// bytes32 internal key3 = keccak256("key3"); + +// uint96 internal subnetwork1 = 0; +// uint96 internal subnetwork2 = 1; + +// function setUp() public override { +// vm.warp(1729690309); + +// super.setUp(); + +// vm.prank(network); +// networkRegistry.registerNetwork(); + +// // Set operators +// operator1 = alice; +// operator2 = bob; +// operator3 = address(0x3); // A third operator + +// // Register operator1 +// vm.startPrank(operator1); +// operatorRegistry.registerOperator(); +// operatorNetworkOptInService.optIn(address(network)); +// vm.stopPrank(); + +// // Register operator2 +// vm.startPrank(operator2); +// operatorRegistry.registerOperator(); +// operatorNetworkOptInService.optIn(address(network)); +// vm.stopPrank(); + +// // Register operator3 +// vm.startPrank(operator3); +// operatorRegistry.registerOperator(); +// operatorNetworkOptInService.optIn(address(network)); +// vm.stopPrank(); + +// // Opt-in operators to the vault +// _optInOperatorVault(vault1, operator1); +// _optInOperatorVault(vault1, operator2); +// _optInOperatorVault(vault2, operator3); +// _optInOperatorVault(vault2, operator1); + +// // Set network limit and operator shares in the delegator +// _setMaxNetworkLimit(address(delegator1), network, subnetwork1, type(uint256).max); +// _setMaxNetworkLimit(address(delegator2), network, subnetwork1, type(uint256).max); + +// _deposit(vault1, alice, 550 ether); +// _deposit(vault2, alice, 500 ether); + +// _setNetworkLimitNetwork(delegator1, alice, address(network), 550 ether); +// _setNetworkLimitFull(delegator2, alice, address(network), 450 ether); + +// _setOperatorNetworkShares(delegator1, alice, address(network), operator1, 250 ether); +// _setOperatorNetworkShares(delegator1, alice, address(network), operator2, 300 ether); +// _setOperatorNetworkLimit(delegator2, alice, address(network), operator1, 250 ether); +// _setOperatorNetworkLimit(delegator2, alice, address(network), operator3, 200 ether); + +// // Initialize middleware contract +// middleware = new SimpleMiddleware( +// address(network), +// address(operatorRegistry), +// address(vaultFactory), +// address(operatorNetworkOptInService), +// owner, +// epochDuration, +// slashingWindow +// ); + +// // Register network middleware +// vm.prank(network); +// networkMiddlewareService.setMiddleware(address(middleware)); + +// // Register operators in the middleware +// vm.startPrank(owner); +// middleware.registerOperator(operator1); +// middleware.registerOperator(operator2); +// middleware.registerOperator(operator3); + +// // Update keys for operators +// middleware.updateKey(operator1, key1); +// middleware.updateKey(operator2, key2); +// middleware.updateKey(operator3, key3); + +// // Register subnetworks +// middleware.registerSubnetwork(subnetwork2); + +// // Register vaults +// middleware.registerSharedVault(address(vault1)); +// middleware.registerSharedVault(address(vault2)); + +// vm.stopPrank(); + +// skipEpoch(); +// // vm.startPrank(owner); +// } + +// function testOperator() public { +// address someOperator = address(0x4); +// vm.expectRevert(); +// middleware.registerOperator(someOperator); + +// middleware.registerOperator(operator1); +// } + +// function testUpdateKeys() public { +// // Update operator1's key +// bytes32 newKey1 = keccak256("newKey1"); +// vm.prank(owner); +// middleware.updateKey(operator1, newKey1); +// assertEq(middleware.operatorKey(operator1), key1, "Operator1's key should not be updated yet"); + +// skipEpoch(); + +// // Verify that the key is updated +// bytes32 currentKey1 = middleware.operatorKey(operator1); +// assertEq(currentKey1, newKey1, "Operator1's key was not updated correctly"); +// } + +// function testPauseUnpauseOperator() public { +// // Pause operator2 +// vm.prank(owner); +// middleware.pauseOperator(operator2); +// skipEpoch(); + +// // Verify operator2 is paused +// address[] memory activeOperators = middleware.activeOperators(); +// bool foundOperator2 = false; +// for (uint256 i = 0; i < activeOperators.length; i++) { +// if (activeOperators[i] == operator2) { +// foundOperator2 = true; +// } +// } +// assertFalse(foundOperator2, "Operator2 should be paused"); + +// // Unpause operator2 +// vm.prank(owner); +// skipEpoch(); +// middleware.unpauseOperator(operator2); +// skipEpoch(); + +// // Verify operator2 is active again +// activeOperators = middleware.activeOperators(); +// foundOperator2 = false; +// for (uint256 i = 0; i < activeOperators.length; i++) { +// if (activeOperators[i] == operator2) { +// foundOperator2 = true; +// break; +// } +// } +// assertTrue(foundOperator2, "Operator2 should be active after unpausing"); +// } + +// function testPauseUnpauseVault() public { +// // Pause the vault +// vm.prank(owner); +// middleware.pauseSharedVault(address(vault1)); +// skipEpoch(); + +// // Verify the vault is paused +// address[] memory vaults = middleware.activeVaults(operator1); +// bool foundVault = false; +// for (uint256 i = 0; i < vaults.length; i++) { +// if (vaults[i] == address(vault1)) { +// foundVault = true; +// break; +// } +// } +// assertFalse(foundVault, "Vault should be paused"); + +// // Unpause the vault +// vm.prank(owner); +// skipEpoch(); +// middleware.unpauseSharedVault(address(vault1)); +// skipEpoch(); + +// // Verify the vault is active again +// vaults = middleware.activeVaults(operator1); +// foundVault = false; +// for (uint256 i = 0; i < vaults.length; i++) { +// if (vaults[i] == address(vault1)) { +// foundVault = true; +// break; +// } +// } +// assertTrue(foundVault, "Vault should be active after unpausing"); +// } + +// function testPauseUnpauseSubnetwork() public { +// // Pause subnetwork2 +// vm.prank(owner); +// middleware.pauseSubnetwork(subnetwork2); +// skipEpoch(); + +// // Verify subnetwork2 is paused +// uint160[] memory activeSubnetworks = middleware.activeSubnetworks(); +// bool foundSubnetwork2 = false; +// for (uint256 i = 0; i < activeSubnetworks.length; i++) { +// if (activeSubnetworks[i] == uint160(subnetwork2)) { +// foundSubnetwork2 = true; +// break; +// } +// } +// assertFalse(foundSubnetwork2, "Subnetwork2 should be paused"); + +// // Unpause subnetwork2 +// vm.prank(owner); +// skipEpoch(); +// middleware.unpauseSubnetwork(subnetwork2); +// skipEpoch(); + +// // Verify subnetwork2 is active again +// activeSubnetworks = middleware.activeSubnetworks(); +// foundSubnetwork2 = false; +// for (uint256 i = 0; i < activeSubnetworks.length; i++) { +// if (activeSubnetworks[i] == uint160(subnetwork2)) { +// foundSubnetwork2 = true; +// break; +// } +// } +// assertTrue(foundSubnetwork2, "Subnetwork2 should be active after unpausing"); +// } + +// function testSlashOperator() public { +// // Prepare hints (empty in this context) +// uint256 vaultsLen = middleware.activeVaults(operator1).length; +// bytes[][] memory stakeHints = new bytes[][](vaultsLen); +// for (uint256 i; i < vaultsLen; i++) { +// stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); +// for (uint256 j; j < stakeHints[i].length; j++) { +// stakeHints[i][j] = ""; +// } +// } + +// bytes[] memory slashHints = new bytes[](stakeHints.length); + +// skipEpoch(); +// uint32 epoch = middleware.getCurrentEpoch(); +// uint256 amount = 100 ether; + +// // Perform slash on operator1 +// vm.prank(owner); +// SimpleMiddleware.SlashResponse[] memory responses = +// middleware.slash(epoch, key1, amount, stakeHints, slashHints); + +// // Check that the slashing occurred +// assertEq(responses.length, 2, "Should have one slash response"); +// assertEq(responses[0].vault, address(vault1), "Incorrect vault in slash response"); +// assertEq(responses[0].slasherType, slasher1.TYPE(), "Incorrect slasher type"); +// assertEq(responses[0].response, amount / 2, "Incorrect slashed amount"); +// assertEq(responses[1].vault, address(vault2), "Incorrect vault in slash response"); +// assertEq(responses[1].slasherType, slasher2.TYPE(), "Incorrect slasher type"); +// assertEq(responses[1].response, amount / 2, "Incorrect slashed amount"); + +// // Verify that the operator's stake has decreased +// uint256 remainingStake = +// delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); +// assertEq(remainingStake, 227272727272727272727, "Operator1 stake not reduced correctly"); + +// skipImmutablePeriod(); +// // can't slash too old epoch +// vm.expectRevert(); +// middleware.slash(epoch, key1, amount, stakeHints, slashHints); +// } + +// function testUnregisterOperator() public { +// // Unregister operator3 +// vm.startPrank(owner); +// middleware.pauseOperator(operator3); +// skipEpoch(); +// skipImmutablePeriod(); +// middleware.unregisterOperator(operator3); +// vm.stopPrank(); +// skipEpoch(); + +// // Verify operator3 is unregistered +// address[] memory activeOperators = middleware.activeOperators(); +// bool foundOperator3 = false; +// for (uint256 i = 0; i < activeOperators.length; i++) { +// if (activeOperators[i] == operator3) { +// foundOperator3 = true; +// break; +// } +// } +// assertFalse(foundOperator3, "Operator3 should be unregistered"); +// } + +// function testUnregisterSubnetwork() public { +// // Unregister subnetwork1 +// vm.startPrank(owner); +// middleware.pauseSubnetwork(subnetwork1); + +// vm.expectRevert(); +// middleware.unregisterSubnetwork(subnetwork1); + +// skipEpoch(); + +// vm.expectRevert(); +// middleware.unregisterSubnetwork(subnetwork1); + +// skipImmutablePeriod(); +// middleware.unregisterSubnetwork(subnetwork1); +// vm.stopPrank(); + +// skipEpoch(); + +// // Verify subnetwork1 is unregistered +// uint160[] memory activeSubnetworks = middleware.activeSubnetworks(); +// bool foundSubnetwork1 = false; +// for (uint256 i = 0; i < activeSubnetworks.length; i++) { +// if (activeSubnetworks[i] == uint160(subnetwork1)) { +// foundSubnetwork1 = true; +// break; +// } +// } +// assertFalse(foundSubnetwork1, "Subnetwork1 should be unregistered"); +// } + +// function testUnregisterVault() public { +// // Unregister the vault +// vm.startPrank(owner); +// middleware.pauseSharedVault(address(vault1)); +// skipEpoch(); +// skipImmutablePeriod(); +// middleware.unregisterSharedVault(address(vault1)); +// vm.stopPrank(); +// skipEpoch(); + +// // Verify the vault is unregistered +// address[] memory vaults = middleware.activeVaults(operator1); +// bool foundVault = false; +// for (uint256 i = 0; i < vaults.length; i++) { +// if (vaults[i] == address(vault1)) { +// foundVault = true; +// break; +// } +// } +// assertFalse(foundVault, "Vault should be unregistered"); +// } + +// function testValidatorSet() public { +// // Get validator set +// SimpleMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); + +// // Expected validator set length is 3 +// assertEq(validatorSet.length, 3, "Validator set length should be 3"); + +// // Verify each validator's power +// for (uint256 i = 0; i < validatorSet.length; i++) { +// SimpleMiddleware.ValidatorData memory validator = validatorSet[i]; +// if (validator.key == key1) { +// assertEq(validator.power, 500 ether, "Operator1 power mismatch"); +// } else if (validator.key == key2) { +// assertEq(validator.power, 300 ether, "Operator2 power mismatch"); +// } else if (validator.key == key3) { +// assertEq(validator.power, 200 ether, "Operator3 power mismatch"); +// } else { +// assert(false); +// } +// } +// } + +// function testOperatorStakeAfterSlash() public { +// // Prepare hints +// uint256 vaultsLen = middleware.activeVaults(operator1).length; +// bytes[][] memory stakeHints = new bytes[][](vaultsLen); +// for (uint256 i; i < vaultsLen; i++) { +// stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); +// for (uint256 j; j < stakeHints[i].length; j++) { +// stakeHints[i][j] = ""; +// } +// } + +// bytes[] memory slashHints = new bytes[](stakeHints.length); +// slashHints[0] = ""; + +// uint32 epoch = middleware.getCurrentEpoch(); +// uint256 amount = 100 ether; + +// // Perform a slash on operator1 +// vm.prank(owner); +// middleware.slash(epoch, key1, amount, stakeHints, slashHints); + +// // Verify operator1's stake is reduced +// uint256 remainingStake = +// delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); +// assertEq(remainingStake, 227272727272727272727, "Operator1 stake not reduced correctly"); + +// // Verify total stake is updated +// skipEpoch(); +// uint256 totalStake = middleware.getTotalStake(); +// uint256 expectedTotalStake = 950 ether - 1; // 1000 ether - 100 / 2 ether +// assertEq(totalStake, expectedTotalStake, "Total stake not updated correctly"); +// } + +// function testRevertOnUnregisteredOperator() public { +// // Attempt to register an operator not in the registry +// address unregisteredOperator = address(0x4); +// vm.expectRevert(OperatorManager.NotOperator.selector); +// vm.prank(owner); +// middleware.registerOperator(unregisteredOperator); +// } + +// function testModifyStake() public { +// // Increase operator1's stake +// _deposit(vault1, operator1, 50 ether); + +// _setNetworkLimitNetwork(delegator1, operator1, address(network), 600 ether); +// _setOperatorNetworkShares(delegator1, alice, address(network), operator1, 600 ether); + +// // Verify the stake is updated +// uint256 newStake = delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); +// assertEq(newStake, 400 ether, "Operator1's stake not updated correctly"); + +// skipEpoch(); +// // Verify total stake is updated +// uint256 totalStake = middleware.getTotalStake(); +// uint256 expectedTotalStake = 1050 ether; // Previous total + 50 ether +// assertEq(totalStake, expectedTotalStake, "Total stake not updated correctly"); +// } + +// function skipEpoch() private { +// vm.warp(block.timestamp + epochDuration); +// } + +// function skipImmutablePeriod() private { +// vm.warp(block.timestamp + slashingWindow); +// } +// } + +// // subnetworks add +// // diff --git a/test/mocks/ExtendedSimpleMiddleware.sol b/test/mocks/ExtendedSimpleMiddleware.sol new file mode 100644 index 0000000..03e5cdd --- /dev/null +++ b/test/mocks/ExtendedSimpleMiddleware.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {SimpleMiddleware} from "../../src/examples/simple-network/SimpleMiddleware.sol"; +import {BLSKeyManager} from "../../src/BLSKeyManager.sol"; + +contract ExtendedSimpleMiddleware is SimpleMiddleware, BLSKeyManager { + constructor( + address network, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + address owner, + uint48 epochDuration, + uint48 slashingWindow + ) + SimpleMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, owner, epochDuration, slashingWindow) + {} +} From 76ab4dbcb6e83da3df90e51489f59897c6268537 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Tue, 5 Nov 2024 07:23:13 +0700 Subject: [PATCH 022/115] test: slash stake tests --- src/VaultManager.sol | 19 +- test/SDK.t.sol | 186 +++++++++++++- test/SimpleMiddleware.t.sol | 469 ------------------------------------ 3 files changed, 191 insertions(+), 483 deletions(-) delete mode 100644 test/SimpleMiddleware.t.sol diff --git a/src/VaultManager.sol b/src/VaultManager.sol index dad124e..d4555a6 100644 --- a/src/VaultManager.sol +++ b/src/VaultManager.sol @@ -36,7 +36,7 @@ abstract contract VaultManager is BaseMiddleware { PauseableEnumerableSet.AddressSet internal _sharedVaults; mapping(address => PauseableEnumerableSet.AddressSet) internal _operatorVaults; - mapping(address => bool) public vaultExists; + mapping(address => uint256) public operatorVaultExists; struct SlashResponse { address vault; @@ -123,7 +123,9 @@ abstract contract VaultManager is BaseMiddleware { */ function registerSharedVault(address vault) public virtual onlyOwner { _validateVault(vault); - vaultExists[vault] = true; + if (operatorVaultExists[vault] > 0) { + revert VaultAlreadyRegistred(); + } _sharedVaults.register(getCurrentEpoch(), vault); } @@ -134,7 +136,7 @@ abstract contract VaultManager is BaseMiddleware { */ function registerOperatorVault(address operator, address vault) public virtual onlyOwner { _validateVault(vault); - vaultExists[vault] = true; + operatorVaultExists[vault]++; _operatorVaults[operator].register(getCurrentEpoch(), vault); } @@ -178,7 +180,6 @@ abstract contract VaultManager is BaseMiddleware { */ function unregisterSharedVault(address vault) public virtual onlyOwner { _sharedVaults.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); - delete vaultExists[vault]; } /* @@ -188,7 +189,7 @@ abstract contract VaultManager is BaseMiddleware { */ function unregisterOperatorVault(address operator, address vault) public virtual onlyOwner { _operatorVaults[operator].unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); - delete vaultExists[vault]; + operatorVaultExists[vault]--; } /* @@ -318,14 +319,14 @@ abstract contract VaultManager is BaseMiddleware { * @param vault The address of the vault to validate. */ function _validateVault(address vault) private view { - if (vaultExists[vault]) { - revert VaultAlreadyRegistred(); - } - if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { revert NotVault(); } + if (_sharedVaults.contains(vault)) { + revert VaultAlreadyRegistred(); + } + if (!IVault(vault).isInitialized()) { revert VaultNotInitialized(); } diff --git a/test/SDK.t.sol b/test/SDK.t.sol index f8f3a9b..590c99e 100644 --- a/test/SDK.t.sol +++ b/test/SDK.t.sol @@ -37,8 +37,9 @@ contract SDKTest is POCBaseTest { super.setUp(); - _deposit(vault1, alice, 550 ether); - _deposit(vault2, alice, 500 ether); + _deposit(vault1, alice, 1000 ether); + _deposit(vault2, alice, 1000 ether); + _deposit(vault3, alice, 1000 ether); // Initialize middleware contract middleware = new ExtendedSimpleMiddleware( @@ -376,6 +377,9 @@ contract SDKTest is POCBaseTest { vm.expectRevert(); middleware.registerSharedVault(address(vault2)); + // can register to another operator if registered as operator's + middleware.registerOperatorVault(address(0x1339), address(vault2)); + // pause, unpause and unregister same as operators, subnetworks so don't test middleware.pauseSharedVault(vault); skipImmutablePeriod(); @@ -385,7 +389,7 @@ contract SDKTest is POCBaseTest { middleware.registerOperatorVault(operator, vault); } - function testStakes() public { + function testValidatorSet() public { address operator1 = address(0x1337); address operator2 = address(0x1338); @@ -399,11 +403,183 @@ contract SDKTest is POCBaseTest { _optInOperatorVault(vault1, operator2); _optInOperatorVault(vault2, operator1); _optInOperatorVault(vault2, operator2); + + _setMaxNetworkLimit(address(delegator1), network, 0, 1000 ether); + _setMaxNetworkLimit(address(delegator2), network, 0, 1000 ether); + + _setNetworkLimitNetwork(delegator1, alice, network, 1000 ether); + _setNetworkLimitFull(delegator2, alice, network, 1000 ether); + + _setOperatorNetworkShares(delegator1, alice, network, operator1, 500 ether); + _setOperatorNetworkShares(delegator1, alice, network, operator2, 500 ether); + + _setOperatorNetworkLimit(delegator2, alice, network, operator1, 500 ether); + _setOperatorNetworkLimit(delegator2, alice, network, operator2, 500 ether); + + middleware.registerOperator(operator1); + middleware.registerOperator(operator2); + middleware.registerSharedVault(address(vault1)); + middleware.registerSharedVault(address(vault2)); + + bytes32 key1 = keccak256("key1"); + bytes32 key2 = keccak256("key2"); + + middleware.updateKey(operator1, key1); + middleware.updateKey(operator2, key2); + + ExtendedSimpleMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); + assertEq(validatorSet.length, 0, "valset length should be 0"); + + skipEpoch(); + + // updates applies on next epoch + validatorSet = middleware.getValidatorSet(); + assertEq(validatorSet.length, 2, "valset length should be 2"); + for (uint256 i = 0; i < validatorSet.length; i++) { + ExtendedSimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + if (validator.key == key1) { + assertEq(validator.power, 1000 ether, "validator1 power should be 1000"); + } else if (validator.key == key2) { + assertEq(validator.power, 1000 ether, "validator2 power should be 1000"); + } else { + assertEq(true, false, "unexpected validator key"); + } + } + + middleware.pauseOperator(operator1); + + // not excluded immediately + validatorSet = middleware.getValidatorSet(); + assertEq(validatorSet.length, 2, "valset length should be 1"); + skipEpoch(); + validatorSet = middleware.getValidatorSet(); + assertEq(validatorSet.length, 1, "valset length should be 1"); + assertEq(validatorSet[0].key, key2, "validator key should be key2"); + assertEq(validatorSet[0].power, 1000 ether, "validator2 power should be 1000"); + + middleware.unpauseOperator(operator1); + skipEpoch(); + validatorSet = middleware.getValidatorSet(); + assertEq(validatorSet.length, 2, "valset length should be 1"); + + // stake decrease if vault paused + middleware.pauseSharedVault(address(vault1)); + skipEpoch(); + validatorSet = middleware.getValidatorSet(); + assertEq(validatorSet.length, 2, "valset length should be 2"); + for (uint256 i = 0; i < validatorSet.length; i++) { + ExtendedSimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + if (validator.key == key1) { + assertEq(validator.power, 500 ether, "validator1 power should be 1000"); + } else if (validator.key == key2) { + assertEq(validator.power, 500 ether, "validator2 power should be 1000"); + } else { + assertEq(true, false, "unexpected validator key"); + } + } + + // change on next epoch + _setOperatorNetworkLimit(delegator2, alice, network, operator2, 1000 ether); + validatorSet = middleware.getValidatorSet(); + assertEq(validatorSet.length, 2, "valset length should be 2"); + for (uint256 i = 0; i < validatorSet.length; i++) { + ExtendedSimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + if (validator.key == key1) { + assertEq(validator.power, 500 ether, "validator1 power should be 1000"); + } else if (validator.key == key2) { + assertEq(validator.power, 500 ether, "validator2 power should be 1000"); + } else { + assertEq(true, false, "unexpected validator key"); + } + } + skipEpoch(); + validatorSet = middleware.getValidatorSet(); + assertEq(validatorSet.length, 2, "valset length should be 2"); + for (uint256 i = 0; i < validatorSet.length; i++) { + ExtendedSimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + if (validator.key == key1) { + assertEq(validator.power, 500 ether, "validator1 power should be 1000"); + } else if (validator.key == key2) { + assertEq(validator.power, 1000 ether, "validator2 power should be 1000"); + } else { + assertEq(true, false, "unexpected validator key"); + } + } } - // function testSlash() public {} + function testSlash() public { + address operator1 = address(0x1337); + address operator2 = address(0x1338); + + _registerOperator(operator1); + _registerOperator(operator2); - // function testEpochs() public {} + _optInOperatorNetwork(operator1, network); + _optInOperatorNetwork(operator2, network); + + _optInOperatorVault(vault3, operator1); + _optInOperatorVault(vault3, operator2); + _optInOperatorVault(vault2, operator1); + _optInOperatorVault(vault2, operator2); + + _setMaxNetworkLimit(address(delegator3), network, 0, 1000 ether); + _setMaxNetworkLimit(address(delegator2), network, 0, 1000 ether); + + _setNetworkLimitNetwork(delegator3, alice, network, 1000 ether); + _setNetworkLimitFull(delegator2, alice, network, 1000 ether); + + _setOperatorNetworkShares(delegator3, alice, network, operator1, 500 ether); + _setOperatorNetworkShares(delegator3, alice, network, operator2, 500 ether); + + _setOperatorNetworkLimit(delegator2, alice, network, operator1, 500 ether); + _setOperatorNetworkLimit(delegator2, alice, network, operator2, 500 ether); + + middleware.registerOperator(operator1); + middleware.registerOperator(operator2); + middleware.registerSharedVault(address(vault3)); + middleware.registerSharedVault(address(vault2)); + + bytes32 key1 = keccak256("key1"); + bytes32 key2 = keccak256("key2"); + + middleware.updateKey(operator1, key1); + middleware.updateKey(operator2, key2); + + skipEpoch(); + + // Prepare hints + uint256 vaultsLen = middleware.activeVaults(operator1).length; + bytes[][] memory stakeHints = new bytes[][](vaultsLen); + for (uint256 i; i < vaultsLen; i++) { + stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); + for (uint256 j; j < stakeHints[i].length; j++) { + stakeHints[i][j] = ""; + } + } + + bytes[] memory slashHints = new bytes[](stakeHints.length); + slashHints[0] = ""; + + uint32 epoch = middleware.getCurrentEpoch(); + uint256 amount = 100 ether; + + // Perform a slash on operator1 + vm.prank(owner); + middleware.slash(epoch, key1, amount, stakeHints, slashHints); + + vm.warp(block.timestamp + 1 days); + middleware.executeSlash(address(vault3), 0, ""); + + skipEpoch(); + uint256 totalStake = middleware.getTotalStake(); + + assertEq(totalStake, 1950 ether, "Total stake not updated correctly"); + + // can't slash after immutable period + skipImmutablePeriod(); + vm.expectRevert(); + middleware.slash(epoch, key1, amount, stakeHints, slashHints); + } function skipEpoch() private { vm.warp(block.timestamp + epochDuration); diff --git a/test/SimpleMiddleware.t.sol b/test/SimpleMiddleware.t.sol deleted file mode 100644 index 3b27388..0000000 --- a/test/SimpleMiddleware.t.sol +++ /dev/null @@ -1,469 +0,0 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity 0.8.25; - -// import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; - -// import {SimpleMiddleware} from "../src/examples/simple-network/SimpleMiddleware.sol"; -// import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -// import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -// import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -// import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -// import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -// import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -// import {BaseMiddleware} from "../src/BaseMiddleware.sol"; -// import {PauseableEnumerableSet} from "../src/libraries/PauseableEnumerableSet.sol"; -// import {KeyManager} from "../src/KeyManager.sol"; -// import {OperatorManager} from "../src/OperatorManager.sol"; -// import {VaultManager} from "../src/VaultManager.sol"; -// import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; -// import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; - -// contract SimpleMiddlewareTest is POCBaseTest { -// using Subnetwork for bytes32; -// using Subnetwork for address; -// using Math for uint256; - -// address network = address(0x123); - -// SimpleMiddleware internal middleware; - -// uint48 internal epochDuration = 600; // 10 minutes -// uint48 internal slashingWindow = 1200; // 20 minutes - -// address internal operator1; -// address internal operator2; -// address internal operator3; - -// bytes32 internal key1 = keccak256("key1"); -// bytes32 internal key2 = keccak256("key2"); -// bytes32 internal key3 = keccak256("key3"); - -// uint96 internal subnetwork1 = 0; -// uint96 internal subnetwork2 = 1; - -// function setUp() public override { -// vm.warp(1729690309); - -// super.setUp(); - -// vm.prank(network); -// networkRegistry.registerNetwork(); - -// // Set operators -// operator1 = alice; -// operator2 = bob; -// operator3 = address(0x3); // A third operator - -// // Register operator1 -// vm.startPrank(operator1); -// operatorRegistry.registerOperator(); -// operatorNetworkOptInService.optIn(address(network)); -// vm.stopPrank(); - -// // Register operator2 -// vm.startPrank(operator2); -// operatorRegistry.registerOperator(); -// operatorNetworkOptInService.optIn(address(network)); -// vm.stopPrank(); - -// // Register operator3 -// vm.startPrank(operator3); -// operatorRegistry.registerOperator(); -// operatorNetworkOptInService.optIn(address(network)); -// vm.stopPrank(); - -// // Opt-in operators to the vault -// _optInOperatorVault(vault1, operator1); -// _optInOperatorVault(vault1, operator2); -// _optInOperatorVault(vault2, operator3); -// _optInOperatorVault(vault2, operator1); - -// // Set network limit and operator shares in the delegator -// _setMaxNetworkLimit(address(delegator1), network, subnetwork1, type(uint256).max); -// _setMaxNetworkLimit(address(delegator2), network, subnetwork1, type(uint256).max); - -// _deposit(vault1, alice, 550 ether); -// _deposit(vault2, alice, 500 ether); - -// _setNetworkLimitNetwork(delegator1, alice, address(network), 550 ether); -// _setNetworkLimitFull(delegator2, alice, address(network), 450 ether); - -// _setOperatorNetworkShares(delegator1, alice, address(network), operator1, 250 ether); -// _setOperatorNetworkShares(delegator1, alice, address(network), operator2, 300 ether); -// _setOperatorNetworkLimit(delegator2, alice, address(network), operator1, 250 ether); -// _setOperatorNetworkLimit(delegator2, alice, address(network), operator3, 200 ether); - -// // Initialize middleware contract -// middleware = new SimpleMiddleware( -// address(network), -// address(operatorRegistry), -// address(vaultFactory), -// address(operatorNetworkOptInService), -// owner, -// epochDuration, -// slashingWindow -// ); - -// // Register network middleware -// vm.prank(network); -// networkMiddlewareService.setMiddleware(address(middleware)); - -// // Register operators in the middleware -// vm.startPrank(owner); -// middleware.registerOperator(operator1); -// middleware.registerOperator(operator2); -// middleware.registerOperator(operator3); - -// // Update keys for operators -// middleware.updateKey(operator1, key1); -// middleware.updateKey(operator2, key2); -// middleware.updateKey(operator3, key3); - -// // Register subnetworks -// middleware.registerSubnetwork(subnetwork2); - -// // Register vaults -// middleware.registerSharedVault(address(vault1)); -// middleware.registerSharedVault(address(vault2)); - -// vm.stopPrank(); - -// skipEpoch(); -// // vm.startPrank(owner); -// } - -// function testOperator() public { -// address someOperator = address(0x4); -// vm.expectRevert(); -// middleware.registerOperator(someOperator); - -// middleware.registerOperator(operator1); -// } - -// function testUpdateKeys() public { -// // Update operator1's key -// bytes32 newKey1 = keccak256("newKey1"); -// vm.prank(owner); -// middleware.updateKey(operator1, newKey1); -// assertEq(middleware.operatorKey(operator1), key1, "Operator1's key should not be updated yet"); - -// skipEpoch(); - -// // Verify that the key is updated -// bytes32 currentKey1 = middleware.operatorKey(operator1); -// assertEq(currentKey1, newKey1, "Operator1's key was not updated correctly"); -// } - -// function testPauseUnpauseOperator() public { -// // Pause operator2 -// vm.prank(owner); -// middleware.pauseOperator(operator2); -// skipEpoch(); - -// // Verify operator2 is paused -// address[] memory activeOperators = middleware.activeOperators(); -// bool foundOperator2 = false; -// for (uint256 i = 0; i < activeOperators.length; i++) { -// if (activeOperators[i] == operator2) { -// foundOperator2 = true; -// } -// } -// assertFalse(foundOperator2, "Operator2 should be paused"); - -// // Unpause operator2 -// vm.prank(owner); -// skipEpoch(); -// middleware.unpauseOperator(operator2); -// skipEpoch(); - -// // Verify operator2 is active again -// activeOperators = middleware.activeOperators(); -// foundOperator2 = false; -// for (uint256 i = 0; i < activeOperators.length; i++) { -// if (activeOperators[i] == operator2) { -// foundOperator2 = true; -// break; -// } -// } -// assertTrue(foundOperator2, "Operator2 should be active after unpausing"); -// } - -// function testPauseUnpauseVault() public { -// // Pause the vault -// vm.prank(owner); -// middleware.pauseSharedVault(address(vault1)); -// skipEpoch(); - -// // Verify the vault is paused -// address[] memory vaults = middleware.activeVaults(operator1); -// bool foundVault = false; -// for (uint256 i = 0; i < vaults.length; i++) { -// if (vaults[i] == address(vault1)) { -// foundVault = true; -// break; -// } -// } -// assertFalse(foundVault, "Vault should be paused"); - -// // Unpause the vault -// vm.prank(owner); -// skipEpoch(); -// middleware.unpauseSharedVault(address(vault1)); -// skipEpoch(); - -// // Verify the vault is active again -// vaults = middleware.activeVaults(operator1); -// foundVault = false; -// for (uint256 i = 0; i < vaults.length; i++) { -// if (vaults[i] == address(vault1)) { -// foundVault = true; -// break; -// } -// } -// assertTrue(foundVault, "Vault should be active after unpausing"); -// } - -// function testPauseUnpauseSubnetwork() public { -// // Pause subnetwork2 -// vm.prank(owner); -// middleware.pauseSubnetwork(subnetwork2); -// skipEpoch(); - -// // Verify subnetwork2 is paused -// uint160[] memory activeSubnetworks = middleware.activeSubnetworks(); -// bool foundSubnetwork2 = false; -// for (uint256 i = 0; i < activeSubnetworks.length; i++) { -// if (activeSubnetworks[i] == uint160(subnetwork2)) { -// foundSubnetwork2 = true; -// break; -// } -// } -// assertFalse(foundSubnetwork2, "Subnetwork2 should be paused"); - -// // Unpause subnetwork2 -// vm.prank(owner); -// skipEpoch(); -// middleware.unpauseSubnetwork(subnetwork2); -// skipEpoch(); - -// // Verify subnetwork2 is active again -// activeSubnetworks = middleware.activeSubnetworks(); -// foundSubnetwork2 = false; -// for (uint256 i = 0; i < activeSubnetworks.length; i++) { -// if (activeSubnetworks[i] == uint160(subnetwork2)) { -// foundSubnetwork2 = true; -// break; -// } -// } -// assertTrue(foundSubnetwork2, "Subnetwork2 should be active after unpausing"); -// } - -// function testSlashOperator() public { -// // Prepare hints (empty in this context) -// uint256 vaultsLen = middleware.activeVaults(operator1).length; -// bytes[][] memory stakeHints = new bytes[][](vaultsLen); -// for (uint256 i; i < vaultsLen; i++) { -// stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); -// for (uint256 j; j < stakeHints[i].length; j++) { -// stakeHints[i][j] = ""; -// } -// } - -// bytes[] memory slashHints = new bytes[](stakeHints.length); - -// skipEpoch(); -// uint32 epoch = middleware.getCurrentEpoch(); -// uint256 amount = 100 ether; - -// // Perform slash on operator1 -// vm.prank(owner); -// SimpleMiddleware.SlashResponse[] memory responses = -// middleware.slash(epoch, key1, amount, stakeHints, slashHints); - -// // Check that the slashing occurred -// assertEq(responses.length, 2, "Should have one slash response"); -// assertEq(responses[0].vault, address(vault1), "Incorrect vault in slash response"); -// assertEq(responses[0].slasherType, slasher1.TYPE(), "Incorrect slasher type"); -// assertEq(responses[0].response, amount / 2, "Incorrect slashed amount"); -// assertEq(responses[1].vault, address(vault2), "Incorrect vault in slash response"); -// assertEq(responses[1].slasherType, slasher2.TYPE(), "Incorrect slasher type"); -// assertEq(responses[1].response, amount / 2, "Incorrect slashed amount"); - -// // Verify that the operator's stake has decreased -// uint256 remainingStake = -// delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); -// assertEq(remainingStake, 227272727272727272727, "Operator1 stake not reduced correctly"); - -// skipImmutablePeriod(); -// // can't slash too old epoch -// vm.expectRevert(); -// middleware.slash(epoch, key1, amount, stakeHints, slashHints); -// } - -// function testUnregisterOperator() public { -// // Unregister operator3 -// vm.startPrank(owner); -// middleware.pauseOperator(operator3); -// skipEpoch(); -// skipImmutablePeriod(); -// middleware.unregisterOperator(operator3); -// vm.stopPrank(); -// skipEpoch(); - -// // Verify operator3 is unregistered -// address[] memory activeOperators = middleware.activeOperators(); -// bool foundOperator3 = false; -// for (uint256 i = 0; i < activeOperators.length; i++) { -// if (activeOperators[i] == operator3) { -// foundOperator3 = true; -// break; -// } -// } -// assertFalse(foundOperator3, "Operator3 should be unregistered"); -// } - -// function testUnregisterSubnetwork() public { -// // Unregister subnetwork1 -// vm.startPrank(owner); -// middleware.pauseSubnetwork(subnetwork1); - -// vm.expectRevert(); -// middleware.unregisterSubnetwork(subnetwork1); - -// skipEpoch(); - -// vm.expectRevert(); -// middleware.unregisterSubnetwork(subnetwork1); - -// skipImmutablePeriod(); -// middleware.unregisterSubnetwork(subnetwork1); -// vm.stopPrank(); - -// skipEpoch(); - -// // Verify subnetwork1 is unregistered -// uint160[] memory activeSubnetworks = middleware.activeSubnetworks(); -// bool foundSubnetwork1 = false; -// for (uint256 i = 0; i < activeSubnetworks.length; i++) { -// if (activeSubnetworks[i] == uint160(subnetwork1)) { -// foundSubnetwork1 = true; -// break; -// } -// } -// assertFalse(foundSubnetwork1, "Subnetwork1 should be unregistered"); -// } - -// function testUnregisterVault() public { -// // Unregister the vault -// vm.startPrank(owner); -// middleware.pauseSharedVault(address(vault1)); -// skipEpoch(); -// skipImmutablePeriod(); -// middleware.unregisterSharedVault(address(vault1)); -// vm.stopPrank(); -// skipEpoch(); - -// // Verify the vault is unregistered -// address[] memory vaults = middleware.activeVaults(operator1); -// bool foundVault = false; -// for (uint256 i = 0; i < vaults.length; i++) { -// if (vaults[i] == address(vault1)) { -// foundVault = true; -// break; -// } -// } -// assertFalse(foundVault, "Vault should be unregistered"); -// } - -// function testValidatorSet() public { -// // Get validator set -// SimpleMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); - -// // Expected validator set length is 3 -// assertEq(validatorSet.length, 3, "Validator set length should be 3"); - -// // Verify each validator's power -// for (uint256 i = 0; i < validatorSet.length; i++) { -// SimpleMiddleware.ValidatorData memory validator = validatorSet[i]; -// if (validator.key == key1) { -// assertEq(validator.power, 500 ether, "Operator1 power mismatch"); -// } else if (validator.key == key2) { -// assertEq(validator.power, 300 ether, "Operator2 power mismatch"); -// } else if (validator.key == key3) { -// assertEq(validator.power, 200 ether, "Operator3 power mismatch"); -// } else { -// assert(false); -// } -// } -// } - -// function testOperatorStakeAfterSlash() public { -// // Prepare hints -// uint256 vaultsLen = middleware.activeVaults(operator1).length; -// bytes[][] memory stakeHints = new bytes[][](vaultsLen); -// for (uint256 i; i < vaultsLen; i++) { -// stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); -// for (uint256 j; j < stakeHints[i].length; j++) { -// stakeHints[i][j] = ""; -// } -// } - -// bytes[] memory slashHints = new bytes[](stakeHints.length); -// slashHints[0] = ""; - -// uint32 epoch = middleware.getCurrentEpoch(); -// uint256 amount = 100 ether; - -// // Perform a slash on operator1 -// vm.prank(owner); -// middleware.slash(epoch, key1, amount, stakeHints, slashHints); - -// // Verify operator1's stake is reduced -// uint256 remainingStake = -// delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); -// assertEq(remainingStake, 227272727272727272727, "Operator1 stake not reduced correctly"); - -// // Verify total stake is updated -// skipEpoch(); -// uint256 totalStake = middleware.getTotalStake(); -// uint256 expectedTotalStake = 950 ether - 1; // 1000 ether - 100 / 2 ether -// assertEq(totalStake, expectedTotalStake, "Total stake not updated correctly"); -// } - -// function testRevertOnUnregisteredOperator() public { -// // Attempt to register an operator not in the registry -// address unregisteredOperator = address(0x4); -// vm.expectRevert(OperatorManager.NotOperator.selector); -// vm.prank(owner); -// middleware.registerOperator(unregisteredOperator); -// } - -// function testModifyStake() public { -// // Increase operator1's stake -// _deposit(vault1, operator1, 50 ether); - -// _setNetworkLimitNetwork(delegator1, operator1, address(network), 600 ether); -// _setOperatorNetworkShares(delegator1, alice, address(network), operator1, 600 ether); - -// // Verify the stake is updated -// uint256 newStake = delegator1.stakeAt(network.subnetwork(subnetwork1), operator1, uint48(block.timestamp), ""); -// assertEq(newStake, 400 ether, "Operator1's stake not updated correctly"); - -// skipEpoch(); -// // Verify total stake is updated -// uint256 totalStake = middleware.getTotalStake(); -// uint256 expectedTotalStake = 1050 ether; // Previous total + 50 ether -// assertEq(totalStake, expectedTotalStake, "Total stake not updated correctly"); -// } - -// function skipEpoch() private { -// vm.warp(block.timestamp + epochDuration); -// } - -// function skipImmutablePeriod() private { -// vm.warp(block.timestamp + slashingWindow); -// } -// } - -// // subnetworks add -// // From e28288ab6ca4b07fed4eef98e4df85d899fff2bc Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Wed, 6 Nov 2024 06:05:23 +0700 Subject: [PATCH 023/115] refactor: separate modules as default and base --- .../BaseBLSKeyManager.sol} | 8 +- .../BaseKeyManager.sol} | 8 +- src/KeyManagers/DefaultBLSKeyManager.sol | 16 ++ src/KeyManagers/DefaultKeyManager.sol | 16 ++ .../BaseOperatorManager.sol} | 14 +- .../DefaultOperatorManager.sol | 38 ++++ .../BaseVaultManager.sol} | 163 ++++++++++-------- src/VaultManagers/DefaultVaultManager.sol | 90 ++++++++++ .../simple-network/SimpleMiddleware.sol | 19 +- .../simple-network/SqrtTaskMiddleware.sol | 8 +- 10 files changed, 281 insertions(+), 99 deletions(-) rename src/{BLSKeyManager.sol => KeyManagers/BaseBLSKeyManager.sol} (91%) rename src/{KeyManager.sol => KeyManagers/BaseKeyManager.sol} (91%) create mode 100644 src/KeyManagers/DefaultBLSKeyManager.sol create mode 100644 src/KeyManagers/DefaultKeyManager.sol rename src/{OperatorManager.sol => OperatorManagers/BaseOperatorManager.sol} (84%) create mode 100644 src/OperatorManagers/DefaultOperatorManager.sol rename src/{VaultManager.sol => VaultManagers/BaseVaultManager.sol} (89%) create mode 100644 src/VaultManagers/DefaultVaultManager.sol diff --git a/src/BLSKeyManager.sol b/src/KeyManagers/BaseBLSKeyManager.sol similarity index 91% rename from src/BLSKeyManager.sol rename to src/KeyManagers/BaseBLSKeyManager.sol index 5ef7058..cef86f4 100644 --- a/src/BLSKeyManager.sol +++ b/src/KeyManagers/BaseBLSKeyManager.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {BaseMiddleware} from "./BaseMiddleware.sol"; -import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; +import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BLSKeyManager is BaseMiddleware { +abstract contract BaseBLSKeyManager is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateBLSKey(); @@ -56,7 +56,7 @@ abstract contract BLSKeyManager is BaseMiddleware { * @param operator The address of the operator whose BLS key is to be updated. * @param key The new BLS key to associate with the operator. */ - function updateBLSKey(address operator, bytes memory key) public virtual onlyOwner { + function _updateBLSKey(address operator, bytes memory key) internal { uint32 epoch = getCurrentEpoch(); if (keccak256(blsKeys[operator]) == keccak256(key)) { diff --git a/src/KeyManager.sol b/src/KeyManagers/BaseKeyManager.sol similarity index 91% rename from src/KeyManager.sol rename to src/KeyManagers/BaseKeyManager.sol index 54e6bd0..4157ed6 100644 --- a/src/KeyManager.sol +++ b/src/KeyManagers/BaseKeyManager.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {BaseMiddleware} from "./BaseMiddleware.sol"; -import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; +import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract KeyManager is BaseMiddleware { +abstract contract BaseKeyManager is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateKey(); @@ -56,7 +56,7 @@ abstract contract KeyManager is BaseMiddleware { * @param operator The address of the operator whose key is to be updated. * @param key The new key to associate with the operator. */ - function updateKey(address operator, bytes32 key) public virtual onlyOwner { + function _updateKey(address operator, bytes32 key) internal { uint32 epoch = getCurrentEpoch(); if (keys[operator] == key) { diff --git a/src/KeyManagers/DefaultBLSKeyManager.sol b/src/KeyManagers/DefaultBLSKeyManager.sol new file mode 100644 index 0000000..a3bcf37 --- /dev/null +++ b/src/KeyManagers/DefaultBLSKeyManager.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {BaseBLSKeyManager} from "./BaseBLSKeyManager.sol"; + +abstract contract DefaultBLSKeyManager is BaseBLSKeyManager { + /* + * @notice Updates the BLS key associated with an operator. + * If the new key already exists, a DuplicateBLSKey error is thrown. + * @param operator The address of the operator whose BLS key is to be updated. + * @param key The new BLS key to associate with the operator. + */ + function updateBLSKey(address operator, bytes memory key) public virtual onlyOwner { + _updateBLSKey(operator, key); + } +} diff --git a/src/KeyManagers/DefaultKeyManager.sol b/src/KeyManagers/DefaultKeyManager.sol new file mode 100644 index 0000000..32e9f2f --- /dev/null +++ b/src/KeyManagers/DefaultKeyManager.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {BaseKeyManager} from "./BaseKeyManager.sol"; + +abstract contract DefaultKeyManager is BaseKeyManager { + /* + * @notice Updates the key associated with an operator. + * If the new key already exists, a DuplicateKey error is thrown. + * @param operator The address of the operator whose key is to be updated. + * @param key The new key to associate with the operator. + */ + function updateKey(address operator, bytes32 key) public virtual onlyOwner { + _updateKey(operator, key); + } +} diff --git a/src/OperatorManager.sol b/src/OperatorManagers/BaseOperatorManager.sol similarity index 84% rename from src/OperatorManager.sol rename to src/OperatorManagers/BaseOperatorManager.sol index ffb9c1e..82ee2d1 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManagers/BaseOperatorManager.sol @@ -8,10 +8,10 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseMiddleware} from "./BaseMiddleware.sol"; -import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; +import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract OperatorManager is BaseMiddleware { +abstract contract BaseOperatorManager is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; error NotOperator(); @@ -50,7 +50,7 @@ abstract contract OperatorManager is BaseMiddleware { * @notice Registers a new operator. * @param operator The address of the operator to register. */ - function registerOperator(address operator) public virtual onlyOwner { + function _registerOperator(address operator) internal { if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { revert NotOperator(); } @@ -66,7 +66,7 @@ abstract contract OperatorManager is BaseMiddleware { * @notice Pauses a registered operator. * @param operator The address of the operator to pause. */ - function pauseOperator(address operator) public virtual onlyOwner { + function _pauseOperator(address operator) internal { _operators.pause(getCurrentEpoch(), operator); } @@ -74,7 +74,7 @@ abstract contract OperatorManager is BaseMiddleware { * @notice Unpauses a paused operator. * @param operator The address of the operator to unpause. */ - function unpauseOperator(address operator) public virtual onlyOwner { + function _unpauseOperator(address operator) internal { _operators.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, operator); } @@ -82,7 +82,7 @@ abstract contract OperatorManager is BaseMiddleware { * @notice Unregisters an operator. * @param operator The address of the operator to unregister. */ - function unregisterOperator(address operator) public virtual onlyOwner { + function _unregisterOperator(address operator) internal { _operators.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, operator); } } diff --git a/src/OperatorManagers/DefaultOperatorManager.sol b/src/OperatorManagers/DefaultOperatorManager.sol new file mode 100644 index 0000000..9d8021e --- /dev/null +++ b/src/OperatorManagers/DefaultOperatorManager.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {BaseOperatorManager} from "./BaseOperatorManager.sol"; + +abstract contract DefaultOperatorManager is BaseOperatorManager { + /* + * @notice Registers a new operator. + * @param operator The address of the operator to register. + */ + function registerOperator(address operator) public virtual onlyOwner { + _registerOperator(operator); + } + + /* + * @notice Pauses a registered operator. + * @param operator The address of the operator to pause. + */ + function pauseOperator(address operator) public virtual onlyOwner { + _pauseOperator(operator); + } + + /* + * @notice Unpauses a paused operator. + * @param operator The address of the operator to unpause. + */ + function unpauseOperator(address operator) public virtual onlyOwner { + _unpauseOperator(operator); + } + + /* + * @notice Unregisters an operator. + * @param operator The address of the operator to unregister. + */ + function unregisterOperator(address operator) public virtual onlyOwner { + _unregisterOperator(operator); + } +} diff --git a/src/VaultManager.sol b/src/VaultManagers/BaseVaultManager.sol similarity index 89% rename from src/VaultManager.sol rename to src/VaultManagers/BaseVaultManager.sol index d4555a6..2f65320 100644 --- a/src/VaultManager.sol +++ b/src/VaultManagers/BaseVaultManager.sol @@ -13,10 +13,10 @@ import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseMiddleware} from "./BaseMiddleware.sol"; -import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; +import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract VaultManager is BaseMiddleware { +abstract contract BaseVaultManager is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; using Subnetwork for address; @@ -117,11 +117,86 @@ abstract contract VaultManager is BaseMiddleware { return vaults; } + /* + * @notice Returns the stake of an operator at a specific epoch. + * @param epoch The epoch to check. + * @param operator The address of the operator. + * @return The stake of the operator. + */ + function getOperatorStake(uint32 epoch, address operator) public view returns (uint256 stake) { + uint48 timestamp = getEpochStart(epoch); + address[] memory vaults = activeVaults(operator); + uint160[] memory _subnetworks = activeSubnetworks(); + + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + for (uint256 j; j < _subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); + stake += IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); + } + } + + return stake; + } + + /* + * @notice Returns the power of an operator at a specific epoch. + * @param epoch The epoch to check. + * @param operator The address of the operator. + * @return The power of the operator. + */ + function getOperatorPower(uint32 epoch, address operator) public view returns (uint256 power) { + uint48 timestamp = getEpochStart(epoch); + address[] memory vaults = activeVaults(operator); + uint160[] memory _subnetworks = activeSubnetworks(); + + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + for (uint256 j; j < _subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); + uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); + power += stakeToPower(vault, stake); + } + } + + return power; + } + + /* + * @notice Returns the total stake of multiple operators at a specific epoch. + * @param epoch The epoch to check. + * @param operators The list of operator addresses. + * @return The total stake of the operators. + */ + function _totalStake(uint32 epoch, address[] memory operators) internal view returns (uint256 stake) { + for (uint256 i; i < operators.length; ++i) { + uint256 operatorStake = getOperatorStake(epoch, operators[i]); + stake += operatorStake; + } + + return stake; + } + + /* + * @notice Returns the total power of multiple operators at a specific epoch. + * @param epoch The epoch to check. + * @param operators The list of operator addresses. + * @return The total power of the operators. + */ + function _totalPower(uint32 epoch, address[] memory operators) internal view returns (uint256 power) { + for (uint256 i; i < operators.length; ++i) { + uint256 operatorStake = getOperatorPower(epoch, operators[i]); + power += operatorStake; + } + + return power; + } + /* * @notice Registers a new shared vault. * @param vault The address of the vault to register. */ - function registerSharedVault(address vault) public virtual onlyOwner { + function _registerSharedVault(address vault) internal { _validateVault(vault); if (operatorVaultExists[vault] > 0) { revert VaultAlreadyRegistred(); @@ -134,7 +209,7 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @param vault The address of the vault to register. */ - function registerOperatorVault(address operator, address vault) public virtual onlyOwner { + function _registerOperatorVault(address operator, address vault) internal { _validateVault(vault); operatorVaultExists[vault]++; _operatorVaults[operator].register(getCurrentEpoch(), vault); @@ -144,7 +219,7 @@ abstract contract VaultManager is BaseMiddleware { * @notice Pauses a shared vault. * @param vault The address of the vault to pause. */ - function pauseSharedVault(address vault) public virtual onlyOwner { + function _pauseSharedVault(address vault) internal { _sharedVaults.pause(getCurrentEpoch(), vault); } @@ -152,7 +227,7 @@ abstract contract VaultManager is BaseMiddleware { * @notice Unpauses a shared vault. * @param vault The address of the vault to unpause. */ - function unpauseSharedVault(address vault) public virtual onlyOwner { + function _unpauseSharedVault(address vault) internal { _sharedVaults.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); } @@ -161,7 +236,7 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @param vault The address of the vault to pause. */ - function pauseOperatorVault(address operator, address vault) public virtual onlyOwner { + function _pauseOperatorVault(address operator, address vault) internal { _operatorVaults[operator].pause(getCurrentEpoch(), vault); } @@ -170,7 +245,7 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @param vault The address of the vault to unpause. */ - function unpauseOperatorVault(address operator, address vault) public virtual onlyOwner { + function _unpauseOperatorVault(address operator, address vault) internal { _operatorVaults[operator].unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); } @@ -178,7 +253,7 @@ abstract contract VaultManager is BaseMiddleware { * @notice Unregisters a shared vault. * @param vault The address of the vault to unregister. */ - function unregisterSharedVault(address vault) public virtual onlyOwner { + function _unregisterSharedVault(address vault) internal { _sharedVaults.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); } @@ -187,71 +262,11 @@ abstract contract VaultManager is BaseMiddleware { * @param operator The address of the operator. * @param vault The address of the vault to unregister. */ - function unregisterOperatorVault(address operator, address vault) public virtual onlyOwner { + function _unregisterOperatorVault(address operator, address vault) internal { _operatorVaults[operator].unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); operatorVaultExists[vault]--; } - /* - * @notice Returns the stake of an operator at a specific epoch. - * @param epoch The epoch to check. - * @param operator The address of the operator. - * @return The stake of the operator. - */ - function getOperatorStake(uint32 epoch, address operator) public view returns (uint256 stake) { - uint48 timestamp = getEpochStart(epoch); - address[] memory vaults = activeVaults(operator); - uint160[] memory _subnetworks = activeSubnetworks(); - - for (uint256 i; i < vaults.length; ++i) { - address vault = vaults[i]; - for (uint256 j; j < _subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); - stake += IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); - } - } - - return stake; - } - - /* - * @notice Returns the power of an operator at a specific epoch. - * @param epoch The epoch to check. - * @param operator The address of the operator. - * @return The power of the operator. - */ - function getOperatorPower(uint32 epoch, address operator) public view returns (uint256 power) { - uint48 timestamp = getEpochStart(epoch); - address[] memory vaults = activeVaults(operator); - uint160[] memory _subnetworks = activeSubnetworks(); - - for (uint256 i; i < vaults.length; ++i) { - address vault = vaults[i]; - for (uint256 j; j < _subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); - uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); - power += stakeToPower(vault, stake); - } - } - - return power; - } - - /* - * @notice Returns the total stake of multiple operators at a specific epoch. - * @param epoch The epoch to check. - * @param operators The list of operator addresses. - * @return The total stake of the operators. - */ - function _totalStake(uint32 epoch, address[] memory operators) internal view returns (uint256 stake) { - for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = getOperatorStake(epoch, operators[i]); - stake += operatorStake; - } - - return stake; - } - /* * @notice Slashes a vault based on provided conditions. * @param timestamp The timestamp when the slash occurs. @@ -299,10 +314,8 @@ abstract contract VaultManager is BaseMiddleware { * @param hints Additional data for the veto slasher. * @return The amount that was slashed. */ - function executeSlash(address vault, uint256 slashIndex, bytes calldata hints) - public - virtual - onlyOwner + function _executeSlash(address vault, uint256 slashIndex, bytes calldata hints) + internal returns (uint256 slashedAmount) { address slasher = IVault(vault).slasher(); diff --git a/src/VaultManagers/DefaultVaultManager.sol b/src/VaultManagers/DefaultVaultManager.sol new file mode 100644 index 0000000..76cb437 --- /dev/null +++ b/src/VaultManagers/DefaultVaultManager.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {BaseVaultManager} from "./BaseVaultManager.sol"; + +abstract contract DefaultVaultManager is BaseVaultManager { + /* + * @notice Registers a new shared vault. + * @param vault The address of the vault to register. + */ + function registerSharedVault(address vault) public virtual onlyOwner { + _registerSharedVault(vault); + } + + /* + * @notice Registers a new operator vault. + * @param operator The address of the operator. + * @param vault The address of the vault to register. + */ + function registerOperatorVault(address operator, address vault) public virtual onlyOwner { + _registerOperatorVault(operator, vault); + } + + /* + * @notice Pauses a shared vault. + * @param vault The address of the vault to pause. + */ + function pauseSharedVault(address vault) public virtual onlyOwner { + _pauseSharedVault(vault); + } + + /* + * @notice Unpauses a shared vault. + * @param vault The address of the vault to unpause. + */ + function unpauseSharedVault(address vault) public virtual onlyOwner { + _unpauseSharedVault(vault); + } + + /* + * @notice Pauses an operator vault. + * @param operator The address of the operator. + * @param vault The address of the vault to pause. + */ + function pauseOperatorVault(address operator, address vault) public virtual onlyOwner { + _pauseOperatorVault(operator, vault); + } + + /* + * @notice Unpauses an operator vault. + * @param operator The address of the operator. + * @param vault The address of the vault to unpause. + */ + function unpauseOperatorVault(address operator, address vault) public virtual onlyOwner { + _unpauseOperatorVault(operator, vault); + } + + /* + * @notice Unregisters a shared vault. + * @param vault The address of the vault to unregister. + */ + function unregisterSharedVault(address vault) public virtual onlyOwner { + _unregisterSharedVault(vault); + } + + /* + * @notice Unregisters an operator vault. + * @param operator The address of the operator. + * @param vault The address of the vault to unregister. + */ + function unregisterOperatorVault(address operator, address vault) public virtual onlyOwner { + _unregisterOperatorVault(operator, vault); + } + + /* + * @notice Executes a veto-based slash for a vault. + * @param vault The address of the vault. + * @param slashIndex The index of the slash to execute. + * @param hints Additional data for the veto slasher. + * @return The amount that was slashed. + */ + function executeSlash(address vault, uint256 slashIndex, bytes calldata hints) + public + virtual + onlyOwner + returns (uint256 slashedAmount) + { + return _executeSlash(vault, slashIndex, hints); + } +} diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index adc2822..d7ffd9f 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -9,12 +9,12 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {VaultManager} from "../../VaultManager.sol"; -import {OperatorManager} from "../../OperatorManager.sol"; -import {KeyManager} from "../../KeyManager.sol"; -import {BaseMiddleware} from "../..//BaseMiddleware.sol"; +import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {DefaultVaultManager} from "../../VaultManagers/DefaultVaultManager.sol"; +import {DefaultOperatorManager} from "../../OperatorManagers/DefaultOperatorManager.sol"; +import {DefaultKeyManager} from "../../KeyManagers/DefaultKeyManager.sol"; -contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { +contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager { using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key @@ -57,6 +57,15 @@ contract SimpleMiddleware is VaultManager, OperatorManager, KeyManager { return _totalStake(getCurrentEpoch(), operators); // Return the total stake for the current epoch } + /* + * @notice Returns the total power for the active operators in the current epoch. + * @return The total power amount. + */ + function getTotalPower() public view returns (uint256) { + address[] memory operators = activeOperators(); // Get the list of active operators + return _totalPower(getCurrentEpoch(), operators); // Return the total power for the current epoch + } + /* * @notice Returns the current validator set as an array of ValidatorData. * @return An array of ValidatorData containing the power and key of each validator. diff --git a/src/examples/simple-network/SqrtTaskMiddleware.sol b/src/examples/simple-network/SqrtTaskMiddleware.sol index 5874a89..3ba4037 100644 --- a/src/examples/simple-network/SqrtTaskMiddleware.sol +++ b/src/examples/simple-network/SqrtTaskMiddleware.sol @@ -10,12 +10,12 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {VaultManager} from "../../VaultManager.sol"; -import {OperatorManager} from "../../OperatorManager.sol"; -import {KeyManager} from "../../KeyManager.sol"; import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {DefaultVaultManager} from "../../VaultManagers/DefaultVaultManager.sol"; +import {DefaultOperatorManager} from "../../OperatorManagers/DefaultOperatorManager.sol"; +import {DefaultKeyManager} from "../../KeyManagers/DefaultKeyManager.sol"; -contract SqrtTaskMiddleware is VaultManager, OperatorManager, KeyManager, EIP712 { +contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager, EIP712 { using Subnetwork for address; using Math for uint256; From 2bb43f80491bbedafb6bebd1020506e1a5a2f81b Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Wed, 6 Nov 2024 06:07:38 +0700 Subject: [PATCH 024/115] refactor: tests adapt --- test/{SDK.t.sol => DefaultSDK.t.sol} | 7 +------ test/mocks/ExtendedSimpleMiddleware.sol | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) rename test/{SDK.t.sol => DefaultSDK.t.sol} (98%) diff --git a/test/SDK.t.sol b/test/DefaultSDK.t.sol similarity index 98% rename from test/SDK.t.sol rename to test/DefaultSDK.t.sol index 590c99e..afe0ee9 100644 --- a/test/SDK.t.sol +++ b/test/DefaultSDK.t.sol @@ -10,16 +10,11 @@ import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseMiddleware} from "../src/BaseMiddleware.sol"; -import {PauseableEnumerableSet} from "../src/libraries/PauseableEnumerableSet.sol"; -import {KeyManager} from "../src/KeyManager.sol"; -import {OperatorManager} from "../src/OperatorManager.sol"; -import {VaultManager} from "../src/VaultManager.sol"; import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -contract SDKTest is POCBaseTest { +contract DefaultSDKTest is POCBaseTest { using Subnetwork for bytes32; using Subnetwork for address; using Math for uint256; diff --git a/test/mocks/ExtendedSimpleMiddleware.sol b/test/mocks/ExtendedSimpleMiddleware.sol index 03e5cdd..71359dc 100644 --- a/test/mocks/ExtendedSimpleMiddleware.sol +++ b/test/mocks/ExtendedSimpleMiddleware.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.25; import {SimpleMiddleware} from "../../src/examples/simple-network/SimpleMiddleware.sol"; -import {BLSKeyManager} from "../../src/BLSKeyManager.sol"; +import {DefaultBLSKeyManager} from "../../src/KeyManagers/DefaultBLSKeyManager.sol"; -contract ExtendedSimpleMiddleware is SimpleMiddleware, BLSKeyManager { +contract ExtendedSimpleMiddleware is SimpleMiddleware, DefaultBLSKeyManager { constructor( address network, address operatorRegistry, From ef7127232abebc3cd50854464cc9e23de3d56597 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 7 Nov 2024 02:11:34 +0700 Subject: [PATCH 025/115] refactor: add checks, make virtual some public functions and add was active at --- src/{BaseMiddleware.sol => BaseManager.sol} | 32 ++++++---- src/KeyManagers/BaseBLSKeyManager.sol | 8 +-- src/KeyManagers/BaseKeyManager.sol | 6 +- src/OperatorManagers/BaseOperatorManager.sol | 14 ++++- src/VaultManagers/BaseVaultManager.sol | 60 ++++++++++++------- .../simple-network/SimpleMiddleware.sol | 22 +++---- .../simple-network/SqrtTaskMiddleware.sol | 18 +++--- src/libraries/PauseableEnumerableSet.sol | 24 ++++++++ test/DefaultSDK.t.sol | 1 - 9 files changed, 123 insertions(+), 62 deletions(-) rename src/{BaseMiddleware.sol => BaseManager.sol} (81%) diff --git a/src/BaseMiddleware.sol b/src/BaseManager.sol similarity index 81% rename from src/BaseMiddleware.sol rename to src/BaseManager.sol index 27b1989..0b8ed9e 100644 --- a/src/BaseMiddleware.sol +++ b/src/BaseManager.sol @@ -6,7 +6,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; -abstract contract BaseMiddleware is Ownable { +abstract contract BaseManager is Ownable { using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; error SlashingWindowTooShort(); // Error thrown when the slashing window is lower than epoch @@ -24,10 +24,10 @@ abstract contract BaseMiddleware is Ownable { uint64 public constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type uint96 public constant DEFAULT_SUBNETWORK = 0; // Default subnetwork identifier - PauseableEnumerableSet.Uint160Set subnetworks; // Set of active subnetworks + PauseableEnumerableSet.Uint160Set _subnetworks; // Set of active subnetworks /* - * @notice Constructor for initializing the BaseMiddleware contract. + * @notice Constructor for initializing the BaseManager contract. * @param owner The address of the contract owner. * @param network The address of the network. * @param epochDuration The duration of each epoch. @@ -57,7 +57,7 @@ abstract contract BaseMiddleware is Ownable { OPERATOR_REGISTRY = operatorRegistry; OPERATOR_NET_OPTIN = operatorNetOptIn; - subnetworks.register(getCurrentEpoch(), uint160(DEFAULT_SUBNETWORK)); // Register default subnetwork + _subnetworks.register(getCurrentEpoch(), uint160(DEFAULT_SUBNETWORK)); // Register default subnetwork } /* @@ -99,7 +99,7 @@ abstract contract BaseMiddleware is Ownable { * @return The count of registered subnetworks. */ function subnetworksLength() public view returns (uint256) { - return subnetworks.length(); + return _subnetworks.length(); } /* @@ -108,7 +108,7 @@ abstract contract BaseMiddleware is Ownable { * @return The subnetwork details including address, enabled epoch, disabled epoch and enabled before disabled epoch. */ function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint32, uint32, uint32) { - return subnetworks.at(pos); + return _subnetworks.at(pos); } /* @@ -116,7 +116,17 @@ abstract contract BaseMiddleware is Ownable { * @return An array of active subnetwork addresses. */ function activeSubnetworks() public view returns (uint160[] memory) { - return subnetworks.getActive(getCurrentEpoch()); + return _subnetworks.getActive(getCurrentEpoch()); + } + + /* + * @notice Checks if a given subnetwork was active at a specified epoch. + * @param epoch The epoch to check. + * @param subnetwork The subnetwork to check. + * @return A boolean indicating whether the subnetwork was active at the specified epoch. + */ + function subnetworkWasActiveAt(uint32 epoch, uint96 subnetwork) public view returns (bool) { + return _subnetworks.wasActiveAt(epoch, uint160(subnetwork)); } /* @@ -124,7 +134,7 @@ abstract contract BaseMiddleware is Ownable { * @param subnetwork The identifier of the subnetwork to register. */ function registerSubnetwork(uint96 subnetwork) public virtual onlyOwner { - subnetworks.register(getCurrentEpoch(), uint160(subnetwork)); + _subnetworks.register(getCurrentEpoch(), uint160(subnetwork)); } /* @@ -132,7 +142,7 @@ abstract contract BaseMiddleware is Ownable { * @param subnetwork The identifier of the subnetwork to pause. */ function pauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { - subnetworks.pause(getCurrentEpoch(), uint160(subnetwork)); + _subnetworks.pause(getCurrentEpoch(), uint160(subnetwork)); } /* @@ -140,7 +150,7 @@ abstract contract BaseMiddleware is Ownable { * @param subnetwork The identifier of the subnetwork to unpause. */ function unpauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { - subnetworks.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); + _subnetworks.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); } /* @@ -148,6 +158,6 @@ abstract contract BaseMiddleware is Ownable { * @param subnetwork The identifier of the subnetwork to unregister. */ function unregisterSubnetwork(uint96 subnetwork) public virtual onlyOwner { - subnetworks.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); + _subnetworks.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); } } diff --git a/src/KeyManagers/BaseBLSKeyManager.sol b/src/KeyManagers/BaseBLSKeyManager.sol index cef86f4..e17f77d 100644 --- a/src/KeyManagers/BaseBLSKeyManager.sol +++ b/src/KeyManagers/BaseBLSKeyManager.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BaseBLSKeyManager is BaseMiddleware { +abstract contract BaseBLSKeyManager is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateBLSKey(); @@ -42,9 +42,9 @@ abstract contract BaseBLSKeyManager is BaseMiddleware { /* * @notice Checks if a given BLS key was active at a specified epoch. - * @param epoch The epoch to check for key activity. + * @param epoch The epoch to check. * @param key The BLS key to check. - * @return A boolean indicating whether the BLS key was active at the specified epoch. + * @return A boolean indicating whether the BLS key was active at the specifed epoch. */ function blsKeyWasActiveAt(uint32 epoch, bytes memory key) public view returns (bool) { return _blsKeyData[key].wasActiveAt(epoch); diff --git a/src/KeyManagers/BaseKeyManager.sol b/src/KeyManagers/BaseKeyManager.sol index 4157ed6..a14121e 100644 --- a/src/KeyManagers/BaseKeyManager.sol +++ b/src/KeyManagers/BaseKeyManager.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BaseKeyManager is BaseMiddleware { +abstract contract BaseKeyManager is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateKey(); @@ -42,7 +42,7 @@ abstract contract BaseKeyManager is BaseMiddleware { /* * @notice Checks if a given key was active at a specified epoch. - * @param epoch The epoch to check for key activity. + * @param epoch The epoch to check. * @param key The key to check. * @return A boolean indicating whether the key was active at the specified epoch. */ diff --git a/src/OperatorManagers/BaseOperatorManager.sol b/src/OperatorManagers/BaseOperatorManager.sol index 82ee2d1..18c464b 100644 --- a/src/OperatorManagers/BaseOperatorManager.sol +++ b/src/OperatorManagers/BaseOperatorManager.sol @@ -8,10 +8,10 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BaseOperatorManager is BaseMiddleware { +abstract contract BaseOperatorManager is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; error NotOperator(); @@ -46,6 +46,16 @@ abstract contract BaseOperatorManager is BaseMiddleware { return _operators.getActive(getCurrentEpoch()); } + /* + * @notice Checks if a given operator was active at a specified epoch. + * @param epoch The epoch to check. + * @param operator The operator to check. + * @return A boolean indicating whether the operator was active at the specified epoch. + */ + function operatorWasActiveAt(uint32 epoch, address operator) public view returns (bool) { + return _operators.wasActiveAt(epoch, operator); + } + /* * @notice Registers a new operator. * @param operator The address of the operator to register. diff --git a/src/VaultManagers/BaseVaultManager.sol b/src/VaultManagers/BaseVaultManager.sol index 2f65320..f209051 100644 --- a/src/VaultManagers/BaseVaultManager.sol +++ b/src/VaultManagers/BaseVaultManager.sol @@ -13,10 +13,10 @@ import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BaseVaultManager is BaseMiddleware { +abstract contract BaseVaultManager is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; using Subnetwork for address; @@ -100,7 +100,7 @@ abstract contract BaseVaultManager is BaseMiddleware { * @param operator The address of the operator. * @return An array of addresses representing the active vaults. */ - function activeVaults(address operator) public view returns (address[] memory) { + function activeVaults(address operator) public view virtual returns (address[] memory) { uint32 epoch = getCurrentEpoch(); address[] memory activeSharedVaults = _sharedVaults.getActive(epoch); address[] memory activeOperatorVaults = _operatorVaults[operator].getActive(epoch); @@ -118,13 +118,33 @@ abstract contract BaseVaultManager is BaseMiddleware { } /* - * @notice Returns the stake of an operator at a specific epoch. + * @notice Checks if a given shared vault was active at a specified epoch. * @param epoch The epoch to check. + * @param vault The vault to check. + * @return A boolean indicating whether the shared vault was active at the specified epoch. + */ + function sharedVaultWasActiveAt(uint32 epoch, address vault) public view returns (bool) { + return _sharedVaults.wasActiveAt(epoch, vault); + } + + /* + * @notice Checks if a given shared vault was active at a specified epoch. + * @param epoch The epoch to check. + * @param operator The address of operator. + * @param vault The vault to check. + * @return A boolean indicating whether the shared vault was active at the specified epoch. + */ + function operatorVaultWasActiveAt(uint32 epoch, address operator, address vault) public view returns (bool) { + return _operatorVaults[operator].wasActiveAt(epoch, vault); + } + + /* + * @notice Returns the stake of an operator at a current epoch. * @param operator The address of the operator. * @return The stake of the operator. */ - function getOperatorStake(uint32 epoch, address operator) public view returns (uint256 stake) { - uint48 timestamp = getEpochStart(epoch); + function getOperatorStake(address operator) public view virtual returns (uint256 stake) { + uint48 timestamp = getCurrentEpochStart(); address[] memory vaults = activeVaults(operator); uint160[] memory _subnetworks = activeSubnetworks(); @@ -140,13 +160,12 @@ abstract contract BaseVaultManager is BaseMiddleware { } /* - * @notice Returns the power of an operator at a specific epoch. - * @param epoch The epoch to check. + * @notice Returns the power of an operator at a current epoch. * @param operator The address of the operator. * @return The power of the operator. */ - function getOperatorPower(uint32 epoch, address operator) public view returns (uint256 power) { - uint48 timestamp = getEpochStart(epoch); + function getOperatorPower(address operator) public view virtual returns (uint256 power) { + uint48 timestamp = getCurrentEpochStart(); address[] memory vaults = activeVaults(operator); uint160[] memory _subnetworks = activeSubnetworks(); @@ -163,14 +182,13 @@ abstract contract BaseVaultManager is BaseMiddleware { } /* - * @notice Returns the total stake of multiple operators at a specific epoch. - * @param epoch The epoch to check. + * @notice Returns the total stake of multiple operators at a current epoch. * @param operators The list of operator addresses. * @return The total stake of the operators. */ - function _totalStake(uint32 epoch, address[] memory operators) internal view returns (uint256 stake) { + function _totalStake(address[] memory operators) internal view returns (uint256 stake) { for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = getOperatorStake(epoch, operators[i]); + uint256 operatorStake = getOperatorStake(operators[i]); stake += operatorStake; } @@ -178,14 +196,13 @@ abstract contract BaseVaultManager is BaseMiddleware { } /* - * @notice Returns the total power of multiple operators at a specific epoch. - * @param epoch The epoch to check. + * @notice Returns the total power of multiple operators at a current epoch. * @param operators The list of operator addresses. * @return The total power of the operators. */ - function _totalPower(uint32 epoch, address[] memory operators) internal view returns (uint256 power) { + function _totalPower(address[] memory operators) internal view returns (uint256 power) { for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = getOperatorPower(epoch, operators[i]); + uint256 operatorStake = getOperatorPower(operators[i]); power += operatorStake; } @@ -211,6 +228,9 @@ abstract contract BaseVaultManager is BaseMiddleware { */ function _registerOperatorVault(address operator, address vault) internal { _validateVault(vault); + if (_sharedVaults.contains(vault)) { + revert VaultAlreadyRegistred(); + } operatorVaultExists[vault]++; _operatorVaults[operator].register(getCurrentEpoch(), vault); } @@ -336,10 +356,6 @@ abstract contract BaseVaultManager is BaseMiddleware { revert NotVault(); } - if (_sharedVaults.contains(vault)) { - revert VaultAlreadyRegistred(); - } - if (!IVault(vault).isInitialized()) { revert VaultNotInitialized(); } diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/simple-network/SimpleMiddleware.sol index d7ffd9f..f499f31 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/simple-network/SimpleMiddleware.sol @@ -9,7 +9,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {BaseManager} from "../../BaseManager.sol"; import {DefaultVaultManager} from "../../VaultManagers/DefaultVaultManager.sol"; import {DefaultOperatorManager} from "../../OperatorManagers/DefaultOperatorManager.sol"; import {DefaultKeyManager} from "../../KeyManagers/DefaultKeyManager.sol"; @@ -18,6 +18,7 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key + error InactiveOperatorSlash(); // Error thrown when trying to slash an inactive operator error NotExistKeySlash(); // Error thrown when the key does not exist for slashing error InvalidHints(); // Error thrown for invalid hints provided @@ -44,9 +45,7 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul address owner, uint48 epochDuration, uint48 slashingWindow - ) - BaseMiddleware(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) - {} + ) BaseManager(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) {} /* * @notice Returns the total stake for the active operators in the current epoch. @@ -54,7 +53,7 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul */ function getTotalStake() public view returns (uint256) { address[] memory operators = activeOperators(); // Get the list of active operators - return _totalStake(getCurrentEpoch(), operators); // Return the total stake for the current epoch + return _totalStake(operators); // Return the total stake for the current epoch } /* @@ -63,7 +62,7 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul */ function getTotalPower() public view returns (uint256) { address[] memory operators = activeOperators(); // Get the list of active operators - return _totalPower(getCurrentEpoch(), operators); // Return the total power for the current epoch + return _totalPower(operators); // Return the total power for the current epoch } /* @@ -71,7 +70,6 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul * @return An array of ValidatorData containing the power and key of each validator. */ function getValidatorSet() public view returns (ValidatorData[] memory validatorSet) { - uint32 epoch = getCurrentEpoch(); // Get the current epoch address[] memory operators = activeOperators(); // Get the list of active operators validatorSet = new ValidatorData[](operators.length); // Initialize the validator set uint256 len = 0; // Length counter @@ -80,11 +78,11 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul address operator = operators[i]; // Get the operator address bytes32 key = operatorKey(operator); // Get the key for the operator - if (key == bytes32(0) || !keyWasActiveAt(epoch, key)) { + if (key == bytes32(0) || !keyWasActiveAt(getCurrentEpoch(), key)) { continue; // Skip if the key is inactive } - uint256 power = getOperatorPower(epoch, operator); // Get the operator's power + uint256 power = getOperatorPower(operator); // Get the operator's power validatorSet[len++] = ValidatorData(power, key); // Store the validator data } @@ -121,7 +119,11 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul revert InactiveKeySlash(); // Revert if the key is inactive } - uint256 totalStake = getOperatorStake(epoch, operator); // Get the total stake for the operator + if (!operatorWasActiveAt(epoch, operator)) { + revert InactiveOperatorSlash(); // Revert if the operator wasn't active + } + + uint256 totalStake = getOperatorStake(operator); // Get the total stake for the operator address[] memory vaults = activeVaults(operator); // Get active vaults for the operator uint160[] memory _subnetworks = activeSubnetworks(); // Get active subnetworks diff --git a/src/examples/simple-network/SqrtTaskMiddleware.sol b/src/examples/simple-network/SqrtTaskMiddleware.sol index 3ba4037..eed4387 100644 --- a/src/examples/simple-network/SqrtTaskMiddleware.sol +++ b/src/examples/simple-network/SqrtTaskMiddleware.sol @@ -10,7 +10,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {BaseManager} from "../../BaseManager.sol"; import {DefaultVaultManager} from "../../VaultManagers/DefaultVaultManager.sol"; import {DefaultOperatorManager} from "../../OperatorManagers/DefaultOperatorManager.sol"; import {DefaultKeyManager} from "../../KeyManagers/DefaultKeyManager.sol"; @@ -46,7 +46,7 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa uint48 epochDuration, uint48 slashingWindow ) - BaseMiddleware(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) + BaseManager(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) EIP712("SqrtTaskMiddleware", "1") {} @@ -75,7 +75,7 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa emit CompleteTask(taskIndex, isValidAnswer); } - function _verify(uint256 taskIndex, uint256 answer, bytes calldata signature) private returns (bool) { + function _verify(uint256 taskIndex, uint256 answer, bytes calldata signature) private view returns (bool) { if (tasks[taskIndex].completed) { revert TaskCompleted(); } @@ -83,7 +83,7 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa return _verifyAnswer(taskIndex, answer); } - function _verifySignature(uint256 taskIndex, uint256 answer, bytes calldata signature) private { + function _verifySignature(uint256 taskIndex, uint256 answer, bytes calldata signature) private view { Task storage task = tasks[taskIndex]; bytes32 hash_ = _hashTypedDataV4(keccak256(abi.encode(COMPLETE_TASK_TYPEHASH, taskIndex, answer))); @@ -93,7 +93,7 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa } } - function _verifyAnswer(uint256 taskIndex, uint256 answer) private returns (bool) { + function _verifyAnswer(uint256 taskIndex, uint256 answer) private view returns (bool) { uint256 value = tasks[taskIndex].value; uint256 square = answer ** 2; if (square == value) { @@ -145,28 +145,28 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa /* * inheritdoc BaseMiddleware */ - function registerSubnetwork(uint96 subnetwork) public override { + function registerSubnetwork(uint96 subnetwork) public pure override { revert(); } /* * inheritdoc BaseMiddleware */ - function pauseSubnetwork(uint96 subnetwork) public override { + function pauseSubnetwork(uint96 subnetwork) public pure override { revert(); } /* * inheritdoc BaseMiddleware */ - function unpauseSubnetwork(uint96 subnetwork) public override { + function unpauseSubnetwork(uint96 subnetwork) public pure override { revert(); } /* * inheritdoc BaseMiddleware */ - function unregisterSubnetwork(uint96 subnetwork) public override { + function unregisterSubnetwork(uint96 subnetwork) public pure override { revert(); } } diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 0e322e4..064169a 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -74,6 +74,16 @@ library PauseableEnumerableSet { return array; } + /* + * @notice Checks if a given addr was active at a specified epoch. + * @param epoch The epoch to check. + * @param addr The address to check. + * @return A boolean indicating whether the addr was active at the specified epoch. + */ + function wasActiveAt(AddressSet storage self, uint32 epoch, address addr) internal view returns (bool) { + return self.set.wasActiveAt(epoch, uint160(addr)); + } + /* * @notice Registers a new address at a given epoch. * @param self The AddressSet storage. @@ -168,6 +178,20 @@ library PauseableEnumerableSet { return array; } + /* + * @notice Checks if a given value was active at a specified epoch. + * @param epoch The epoch to check. + * @param value The value to check. + * @return A boolean indicating whether the value was active at the specified epoch. + */ + function wasActiveAt(Uint160Set storage self, uint32 epoch, uint160 value) internal view returns (bool) { + if (self.positions[value] == 0) { + return false; + } + + return self.array[self.positions[value] - 1].wasActiveAt(epoch); + } + /* * @notice Registers a new Uint160 value at a given epoch. * @param self The Uint160Set storage. diff --git a/test/DefaultSDK.t.sol b/test/DefaultSDK.t.sol index afe0ee9..83c136e 100644 --- a/test/DefaultSDK.t.sol +++ b/test/DefaultSDK.t.sol @@ -20,7 +20,6 @@ contract DefaultSDKTest is POCBaseTest { using Math for uint256; address network = address(0x123); - address notOwner = address(0xdead); ExtendedSimpleMiddleware internal middleware; From 97e49524e624183e0ba78acb86f4a34f15591a05 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 7 Nov 2024 02:19:11 +0700 Subject: [PATCH 026/115] fix: vault inactive slash check --- src/VaultManagers/BaseVaultManager.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/VaultManagers/BaseVaultManager.sol b/src/VaultManagers/BaseVaultManager.sol index f209051..953ca66 100644 --- a/src/VaultManagers/BaseVaultManager.sol +++ b/src/VaultManagers/BaseVaultManager.sol @@ -30,6 +30,7 @@ abstract contract BaseVaultManager is BaseManager { error TooOldEpoch(); error InvalidEpoch(); error InvalidSubnetworksCnt(); + error InactiveVaultSlash(); error UnknownSlasherType(); error NonVetoSlasher(); error TooOldTimestampSlash(); @@ -117,6 +118,17 @@ abstract contract BaseVaultManager is BaseManager { return vaults; } + /* + * @notice Checks if a given vault was active at a specified epoch. + * @param epoch The epoch to check. + * @param operator The address of operator. + * @param vault The vault to check. + * @return A boolean indicating whether the vault was active at the specified epoch. + */ + function vaultWasActiveAt(uint32 epoch, address operator, address vault) public view returns (bool) { + return sharedVaultWasActiveAt(epoch, vault) || operatorVaultWasActiveAt(epoch, operator, vault); + } + /* * @notice Checks if a given shared vault was active at a specified epoch. * @param epoch The epoch to check. @@ -309,6 +321,10 @@ abstract contract BaseVaultManager is BaseManager { revert NotOperatorVault(); } + if (!vaultWasActiveAt(getEpochAt(timestamp), operator, vault)) { + revert InactiveVaultSlash(); + } + if (timestamp + SLASHING_WINDOW < Time.timestamp()) { revert TooOldTimestampSlash(); } From 6b85333acabf74692a9d2ff3f87a7271216b85eb Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 7 Nov 2024 04:01:17 +0700 Subject: [PATCH 027/115] refactor: readme and arch img --- README.md | 387 +++++++++++++++++++++++++++++++++++++++----------- imgs/arch.jpg | Bin 0 -> 141050 bytes 2 files changed, 301 insertions(+), 86 deletions(-) create mode 100644 imgs/arch.jpg diff --git a/README.md b/README.md index f0df12b..254da94 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Middleware SDK -The Symbiotic Middleware SDK is a collection of basic contracts and libraries that handle common tasks such as operator management, key handling, vault interactions, subnetwork operations, and slashing mechanisms. It enables developers to easily create network middleware for the Symbiotic platform. +The Symbiotic Middleware SDK is a collection of foundational contracts and libraries designed to simplify the development of network middleware on the Symbiotic platform. It provides essential functionalities such as operator management, key handling, vault interactions, subnetwork operations, and slashing mechanisms. This SDK enables developers to create custom middleware solutions tailored to their network's requirements with ease. ## Table of Contents @@ -10,33 +10,43 @@ The Symbiotic Middleware SDK is a collection of basic contracts and libraries th - [Libraries](#libraries) - [PauseableEnumerableSet Library](#pauseableenumerableset-library) - [Contracts](#contracts) - - [BaseMiddleware Contract](#basemiddleware-contract) - - [OperatorManager Contract](#operatormanager-contract) - - [VaultManager Contract](#vaultmanager-contract) - - [KeyManager Contract](#keymanager-contract) - - [BLSKeyManager Contract](#blskeymanager-contract) + - [BaseManager Contract](#basemanager-contract) + - [Operator Management Modules](#operator-management-modules) + - [Vault Management Modules](#vault-management-modules) + - [Key Management Modules](#key-management-modules) + - [Subnetwork Management](#subnetwork-management) - [Examples](#examples) - [SimpleMiddleware Example](#simplemiddleware-example) - [SqrtTaskMiddleware Example](#sqrttaskmiddleware-example) + - [Writing Your Own Network Middleware](#writing-your-own-network-middleware) + - [Custom Modules](#custom-modules) + - [Example: Self-Register Operator Manager](#example-self-register-operator-manager) +- [Important Notes](#important-notes) - [License](#license) ## Features - **Operator Management**: Register, pause, unpause, and manage operators within the network. - **Vault Management**: Interact with vaults for staking, power calculation, and slashing mechanisms. -- **Key Management**: Handle operator keys (both standard and BLS keys), including updates and activity checks. +- **Key Management**: Handle operator keys (both general and BLS keys), including updates and activity checks. - **Subnetwork Support**: Manage multiple subnetworks within the main network, including registration and pausing. - **Epoch Management**: Utilities for handling epochs, including start times and durations. - **Slashing Mechanisms**: Implement instant and veto-based slashing for misbehaving operators. +- **Extensibility**: Ability to write custom modules to extend or modify the SDK's functionalities. - **Example Implementations**: Includes example contracts demonstrating how to extend and use the SDK. ## Architecture -The SDK is organized into the following components: +The SDK is organized into modular components, each responsible for managing a specific aspect of the middleware: -- **Libraries**: Reusable code segments like `PauseableEnumerableSet` for managing enumerable sets with pause functionality. -- **SDK Contracts**: Core contracts such as `BaseMiddleware`, `OperatorManager`, `VaultManager`, and key managers that provide essential middleware functionalities. +- **Libraries**: Reusable code segments such as `PauseableEnumerableSet` for managing enumerable sets with pause functionality. +- **Contracts**: Core contracts like `BaseManager`, `OperatorManager`, `VaultManager`, and key managers that provide essential middleware functionalities. - **Examples**: Sample implementations like `SimpleMiddleware` and `SqrtTaskMiddleware` to illustrate how to build upon the SDK. +- **Custom Modules**: The SDK allows for the creation of custom modules to extend or override default behaviors, enabling developers to tailor the middleware to their specific needs. + +Developers can inherit from these modules and extend them to implement custom logic tailored to their specific requirements. + +![arch](imgs/arch.jpg) ## Usage @@ -51,159 +61,364 @@ The `PauseableEnumerableSet` library extends the functionality of enumerable set - Manage sets of `address` or `uint160` values. - Pause and unpause individual elements. - Track enabled and disabled epochs for each element. -- Prevent operations on paused elements. +- Prevent operations on paused elements until they are unpaused. +- Ensure data consistency by enforcing immutable periods before certain actions can be reversed. -### SDK Contracts +### Contracts -#### BaseMiddleware Contract +#### BaseManager Contract -The `BaseMiddleware` contract is an abstract base contract that provides foundational middleware functionalities, including epoch management, subnetwork handling, and immutable period configurations. +The `BaseManager` contract is an abstract base contract that provides foundational middleware functionalities, including epoch management, subnetwork handling, and immutable period configurations. **Key Features:** - **Epoch Management**: Calculate current epoch, epoch start times, and manage epoch durations. - **Subnetwork Management**: Register, pause, unpause, and unregister subnetworks. -- **Immutable Epochs**: Enforce immutable periods before certain actions can be performed. +- **Immutable Epochs**: Enforce immutable periods before certain actions (like unpausing) can be performed. +- **Slashing Window Configuration**: Set slashing windows relative to epoch durations to ensure timely slashing actions. **Key Functions:** -- `getCurrentEpoch()`: Returns the current epoch based on the timestamp. -- `getEpochStart(uint48 epoch)`: Returns the start timestamp of a given epoch. +- `getCurrentEpoch() → uint32`: Returns the current epoch based on the timestamp. +- `getEpochStart(uint32 epoch) → uint48`: Returns the start timestamp of a given epoch. - `registerSubnetwork(uint96 subnetwork)`: Registers a new subnetwork. -- `pauseSubnetwork(uint96 subnetwork)`: Pauses a subnetwork. +- `pauseSubnetwork(uint96 subnetwork)`: Pauses an active subnetwork. - `unpauseSubnetwork(uint96 subnetwork)`: Unpauses a subnetwork after the immutable period. - `unregisterSubnetwork(uint96 subnetwork)`: Unregisters a subnetwork. +- `subnetworksLength() → uint256`: Returns the number of registered subnetworks. +- `activeSubnetworks() → uint160[]`: Returns a list of active subnetworks. -#### OperatorManager Contract +#### Operator Management Modules -The `OperatorManager` contract manages operators within the network. It allows for registering, pausing, unpausing, and unregistering operators. +The SDK provides modules for managing operators within the network. -**Key Features:** - -- **Operator Registration**: Register new operators who are part of the network. -- **Operator State Management**: Pause and unpause operators. -- **Operator Activity Checks**: Retrieve active operators for the current epoch. +- **`BaseOperatorManager` Contract**: Provides internal functions for operator management, including registration, pausing, unpausing, and unregistration. +- **`DefaultOperatorManager` Contract**: Inherits from `BaseOperatorManager` and exposes public functions callable by the contract owner. **Key Functions:** -- `registerOperator(address operator)`: Registers a new operator. Requires that the operator is a valid entity in the operator registry and has opted into the network. +- `registerOperator(address operator)`: Registers a new operator. - `pauseOperator(address operator)`: Pauses an operator, making them inactive. -- `unpauseOperator(address operator)`: Unpauses an operator after the immutable period has passed. -- `unregisterOperator(address operator)`: Unregisters an operator from the middleware. -- `activeOperators()`: Returns a list of active operators for the current epoch. -- `operatorsLength()`: Returns the total number of registered operators. - -#### VaultManager Contract +- `unpauseOperator(address operator)`: Unpauses a paused operator after the immutable period. +- `unregisterOperator(address operator)`: Unregisters an operator from the network. +- `operatorsLength() → uint256`: Returns the total number of registered operators. +- `operatorWithTimesAt(uint256 pos) → (address, uint32, uint32, uint32)`: Retrieves operator details at a specific index. +- `activeOperators() → address[]`: Returns a list of active operators. -The `VaultManager` contract handles interactions with vaults, including registering vaults, calculating operator stakes and power, and implementing slashing mechanisms. +#### Vault Management Modules -**Key Features:** +The SDK includes modules for managing vaults, which are critical for staking and slashing operations. -- **Vault Registration**: Register shared and operator-specific vaults. -- **Stake and Power Calculation**: Calculate the stake and power of operators at specific epochs. -- **Slashing Mechanisms**: Implement slashing logic for misbehaving operators. +- **`BaseVaultManager` Contract**: Provides core functionalities for vault management, including stake calculation and slashing mechanisms. +- **`DefaultVaultManager` Contract**: Extends `BaseVaultManager` to include public methods for managing vaults. **Key Functions:** -- `registerSharedVault(address vault)`: Registers a shared vault accessible by all operators. -- `registerOperatorVault(address vault, address operator)`: Registers a vault specific to an operator. +- `registerSharedVault(address vault)`: Registers a new shared vault accessible by all operators. +- `registerOperatorVault(address operator, address vault)`: Registers a new operator-specific vault. - `pauseSharedVault(address vault)`: Pauses a shared vault. - `unpauseSharedVault(address vault)`: Unpauses a shared vault after the immutable period. -- `pauseOperatorVault(address operator, address vault)`: Pauses an operator's vault. -- `unpauseOperatorVault(address operator, address vault)`: Unpauses an operator's vault after the immutable period. -- `getOperatorStake(uint48 epoch, address operator)`: Retrieves the total stake of an operator at a specific epoch. -- `getOperatorPower(uint48 epoch, address operator)`: Calculates the power of an operator based on their stake. This can be overridden to implement custom stake-to-power logic. - -**Implementing `stakeToPower`:** +- `pauseOperatorVault(address operator, address vault)`: Pauses an operator-specific vault. +- `unpauseOperatorVault(address operator, address vault)`: Unpauses an operator vault after the immutable period. +- `unregisterSharedVault(address vault)`: Unregisters a shared vault. +- `unregisterOperatorVault(address operator, address vault)`: Unregisters an operator-specific vault. +- `sharedVaultsLength() → uint256`: Returns the number of shared vaults. +- `sharedVaultWithEpochsAt(uint256 pos) → (address, uint32, uint32, uint32)`: Retrieves shared vault details at a specific index. +- `operatorVaultsLength(address operator) → uint256`: Returns the number of vaults for a specific operator. +- `operatorVaultWithEpochsAt(address operator, uint256 pos) → (address, uint32, uint32, uint32)`: Retrieves operator vault details at a specific index. +- `getOperatorStake(address operator) → uint256`: Returns the stake of an operator in the current epoch. +- `getOperatorPower(address operator) → uint256`: Returns the power of an operator based on their stake. This function can be overridden to implement custom stake-to-power logic. + +**Implementing Custom `stakeToPower` Logic:** ```solidity function stakeToPower(address vault, uint256 stake) public view virtual override returns (uint256) { - // Custom logic to convert stake to power - return stake; // Simple 1:1 mapping in this example + // Implement custom logic to convert stake to power + // For example, use an oracle to price different vault assets or weight vaults differently + uint256 assetPrice = getAssetPriceFromOracle(vault); + return stake * assetPrice; } ``` -#### KeyManager Contract +**Explanation:** -The `KeyManager` contract manages operator keys, including updating keys, retrieving current and previous keys, and checking if keys were active at specific epochs. +The `stakeToPower` function is critical for converting an operator's stake into power, which can influence their weight in consensus mechanisms or voting. By overriding this function, you can implement custom logic to: -**Key Features:** +- **Price Different Vault Assets Using an Oracle**: If your operators stake assets with varying market values, you can integrate with an oracle to fetch real-time prices. This ensures that the power assigned to an operator accurately reflects the current value of their staked assets. -- **Key Updates**: Update the keys associated with operators. -- **Key Retrieval**: Get the current or previous key of an operator. -- **Key Activity Checks**: Determine if a key was active during a particular epoch. +- **Weight Vaults Differently**: You may assign different weights to vaults based on criteria such as asset volatility, liquidity, or strategic importance. By adjusting the power calculation, you can incentivize operators to stake in preferred vaults or balance the network according to your needs. -**Key Functions:** +This flexibility allows you to create a more nuanced and fair system that reflects the true value of stakes from different vaults. -- `updateKey(address operator, bytes32 key)`: Updates the key associated with an operator. Throws an error if the key is already in use by another operator. -- `operatorKey(address operator)`: Retrieves the current key of an operator. If the key was updated in the current epoch, it returns the previous key. -- `operatorByKey(bytes32 key)`: Returns the operator associated with a specific key. -- `keyWasActiveAt(uint48 epoch, bytes32 key)`: Checks if a key was active during a specific epoch. +#### Key Management Modules -#### BLSKeyManager Contract +Key management is essential for operator authentication and validation. The SDK provides modules for managing both general keys and BLS keys. -The `BLSKeyManager` is similar to `KeyManager` but specifically handles BLS (Boneh-Lynn-Shacham) keys, which are often used in threshold signature schemes. +- **`BaseKeyManager` Contract**: Manages general keys associated with operators. +- **`DefaultKeyManager` Contract**: Extends `BaseKeyManager` to expose public methods for key management. +- **`BaseBLSKeyManager` Contract**: Specifically manages BLS (Boneh–Lynn–Shacham) cryptographic keys. +- **`DefaultBLSKeyManager` Contract**: Extends `BaseBLSKeyManager` to expose public methods. -**Key Features:** +**Key Functions (General Key Management):** -- **BLS Key Management**: Update and retrieve BLS keys for operators. -- **Key Activity Checks**: Check if a BLS key was active during a specific epoch. +- `updateKey(address operator, bytes32 key)`: Updates the key associated with an operator. +- `operatorKey(address operator) → bytes32`: Retrieves the current key of an operator. +- `operatorByKey(bytes32 key) → address`: Returns the operator associated with a specific key. +- `keyWasActiveAt(uint32 epoch, bytes32 key) → bool`: Checks if a key was active during a specific epoch. + +**Key Functions (BLS Key Management):** + +- `updateBLSKey(address operator, bytes memory key)`: Updates the BLS key associated with an operator. +- `operatorBLSKey(address operator) → bytes memory`: Retrieves the current BLS key of an operator. +- `operatorByBLSKey(bytes memory key) → address`: Returns the operator associated with a specific BLS key. +- `blsKeyWasActiveAt(uint32 epoch, bytes memory key) → bool`: Checks if a BLS key was active during a specific epoch. + +#### Subnetwork Management + +Subnetworks allow for segmented management within the main network. **Key Functions:** -- `updateBLSKey(address operator, bytes memory key)`: Updates the BLS key associated with an operator. Throws an error if the key is already in use. -- `operatorBLSKey(address operator)`: Retrieves the current BLS key of an operator. If the key was updated in the current epoch, it returns the previous key. -- `operatorByBLSKey(bytes memory key)`: Returns the operator associated with a specific BLS key. -- `blsKeyWasActiveAt(uint48 epoch, bytes memory key)`: Checks if a BLS key was active during a specific epoch. +- `registerSubnetwork(uint96 subnetwork)`: Registers a new subnetwork. +- `pauseSubnetwork(uint96 subnetwork)`: Pauses an active subnetwork. +- `unpauseSubnetwork(uint96 subnetwork)`: Unpauses a subnetwork after the immutable period. +- `unregisterSubnetwork(uint96 subnetwork)`: Unregisters a subnetwork. +- `subnetworksLength() → uint256`: Returns the number of subnetworks. +- `subnetworkWithTimesAt(uint256 pos) → (uint160, uint32, uint32, uint32)`: Retrieves subnetwork details at a specific index. +- `activeSubnetworks() → uint160[]`: Returns a list of active subnetworks. ### Examples #### SimpleMiddleware Example -The `SimpleMiddleware` contract is an example implementation that demonstrates how to use the SDK to build a middleware contract that manages validators and handles slashing. +The `SimpleMiddleware` contract demonstrates how to combine various modules to build a middleware that manages validators and handles slashing. **Features:** -- **Validator Set Management**: Maintains a set of validators with their power and keys. -- **Slashing Mechanism**: Implements a `slash` function to penalize misbehaving operators. -- **Integration**: Utilizes `OperatorManager`, `VaultManager`, and `KeyManager` for comprehensive management. +- **Validator Set Construction**: Compiles a list of active validators based on the current epoch. +- **Power Calculation**: Calculates the power of each validator, which can be used for consensus mechanisms. +- **Slashing Mechanism**: Implements a `slash` function to penalize misbehaving operators proportionally to their stake. **Key Structures and Functions:** -- **ValidatorData**: Struct containing `power` and `key` of a validator. -- `getTotalStake()`: Returns the total stake for active operators in the current epoch. -- `getValidatorSet()`: Retrieves the current validator set with their power and keys. -- `slash(...)`: Slashes a validator based on provided parameters. +- **ValidatorData Struct**: + + ```solidity + struct ValidatorData { + uint256 power; + bytes32 key; + } + ``` + +- `getTotalStake() → uint256`: Returns the total stake of all active operators. +- `getTotalPower() → uint256`: Returns the total power of all active operators. +- `getValidatorSet() → ValidatorData[]`: Retrieves the current validator set. +- `slash(uint32 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) → SlashResponse[]`: Executes slashing on a misbehaving operator. + +**Note on `stakeToPower` in SimpleMiddleware:** + +In the `SimpleMiddleware` example, you can override the `stakeToPower` function to use an oracle for pricing different vault assets or weighting vaults differently. This allows the middleware to accurately reflect the value of stakes from various vaults, ensuring a fair and balanced validator set. + +For instance: + +```solidity +function stakeToPower(address vault, uint256 stake) public view virtual override returns (uint256) { + // Use an oracle to get the price of the asset staked in the vault + uint256 assetPrice = oracle.getPrice(vault); + // Convert stake to power based on asset price + return stake * assetPrice; +} +``` #### SqrtTaskMiddleware Example -The `SqrtTaskMiddleware` contract is an advanced example that extends the SDK to create computational tasks requiring operators to compute square roots. It includes signature verification and custom slashing logic. +The `SqrtTaskMiddleware` contract is an advanced example showing how to implement custom logic in the middleware. It extends the default modules and introduces a task-based system where operators solve computational tasks. **Features:** -- **Task Creation and Completion**: Allows creating tasks and operators to submit computed answers. -- **Signature Verification**: Uses EIP712 standard for verifying operator signatures. -- **Custom Slashing**: Implements slashing if the operator provides an incorrect answer. +- **Task Creation**: Users can create tasks by specifying a value and an operator. +- **Task Completion**: Operators submit solutions along with signatures. The middleware verifies the solution and signature. +- **Custom Slashing**: Operators are slashed if they provide incorrect solutions. **Key Structures and Functions:** -- **Task**: Struct containing `captureTimestamp`, `value`, `operator`, and `completed` status. -- `createTask(uint256 value, address operator)`: Creates a new computational task. -- `completeTask(...)`: Operators submit answers to tasks with signature verification. -- `_slash(...)`: Internal function to handle slashing logic if the answer is incorrect. +- **Task Struct**: + + ```solidity + struct Task { + uint48 captureTimestamp; + uint256 value; + address operator; + bool completed; + } + ``` + +- `createTask(uint256 value, address operator) → uint256`: Creates a new task for an operator. +- `completeTask(uint256 taskIndex, uint256 answer, bytes calldata signature, bytes[] calldata stakeHints, bytes[] calldata slashHints) → bool`: Allows an operator to complete a task and checks the validity of the answer. +- `_verify(uint256 taskIndex, uint256 answer, bytes calldata signature) → bool`: Verifies the operator's answer and signature. +- `_slash(uint256 taskIndex, bytes[] calldata stakeHints, bytes[] calldata slashHints)`: Executes slashing if the operator's answer is incorrect. **Note:** -In `SqrtTaskMiddleware`, subnetworks are not used, and attempts to register or manage subnetworks are disabled by overriding the functions and reverting. +In `SqrtTaskMiddleware`, subnetworks are not used, and attempts to manage subnetworks are disabled by overriding the functions and reverting: ```solidity -function registerSubnetwork(uint96 subnetwork) public override { +function registerSubnetwork(uint96 subnetwork) public pure override { revert(); } -// Similarly for other subnetwork functions... +// Similar overrides for other subnetwork functions... ``` +### Writing Your Own Network Middleware + +The Middleware SDK is designed to be flexible and extensible, allowing you to create custom network middleware tailored to your specific requirements. This section guides you through the process of writing your own network middleware using the SDK modules. + +#### Steps to Build Your Custom Middleware + +1. **Define Your Middleware's Purpose and Requirements** + + - **Identify Functionalities**: Determine what functionalities your middleware needs to provide, such as custom slashing conditions, validator set management, or unique staking mechanisms. + - **Select Modules**: Decide which SDK modules are necessary for your middleware (e.g., operator management, vault management, key management). + - **Custom Logic**: Consider any custom logic or features you need to implement beyond the provided modules. + +2. **Select and Import the Necessary Modules** + + - **Import Modules**: Import the base or default modules that provide the required functionalities. + - **Example Imports**: + + ```solidity + import {DefaultOperatorManager} from "./OperatorManagers/DefaultOperatorManager.sol"; + import {DefaultVaultManager} from "./VaultManagers/DefaultVaultManager.sol"; + import {DefaultKeyManager} from "./KeyManagers/DefaultKeyManager.sol"; + ``` + +3. **Create Your Middleware Contract** + + - **Inherit Modules**: Inherit from the selected modules to compose your middleware contract. + - **Example Contract Structure**: + + ```solidity + contract CustomMiddleware is DefaultOperatorManager, DefaultVaultManager, DefaultKeyManager { + // Custom logic here + } + ``` + +4. **Implement Custom Logic** + + - **Override Functions**: Override base functions to introduce custom behavior. + - **Example: Custom `stakeToPower` Function**: + + ```solidity + function stakeToPower(address vault, uint256 stake) public view virtual override returns (uint256) { + // Implement custom logic to convert stake to power + uint256 assetPrice = getAssetPriceFromOracle(vault); + return stake * assetPrice; + } + ``` + + - **Add New Functions**: Implement new functions for additional processes, such as specialized slashing conditions or custom validator selection criteria. + - **Example: Custom Slashing Logic**: + + ```solidity + function customSlash(address operator, uint256 penalty) public onlyOwner { + // Custom slashing logic + // For example, slash a fixed penalty amount from the operator's stake + // Implement the slashing mechanism according to your network's rules + } + ``` + +5. **Handle Epoch and State Management** + + - **Epoch Utilities**: Use the epoch management utilities provided by `BaseManager` to handle time-based state changes. + - **State Transitions**: Ensure that your middleware correctly manages state transitions between epochs, especially if your custom logic depends on epoch-specific data. + +### Custom Modules + +The Middleware SDK not only provides default modules but also allows developers to create custom modules to extend or modify the SDK's functionalities. This enables you to tailor the middleware to fit unique requirements that may not be covered by the default modules. + +#### Example: Self-Register Operator Manager + +In some networks, it might be desirable for operators to register themselves without the intervention of the contract owner or an administrator. Below is an example of how you can create a custom operator manager module that allows for self-registration, using the internal functions from `BaseOperatorManager`, similar to `DefaultOperatorManager`. + +##### Creating a Self-Register Operator Manager + +1. **Create a New Contract** + + ```solidity + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.25; + + import {BaseOperatorManager} from "./BaseOperatorManager.sol"; + + contract SelfRegisterOperatorManager is BaseOperatorManager { + error AlreadyRegistered(); + error NotOptedIn(); + error NotOperator(); + + constructor() { + // Initialization if needed + } + + function registerOperator() public { + address operator = msg.sender; + _registerOperator(operator); + } + + function unregisterOperator() public { + address operator = msg.sender; + _unregisterOperator(operator); + } + + // Optionally, you can allow operators to pause and unpause themselves + function pauseOperator() public { + address operator = msg.sender; + _pauseOperator(operator); + } + + function unpauseOperator() public { + address operator = msg.sender; + _unpauseOperator(operator); + } + } + ``` + +2. **Using Internal Functions** + + In this example, the `SelfRegisterOperatorManager` contract extends `BaseOperatorManager` and exposes public functions that internally call the protected functions provided by `BaseOperatorManager`. This approach mirrors how `DefaultOperatorManager` operates but allows operators to register themselves. + +3. **Self-Registration Logic** + + The internal functions `_registerOperator`, `_unregisterOperator`, `_pauseOperator`, and `_unpauseOperator` handle validation and state changes. By using these functions, you ensure consistency and reuse the existing logic. + +4. **Error Handling and Validation** + + The internal functions already include necessary validations, such as checking if the operator is a valid entity and has opted in. Therefore, you can rely on these internal functions for error handling. + +5. **Integrate the Custom Module** + + In your middleware contract, inherit from `SelfRegisterOperatorManager` instead of `DefaultOperatorManager`: + + ```solidity + contract CustomMiddleware is SelfRegisterOperatorManager, DefaultVaultManager, DefaultKeyManager { + // Custom logic here + } + ``` + +6. **Benefits** + + - **Reusability**: By using the internal functions from `BaseOperatorManager`, you ensure consistency with the SDK's architecture. + - **Encapsulation**: Internal functions handle validation and state changes, keeping your public functions clean and focused. + - **Flexibility**: Operators can manage their participation autonomously. + +## Important Notes + +- **Epoch-Based Data**: Most data is relevant only for the current epoch. Ensure that data is fetched at the finalized block moment using calls at specific blocks. +- **Immutable Periods**: Certain actions, like unpausing operators or vaults, require waiting for an immutable period (specified in epochs) before they can be reversed. +- **Slashing Windows**: The slashing window is the duration during which a slashing action can be executed after misbehavior is detected. It should be set appropriately relative to the epoch duration. +- **Finalized Blocks**: Networks typically operate on finalized blocks. Use finalized blocks to retrieve data to avoid discrepancies due to chain reorganizations or forks. +- **Data Consistency**: Be mindful that most state changes are epoch-bound, and historical data may not reflect the current state. Design your middleware to account for this. + ## License This project is licensed under the [MIT License](LICENSE). diff --git a/imgs/arch.jpg b/imgs/arch.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b893a26785855de55ddf435e300c7bebdea58755 GIT binary patch literal 141050 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

1IkU{pCcv8=xxy|n&cGd**2i8w^c`-Amz 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 @WXfy&sRkN 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 Date: Thu, 7 Nov 2024 04:06:18 +0700 Subject: [PATCH 028/115] refactor: move examples to examples dir --- src/examples/{simple-network => }/SimpleMiddleware.sol | 8 ++++---- src/examples/{simple-network => }/SqrtTaskMiddleware.sol | 8 ++++---- test/mocks/ExtendedSimpleMiddleware.sol | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename src/examples/{simple-network => }/SimpleMiddleware.sol (96%) rename src/examples/{simple-network => }/SqrtTaskMiddleware.sol (94%) diff --git a/src/examples/simple-network/SimpleMiddleware.sol b/src/examples/SimpleMiddleware.sol similarity index 96% rename from src/examples/simple-network/SimpleMiddleware.sol rename to src/examples/SimpleMiddleware.sol index f499f31..6787382 100644 --- a/src/examples/simple-network/SimpleMiddleware.sol +++ b/src/examples/SimpleMiddleware.sol @@ -9,10 +9,10 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseManager} from "../../BaseManager.sol"; -import {DefaultVaultManager} from "../../VaultManagers/DefaultVaultManager.sol"; -import {DefaultOperatorManager} from "../../OperatorManagers/DefaultOperatorManager.sol"; -import {DefaultKeyManager} from "../../KeyManagers/DefaultKeyManager.sol"; +import {BaseManager} from "../BaseManager.sol"; +import {DefaultVaultManager} from "../VaultManagers/DefaultVaultManager.sol"; +import {DefaultOperatorManager} from "../OperatorManagers/DefaultOperatorManager.sol"; +import {DefaultKeyManager} from "../KeyManagers/DefaultKeyManager.sol"; contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager { using Subnetwork for address; diff --git a/src/examples/simple-network/SqrtTaskMiddleware.sol b/src/examples/SqrtTaskMiddleware.sol similarity index 94% rename from src/examples/simple-network/SqrtTaskMiddleware.sol rename to src/examples/SqrtTaskMiddleware.sol index eed4387..0279205 100644 --- a/src/examples/simple-network/SqrtTaskMiddleware.sol +++ b/src/examples/SqrtTaskMiddleware.sol @@ -10,10 +10,10 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {BaseManager} from "../../BaseManager.sol"; -import {DefaultVaultManager} from "../../VaultManagers/DefaultVaultManager.sol"; -import {DefaultOperatorManager} from "../../OperatorManagers/DefaultOperatorManager.sol"; -import {DefaultKeyManager} from "../../KeyManagers/DefaultKeyManager.sol"; +import {BaseManager} from "../BaseManager.sol"; +import {DefaultVaultManager} from "../VaultManagers/DefaultVaultManager.sol"; +import {DefaultOperatorManager} from "../OperatorManagers/DefaultOperatorManager.sol"; +import {DefaultKeyManager} from "../KeyManagers/DefaultKeyManager.sol"; contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager, EIP712 { using Subnetwork for address; diff --git a/test/mocks/ExtendedSimpleMiddleware.sol b/test/mocks/ExtendedSimpleMiddleware.sol index 71359dc..38dbfd8 100644 --- a/test/mocks/ExtendedSimpleMiddleware.sol +++ b/test/mocks/ExtendedSimpleMiddleware.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {SimpleMiddleware} from "../../src/examples/simple-network/SimpleMiddleware.sol"; +import {SimpleMiddleware} from "../../src/examples/SimpleMiddleware.sol"; import {DefaultBLSKeyManager} from "../../src/KeyManagers/DefaultBLSKeyManager.sol"; contract ExtendedSimpleMiddleware is SimpleMiddleware, DefaultBLSKeyManager { From 87878df480a343de2b67c9b18085bd2aa1066ebc Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Sat, 9 Nov 2024 09:36:42 +0700 Subject: [PATCH 029/115] refactor: make initializble, add all network active vaults getter, subnetworks moved to vaultmanager --- .gitmodules | 3 + lib/core | 2 +- lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- lib/openzeppelin-contracts-upgradeable | 1 + remappings.txt | 1 + src/BaseManager.sol | 107 ++++--------------- src/VaultManagers/BaseVaultManager.sol | 121 ++++++++++++++++++++-- src/VaultManagers/DefaultVaultManager.sol | 32 ++++++ src/examples/SimpleMiddleware.sol | 15 +-- src/examples/SqrtTaskMiddleware.sol | 8 +- 11 files changed, 181 insertions(+), 113 deletions(-) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index 0983c9e..07c3c2d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/core"] path = lib/core url = https://github.com/symbioticfi/core +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/core b/lib/core index bdbb05e..5996d26 160000 --- a/lib/core +++ b/lib/core @@ -1 +1 @@ -Subproject commit bdbb05ebd9ea6d96c671b672562ae23afcb2123b +Subproject commit 5996d2676d57b56b4568e9378461dfb0e0bdd42d diff --git a/lib/forge-std b/lib/forge-std index 1de6eec..035de35 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1de6eecf821de7fe2c908cc48d3ab3dced20717f +Subproject commit 035de35f5e366c8d6ed142aec4ccb57fe2dd87d4 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index dbb6104..6325009 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 +Subproject commit 632500967504310a07f9d2c70ad378cf53be0109 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..fa52531 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit fa525310e45f91eb20a6d3baa2644be8e0adba31 diff --git a/remappings.txt b/remappings.txt index 9aedebc..6309dcb 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,4 @@ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @symbiotic/=lib/core/src/ @symbiotic-test=lib/core/test \ No newline at end of file diff --git a/src/BaseManager.sol b/src/BaseManager.sol index 0b8ed9e..ddfbf41 100644 --- a/src/BaseManager.sol +++ b/src/BaseManager.sol @@ -2,32 +2,30 @@ pragma solidity 0.8.25; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -import {PauseableEnumerableSet} from "./libraries/PauseableEnumerableSet.sol"; - -abstract contract BaseManager is Ownable { - using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +abstract contract BaseManager is Initializable, OwnableUpgradeable { error SlashingWindowTooShort(); // Error thrown when the slashing window is lower than epoch - address public immutable NETWORK; // Address of the network - uint48 public immutable EPOCH_DURATION; // Duration of each epoch - uint48 public immutable START_TIME; // Start time of the epoch - uint48 public immutable SLASHING_WINDOW; // Duration of the slashing window - uint32 public immutable IMMUTABLE_EPOCHS; // Duration of the state immutability in epochs - address public immutable VAULT_REGISTRY; // Address of the vault registry - address public immutable OPERATOR_REGISTRY; // Address of the operator registry - address public immutable OPERATOR_NET_OPTIN; // Address of the operator network opt-in service + address public NETWORK; // Address of the network + uint48 public EPOCH_DURATION; // Duration of each epoch + uint48 public START_TIME; // Start time of the epoch + uint48 public SLASHING_WINDOW; // Duration of the slashing window + uint32 public IMMUTABLE_EPOCHS; // Duration of the state immutability in epochs + address public VAULT_REGISTRY; // Address of the vault registry + address public OPERATOR_REGISTRY; // Address of the operator registry + address public OPERATOR_NET_OPTIN; // Address of the operator network opt-in service uint64 public constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type uint64 public constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type - uint96 public constant DEFAULT_SUBNETWORK = 0; // Default subnetwork identifier - PauseableEnumerableSet.Uint160Set _subnetworks; // Set of active subnetworks + constructor() { + _disableInitializers(); + } /* - * @notice Constructor for initializing the BaseManager contract. + * @notice initalizer of the BaseManager contract. * @param owner The address of the contract owner. * @param network The address of the network. * @param epochDuration The duration of each epoch. @@ -36,7 +34,7 @@ abstract contract BaseManager is Ownable { * @param operatorRegistry The address of the operator registry. * @param operatorNetOptIn The address of the operator network opt-in service. */ - constructor( + function initialize( address owner, address network, uint48 epochDuration, @@ -44,11 +42,13 @@ abstract contract BaseManager is Ownable { address vaultRegistry, address operatorRegistry, address operatorNetOptIn - ) Ownable(owner) { + ) public virtual initializer { if (slashingWindow < epochDuration) { revert SlashingWindowTooShort(); } + __Ownable_init(owner); + NETWORK = network; EPOCH_DURATION = epochDuration; SLASHING_WINDOW = slashingWindow; @@ -56,8 +56,6 @@ abstract contract BaseManager is Ownable { VAULT_REGISTRY = vaultRegistry; OPERATOR_REGISTRY = operatorRegistry; OPERATOR_NET_OPTIN = operatorNetOptIn; - - _subnetworks.register(getCurrentEpoch(), uint160(DEFAULT_SUBNETWORK)); // Register default subnetwork } /* @@ -93,71 +91,4 @@ abstract contract BaseManager is Ownable { function getCurrentEpochStart() public view returns (uint48 timestamp) { return START_TIME + getCurrentEpoch() * EPOCH_DURATION; } - - /* - * @notice Returns the number of subnetworks registered. - * @return The count of registered subnetworks. - */ - function subnetworksLength() public view returns (uint256) { - return _subnetworks.length(); - } - - /* - * @notice Returns the subnetwork information at a specified position. - * @param pos The index of the subnetwork. - * @return The subnetwork details including address, enabled epoch, disabled epoch and enabled before disabled epoch. - */ - function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint32, uint32, uint32) { - return _subnetworks.at(pos); - } - - /* - * @notice Returns an array of active subnetworks for the current epoch. - * @return An array of active subnetwork addresses. - */ - function activeSubnetworks() public view returns (uint160[] memory) { - return _subnetworks.getActive(getCurrentEpoch()); - } - - /* - * @notice Checks if a given subnetwork was active at a specified epoch. - * @param epoch The epoch to check. - * @param subnetwork The subnetwork to check. - * @return A boolean indicating whether the subnetwork was active at the specified epoch. - */ - function subnetworkWasActiveAt(uint32 epoch, uint96 subnetwork) public view returns (bool) { - return _subnetworks.wasActiveAt(epoch, uint160(subnetwork)); - } - - /* - * @notice Registers a new subnetwork. - * @param subnetwork The identifier of the subnetwork to register. - */ - function registerSubnetwork(uint96 subnetwork) public virtual onlyOwner { - _subnetworks.register(getCurrentEpoch(), uint160(subnetwork)); - } - - /* - * @notice Pauses a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to pause. - */ - function pauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { - _subnetworks.pause(getCurrentEpoch(), uint160(subnetwork)); - } - - /* - * @notice Unpauses a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to unpause. - */ - function unpauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { - _subnetworks.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); - } - - /* - * @notice Unregisters a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to unregister. - */ - function unregisterSubnetwork(uint96 subnetwork) public virtual onlyOwner { - _subnetworks.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); - } } diff --git a/src/VaultManagers/BaseVaultManager.sol b/src/VaultManagers/BaseVaultManager.sol index 953ca66..b454577 100644 --- a/src/VaultManagers/BaseVaultManager.sol +++ b/src/VaultManagers/BaseVaultManager.sol @@ -12,11 +12,13 @@ import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; abstract contract BaseVaultManager is BaseManager { + using EnumerableMap for EnumerableMap.AddressToUintMap; using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; using Subnetwork for address; @@ -35,9 +37,10 @@ abstract contract BaseVaultManager is BaseManager { error NonVetoSlasher(); error TooOldTimestampSlash(); + PauseableEnumerableSet.Uint160Set internal _subnetworks; PauseableEnumerableSet.AddressSet internal _sharedVaults; mapping(address => PauseableEnumerableSet.AddressSet) internal _operatorVaults; - mapping(address => uint256) public operatorVaultExists; + EnumerableMap.AddressToUintMap internal _allOperatorVaults; struct SlashResponse { address vault; @@ -46,6 +49,41 @@ abstract contract BaseVaultManager is BaseManager { uint256 response; // if instant slashed amount else slash index } + /* + * @notice Returns the number of subnetworks registered. + * @return The count of registered subnetworks. + */ + function subnetworksLength() public view returns (uint256) { + return _subnetworks.length(); + } + + /* + * @notice Returns the subnetwork information at a specified position. + * @param pos The index of the subnetwork. + * @return The subnetwork details including address, enabled epoch, disabled epoch and enabled before disabled epoch. + */ + function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint32, uint32, uint32) { + return _subnetworks.at(pos); + } + + /* + * @notice Returns an array of active subnetworks for the current epoch. + * @return An array of active subnetwork addresses. + */ + function activeSubnetworks() public view returns (uint160[] memory) { + return _subnetworks.getActive(getCurrentEpoch()); + } + + /* + * @notice Checks if a given subnetwork was active at a specified epoch. + * @param epoch The epoch to check. + * @param subnetwork The subnetwork to check. + * @return A boolean indicating whether the subnetwork was active at the specified epoch. + */ + function subnetworkWasActiveAt(uint32 epoch, uint96 subnetwork) public view returns (bool) { + return _subnetworks.wasActiveAt(epoch, uint160(subnetwork)); + } + /* * @notice Returns the length of shared vaults. * @return The number of shared vaults. @@ -96,6 +134,32 @@ abstract contract BaseVaultManager is BaseManager { return stake; } + /* + * @notice Returns the list of network's active vaults for the current epoch. + * @return An array of addresses representing the active vaults. + */ + function activeVaults() public view virtual returns (address[] memory) { + uint32 epoch = getCurrentEpoch(); + address[] memory activeSharedVaults = _sharedVaults.getActive(epoch); + address[] memory vaults = new address[](activeSharedVaults.length + _allOperatorVaults.length()); + uint256 len = activeSharedVaults.length; + for (uint256 i; i < activeSharedVaults.length; ++i) { + vaults[i] = activeSharedVaults[i]; + } + for (uint256 i = 0; i < _allOperatorVaults.length(); ++i) { + (address vault, uint256 count) = _allOperatorVaults.at(i); + if (count > 0) { + vaults[len++] = vault; + } + } + + assembly ("memory-safe") { + mstore(vaults, len) + } + + return vaults; + } + /* * @notice Returns the list of active vaults for a specific operator. * @param operator The address of the operator. @@ -158,12 +222,12 @@ abstract contract BaseVaultManager is BaseManager { function getOperatorStake(address operator) public view virtual returns (uint256 stake) { uint48 timestamp = getCurrentEpochStart(); address[] memory vaults = activeVaults(operator); - uint160[] memory _subnetworks = activeSubnetworks(); + uint160[] memory subnetworks = activeSubnetworks(); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; - for (uint256 j; j < _subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); + for (uint256 j; j < subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); stake += IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); } } @@ -179,12 +243,12 @@ abstract contract BaseVaultManager is BaseManager { function getOperatorPower(address operator) public view virtual returns (uint256 power) { uint48 timestamp = getCurrentEpochStart(); address[] memory vaults = activeVaults(operator); - uint160[] memory _subnetworks = activeSubnetworks(); + uint160[] memory subnetworks = activeSubnetworks(); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; - for (uint256 j; j < _subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); + for (uint256 j; j < subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); power += stakeToPower(vault, stake); } @@ -221,13 +285,45 @@ abstract contract BaseVaultManager is BaseManager { return power; } + /* + * @notice Registers a new subnetwork. + * @param subnetwork The identifier of the subnetwork to register. + */ + function _registerSubnetwork(uint96 subnetwork) internal { + _subnetworks.register(getCurrentEpoch(), uint160(subnetwork)); + } + + /* + * @notice Pauses a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to pause. + */ + function _pauseSubnetwork(uint96 subnetwork) internal { + _subnetworks.pause(getCurrentEpoch(), uint160(subnetwork)); + } + + /* + * @notice Unpauses a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to unpause. + */ + function _unpauseSubnetwork(uint96 subnetwork) internal { + _subnetworks.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); + } + + /* + * @notice Unregisters a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to unregister. + */ + function _unregisterSubnetwork(uint96 subnetwork) internal { + _subnetworks.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); + } + /* * @notice Registers a new shared vault. * @param vault The address of the vault to register. */ function _registerSharedVault(address vault) internal { _validateVault(vault); - if (operatorVaultExists[vault] > 0) { + if (_allOperatorVaults.get(vault) > 0) { revert VaultAlreadyRegistred(); } _sharedVaults.register(getCurrentEpoch(), vault); @@ -243,8 +339,8 @@ abstract contract BaseVaultManager is BaseManager { if (_sharedVaults.contains(vault)) { revert VaultAlreadyRegistred(); } - operatorVaultExists[vault]++; _operatorVaults[operator].register(getCurrentEpoch(), vault); + _allOperatorVaults.set(vault, _allOperatorVaults.get(vault) + 2); } /* @@ -270,6 +366,7 @@ abstract contract BaseVaultManager is BaseManager { */ function _pauseOperatorVault(address operator, address vault) internal { _operatorVaults[operator].pause(getCurrentEpoch(), vault); + _allOperatorVaults.set(vault, _allOperatorVaults.get(vault) - 1); } /* @@ -279,6 +376,7 @@ abstract contract BaseVaultManager is BaseManager { */ function _unpauseOperatorVault(address operator, address vault) internal { _operatorVaults[operator].unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); + _allOperatorVaults.set(vault, _allOperatorVaults.get(vault) + 1); } /* @@ -296,7 +394,10 @@ abstract contract BaseVaultManager is BaseManager { */ function _unregisterOperatorVault(address operator, address vault) internal { _operatorVaults[operator].unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); - operatorVaultExists[vault]--; + _allOperatorVaults.set(vault, _allOperatorVaults.get(vault) - 1); + if (_allOperatorVaults.get(vault) == 0) { + _allOperatorVaults.remove(vault); + } } /* diff --git a/src/VaultManagers/DefaultVaultManager.sol b/src/VaultManagers/DefaultVaultManager.sol index 76cb437..cd5b643 100644 --- a/src/VaultManagers/DefaultVaultManager.sol +++ b/src/VaultManagers/DefaultVaultManager.sol @@ -4,6 +4,38 @@ pragma solidity 0.8.25; import {BaseVaultManager} from "./BaseVaultManager.sol"; abstract contract DefaultVaultManager is BaseVaultManager { + /* + * @notice Registers a new subnetwork. + * @param subnetwork The identifier of the subnetwork to register. + */ + function registerSubnetwork(uint96 subnetwork) public virtual onlyOwner { + _registerSubnetwork(subnetwork); + } + + /* + * @notice Pauses a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to pause. + */ + function pauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { + _pauseSubnetwork(subnetwork); + } + + /* + * @notice Unpauses a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to unpause. + */ + function unpauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { + _unpauseSubnetwork(subnetwork); + } + + /* + * @notice Unregisters a specified subnetwork. + * @param subnetwork The identifier of the subnetwork to unregister. + */ + function unregisterSubnetwork(uint96 subnetwork) public virtual onlyOwner { + _unregisterSubnetwork(subnetwork); + } + /* * @notice Registers a new shared vault. * @param vault The address of the vault to register. diff --git a/src/examples/SimpleMiddleware.sol b/src/examples/SimpleMiddleware.sol index 6787382..7836439 100644 --- a/src/examples/SimpleMiddleware.sol +++ b/src/examples/SimpleMiddleware.sol @@ -9,7 +9,6 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseManager} from "../BaseManager.sol"; import {DefaultVaultManager} from "../VaultManagers/DefaultVaultManager.sol"; import {DefaultOperatorManager} from "../OperatorManagers/DefaultOperatorManager.sol"; import {DefaultKeyManager} from "../KeyManagers/DefaultKeyManager.sol"; @@ -45,7 +44,9 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul address owner, uint48 epochDuration, uint48 slashingWindow - ) BaseManager(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) {} + ) { + initialize(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + } /* * @notice Returns the total stake for the active operators in the current epoch. @@ -125,9 +126,9 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul uint256 totalStake = getOperatorStake(operator); // Get the total stake for the operator address[] memory vaults = activeVaults(operator); // Get active vaults for the operator - uint160[] memory _subnetworks = activeSubnetworks(); // Get active subnetworks + uint160[] memory subnetworks = activeSubnetworks(); // Get active subnetworks - slashResponses = new SlashResponse[](vaults.length * _subnetworks.length); // Initialize the array for slash responses + slashResponses = new SlashResponse[](vaults.length * subnetworks.length); // Initialize the array for slash responses uint256 len = 0; // Length counter if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { @@ -135,13 +136,13 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul } for (uint256 i; i < vaults.length; ++i) { - if (stakeHints[i].length != _subnetworks.length) { + if (stakeHints[i].length != subnetworks.length) { revert InvalidHints(); // Revert if the stake hints do not match the subnetworks } address vault = vaults[i]; // Get the vault address - for (uint256 j = 0; j < _subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(_subnetworks[j])); // Get the subnetwork + for (uint256 j = 0; j < subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); // Get the subnetwork uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( subnetwork, operator, diff --git a/src/examples/SqrtTaskMiddleware.sol b/src/examples/SqrtTaskMiddleware.sol index 0279205..871fb4b 100644 --- a/src/examples/SqrtTaskMiddleware.sol +++ b/src/examples/SqrtTaskMiddleware.sol @@ -10,7 +10,6 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {BaseManager} from "../BaseManager.sol"; import {DefaultVaultManager} from "../VaultManagers/DefaultVaultManager.sol"; import {DefaultOperatorManager} from "../OperatorManagers/DefaultOperatorManager.sol"; import {DefaultKeyManager} from "../KeyManagers/DefaultKeyManager.sol"; @@ -45,10 +44,9 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa address owner, uint48 epochDuration, uint48 slashingWindow - ) - BaseManager(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin) - EIP712("SqrtTaskMiddleware", "1") - {} + ) EIP712("SqrtTaskMiddleware", "1") { + initialize(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + } function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { taskIndex = tasks.length; From 9e64c77815ea0710f474a8347e0d71a485da24fb Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Sat, 9 Nov 2024 16:58:50 +0700 Subject: [PATCH 030/115] WIP: removed epochs --- lib/core | 2 +- lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- src/BaseManager.sol | 46 +---- src/KeyManagers/BaseBLSKeyManager.sol | 54 ++--- src/KeyManagers/BaseKeyManager.sol | 54 ++--- src/OperatorManagers/BaseOperatorManager.sol | 20 +- src/VaultManagers/BaseVaultManager.sol | 153 ++++++++------ src/libraries/PauseableEnumerableSet.sol | 199 +++++++++---------- 9 files changed, 266 insertions(+), 266 deletions(-) diff --git a/lib/core b/lib/core index 5996d26..ae741fa 160000 --- a/lib/core +++ b/lib/core @@ -1 +1 @@ -Subproject commit 5996d2676d57b56b4568e9378461dfb0e0bdd42d +Subproject commit ae741fa21daa77c5f78876ff426a8e6450a19fe0 diff --git a/lib/forge-std b/lib/forge-std index 035de35..da591f5 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 035de35f5e366c8d6ed142aec4ccb57fe2dd87d4 +Subproject commit da591f56d8884c5824c0c1b3103fbcfd81123c4c diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 6325009..448efee 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 632500967504310a07f9d2c70ad378cf53be0109 +Subproject commit 448efeea6640bbbc09373f03fbc9c88e280147ba diff --git a/src/BaseManager.sol b/src/BaseManager.sol index ddfbf41..56f5df3 100644 --- a/src/BaseManager.sol +++ b/src/BaseManager.sol @@ -6,13 +6,8 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; abstract contract BaseManager is Initializable, OwnableUpgradeable { - error SlashingWindowTooShort(); // Error thrown when the slashing window is lower than epoch - address public NETWORK; // Address of the network - uint48 public EPOCH_DURATION; // Duration of each epoch - uint48 public START_TIME; // Start time of the epoch uint48 public SLASHING_WINDOW; // Duration of the slashing window - uint32 public IMMUTABLE_EPOCHS; // Duration of the state immutability in epochs address public VAULT_REGISTRY; // Address of the vault registry address public OPERATOR_REGISTRY; // Address of the operator registry address public OPERATOR_NET_OPTIN; // Address of the operator network opt-in service @@ -37,58 +32,27 @@ abstract contract BaseManager is Initializable, OwnableUpgradeable { function initialize( address owner, address network, - uint48 epochDuration, uint48 slashingWindow, address vaultRegistry, address operatorRegistry, address operatorNetOptIn ) public virtual initializer { - if (slashingWindow < epochDuration) { - revert SlashingWindowTooShort(); - } __Ownable_init(owner); NETWORK = network; - EPOCH_DURATION = epochDuration; SLASHING_WINDOW = slashingWindow; - IMMUTABLE_EPOCHS = uint32((slashingWindow + epochDuration - 1) / epochDuration); VAULT_REGISTRY = vaultRegistry; OPERATOR_REGISTRY = operatorRegistry; OPERATOR_NET_OPTIN = operatorNetOptIn; } /* - * @notice Returns the start timestamp of a given epoch. - * @param epoch The epoch number. - * @return The start timestamp of the specified epoch. - */ - function getEpochStart(uint32 epoch) public view returns (uint48 timestamp) { - return START_TIME + epoch * EPOCH_DURATION; - } - - /* - * @notice Returns the epoch number corresponding to a given timestamp. - * @param timestamp The timestamp to convert to an epoch number. - * @return The epoch number associated with the specified timestamp. - */ - function getEpochAt(uint48 timestamp) public view returns (uint32 epoch) { - return uint32((timestamp - START_TIME) / EPOCH_DURATION); - } - - /* - * @notice Returns the current epoch number based on the current timestamp. - * @return The current epoch number. - */ - function getCurrentEpoch() public view returns (uint32 epoch) { - return getEpochAt(Time.timestamp()); - } - - /* - * @notice Returns the start timestamp of the current epoch. - * @return The start timestamp of the current epoch. + * @notice Returns the current capture timestamp + * @dev Returns block.timestamp - 1 by default but can be overrided + * @return The current capture timestamp */ - function getCurrentEpochStart() public view returns (uint48 timestamp) { - return START_TIME + getCurrentEpoch() * EPOCH_DURATION; + function getCaptureTimestamp() public virtual view returns(uint48 timestamp) { + return Time.timestamp() - 1; } } diff --git a/src/KeyManagers/BaseBLSKeyManager.sol b/src/KeyManagers/BaseBLSKeyManager.sol index e17f77d..ba08456 100644 --- a/src/KeyManagers/BaseBLSKeyManager.sol +++ b/src/KeyManagers/BaseBLSKeyManager.sol @@ -14,50 +14,50 @@ abstract contract BaseBLSKeyManager is BaseManager { mapping(address => bytes) public blsKeys; // Mapping from operator addresses to their BLS keys mapping(address => bytes) public prevBLSKeys; // Mapping from operator addresses to their previous BLS keys - mapping(address => uint32) public blsKeyUpdateEpoch; // Mapping from operator addresses to the epoch of the last BLS key update + mapping(address => uint48) public blsKeyUpdateTimestamp; // Mapping from operator addresses to the timestamp of the last BLS key update mapping(bytes => PauseableEnumerableSet.Inner) internal _blsKeyData; // Mapping from BLS keys to their associated data - /* - * @notice Returns the operator address associated with a given BLS key. - * @param key The BLS key for which to find the associated operator. - * @return The address of the operator linked to the specified BLS key. + /** + * @notice Returns the operator address associated with a given BLS key + * @param key The BLS key for which to find the associated operator + * @return The address of the operator linked to the specified BLS key */ function operatorByBLSKey(bytes memory key) public view returns (address) { return _blsKeyData[key].getAddress(); } - /* - * @notice Returns the current BLS key for a given operator. - * If the key has changed in the previous epoch, returns the previous key. - * @param operator The address of the operator. - * @return The BLS key associated with the specified operator. + /** + * @notice Returns the current or previous BLS key for a given operator + * @dev Returns the previous key if the key was updated in the current epoch + * @param operator The address of the operator + * @return The BLS key associated with the specified operator */ function operatorBLSKey(address operator) public view returns (bytes memory) { - if (blsKeyUpdateEpoch[operator] == getCurrentEpoch()) { + if (blsKeyUpdateTimestamp[operator] == getCaptureTimestamp()) { return prevBLSKeys[operator]; } return blsKeys[operator]; } - /* - * @notice Checks if a given BLS key was active at a specified epoch. - * @param epoch The epoch to check. - * @param key The BLS key to check. - * @return A boolean indicating whether the BLS key was active at the specifed epoch. + /** + * @notice Checks if a given BLS key was active at a specified timestamp + * @param timestamp The timestamp to check + * @param key The BLS key to check + * @return A boolean indicating whether the BLS key was active at the specified timestamp */ - function blsKeyWasActiveAt(uint32 epoch, bytes memory key) public view returns (bool) { - return _blsKeyData[key].wasActiveAt(epoch); + function blsKeyWasActiveAt(uint48 timestamp, bytes memory key) public view returns (bool) { + return _blsKeyData[key].wasActiveAt(timestamp); } - /* - * @notice Updates the BLS key associated with an operator. - * If the new key already exists, a DuplicateBLSKey error is thrown. - * @param operator The address of the operator whose BLS key is to be updated. - * @param key The new BLS key to associate with the operator. + /** + * @notice Updates the BLS key associated with an operator + * @dev Reverts if the key is already enabled or if another operator is using it + * @param operator The address of the operator whose BLS key is to be updated + * @param key The new BLS key to associate with the operator */ function _updateBLSKey(address operator, bytes memory key) internal { - uint32 epoch = getCurrentEpoch(); + uint48 timestamp = getCaptureTimestamp(); if (keccak256(blsKeys[operator]) == keccak256(key)) { revert BLSKeyAlreadyEnabled(); @@ -68,12 +68,12 @@ abstract contract BaseBLSKeyManager is BaseManager { } if (keccak256(key) != ZERO_BYTES_HASH && _blsKeyData[key].getAddress() == address(0)) { - _blsKeyData[key].set(epoch, operator); + _blsKeyData[key].set(timestamp, operator); } - if (blsKeyUpdateEpoch[operator] != epoch) { + if (blsKeyUpdateTimestamp[operator] != timestamp) { prevBLSKeys[operator] = blsKeys[operator]; - blsKeyUpdateEpoch[operator] = epoch; + blsKeyUpdateTimestamp[operator] = timestamp; } blsKeys[operator] = key; diff --git a/src/KeyManagers/BaseKeyManager.sol b/src/KeyManagers/BaseKeyManager.sol index a14121e..b8fbd5e 100644 --- a/src/KeyManagers/BaseKeyManager.sol +++ b/src/KeyManagers/BaseKeyManager.sol @@ -14,50 +14,50 @@ abstract contract BaseKeyManager is BaseManager { mapping(address => bytes32) public keys; // Mapping from operator addresses to their current keys mapping(address => bytes32) public prevKeys; // Mapping from operator addresses to their previous keys - mapping(address => uint32) public keyUpdateEpoch; // Mapping from operator addresses to the epoch of the last key update + mapping(address => uint48) public keyUpdateTimestamp; // Mapping from operator addresses to the epoch of the last key update mapping(bytes32 => PauseableEnumerableSet.Inner) internal _keyData; // Mapping from keys to their associated data - /* - * @notice Returns the operator address associated with a given key. - * @param key The key for which to find the associated operator. - * @return The address of the operator linked to the specified key. + /** + * @notice Returns the operator address associated with a given key + * @param key The key for which to find the associated operator + * @return The address of the operator linked to the specified key */ function operatorByKey(bytes32 key) public view returns (address) { return _keyData[key].getAddress(); } - /* - * @notice Returns the current key for a given operator. - * If the key has changed in the previous epoch, returns the previous key. - * @param operator The address of the operator. - * @return The key associated with the specified operator. + /** + * @notice Returns the current or previous key for a given operator + * @dev Returns the previous key if the key was updated in the current epoch + * @param operator The address of the operator + * @return The key associated with the specified operator */ function operatorKey(address operator) public view returns (bytes32) { - if (keyUpdateEpoch[operator] == getCurrentEpoch()) { + if (keyUpdateTimestamp[operator] == getCaptureTimestamp()) { return prevKeys[operator]; } return keys[operator]; } - /* - * @notice Checks if a given key was active at a specified epoch. - * @param epoch The epoch to check. - * @param key The key to check. - * @return A boolean indicating whether the key was active at the specified epoch. + /** + * @notice Checks if a given key was active at a specified timestamp + * @param timestamp The timestamp to check + * @param key The key to check + * @return A boolean indicating whether the key was active at the specified timestamp */ - function keyWasActiveAt(uint32 epoch, bytes32 key) public view returns (bool) { - return _keyData[key].wasActiveAt(epoch); + function keyWasActiveAt(uint48 timestamp, bytes32 key) public view returns (bool) { + return _keyData[key].wasActiveAt(timestamp); } - /* - * @notice Updates the key associated with an operator. - * If the new key already exists, a DuplicateKey error is thrown. - * @param operator The address of the operator whose key is to be updated. - * @param key The new key to associate with the operator. + /** + * @notice Updates the key associated with an operator + * @dev Reverts if the key is already enabled or if another operator is using it + * @param operator The address of the operator whose key is to be updated + * @param key The new key to associate with the operator */ function _updateKey(address operator, bytes32 key) internal { - uint32 epoch = getCurrentEpoch(); + uint48 timestamp = getCaptureTimestamp(); if (keys[operator] == key) { revert KeyAlreadyEnabled(); @@ -68,12 +68,12 @@ abstract contract BaseKeyManager is BaseManager { } if (key != ZERO_BYTES32 && _keyData[key].getAddress() == address(0)) { - _keyData[key].set(epoch, operator); + _keyData[key].set(timestamp, operator); } - if (keyUpdateEpoch[operator] != epoch) { + if (keyUpdateEpoch[operator] != timestamp) { prevKeys[operator] = keys[operator]; - keyUpdateEpoch[operator] = epoch; + keyUpdateTimestamp[operator] = timestamp; } keys[operator] = key; diff --git a/src/OperatorManagers/BaseOperatorManager.sol b/src/OperatorManagers/BaseOperatorManager.sol index 18c464b..24dce31 100644 --- a/src/OperatorManagers/BaseOperatorManager.sol +++ b/src/OperatorManagers/BaseOperatorManager.sol @@ -22,7 +22,7 @@ abstract contract BaseOperatorManager is BaseManager { PauseableEnumerableSet.AddressSet internal _operators; /* - * @notice Returns the length of the operators list. + * @notice Returns the total number of registered operators, including both active and inactive. * @return The number of registered operators. */ function operatorsLength() public view returns (uint256) { @@ -43,17 +43,17 @@ abstract contract BaseOperatorManager is BaseManager { * @return An array of addresses representing the active operators. */ function activeOperators() public view returns (address[] memory) { - return _operators.getActive(getCurrentEpoch()); + return _operators.getActive(getCaptureTimestamp()); } /* - * @notice Checks if a given operator was active at a specified epoch. - * @param epoch The epoch to check. + * @notice Checks if a given operator was active at a specified timestamp. + * @param timestamp The timestamp to check. * @param operator The operator to check. - * @return A boolean indicating whether the operator was active at the specified epoch. + * @return A boolean indicating whether the operator was active at the specified timestamp. */ - function operatorWasActiveAt(uint32 epoch, address operator) public view returns (bool) { - return _operators.wasActiveAt(epoch, operator); + function operatorWasActiveAt(uint48 timestamp, address operator) public view returns (bool) { + return _operators.wasActiveAt(timestamp, operator); } /* @@ -69,7 +69,7 @@ abstract contract BaseOperatorManager is BaseManager { revert OperatorNotOptedIn(); } - _operators.register(getCurrentEpoch(), operator); + _operators.register(getCaptureTimestamp(), operator); } /* @@ -85,7 +85,7 @@ abstract contract BaseOperatorManager is BaseManager { * @param operator The address of the operator to unpause. */ function _unpauseOperator(address operator) internal { - _operators.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, operator); + _operators.unpause(getCurrentEpoch(), SLASHING_WINDOW, operator); } /* @@ -93,6 +93,6 @@ abstract contract BaseOperatorManager is BaseManager { * @param operator The address of the operator to unregister. */ function _unregisterOperator(address operator) internal { - _operators.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, operator); + _operators.unregister(getCurrentEpoch(), SLASHING_WINDOW, operator); } } diff --git a/src/VaultManagers/BaseVaultManager.sol b/src/VaultManagers/BaseVaultManager.sol index b454577..ccb7b89 100644 --- a/src/VaultManagers/BaseVaultManager.sol +++ b/src/VaultManagers/BaseVaultManager.sol @@ -19,6 +19,7 @@ import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; abstract contract BaseVaultManager is BaseManager { using EnumerableMap for EnumerableMap.AddressToUintMap; + using EnumerableMap for EnumerableMap.AddressToAddressMap; using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; using Subnetwork for address; @@ -40,7 +41,7 @@ abstract contract BaseVaultManager is BaseManager { PauseableEnumerableSet.Uint160Set internal _subnetworks; PauseableEnumerableSet.AddressSet internal _sharedVaults; mapping(address => PauseableEnumerableSet.AddressSet) internal _operatorVaults; - EnumerableMap.AddressToUintMap internal _allOperatorVaults; + EnumerableMap.AddressToAddressMap internal _vaultOperator; struct SlashResponse { address vault; @@ -62,7 +63,7 @@ abstract contract BaseVaultManager is BaseManager { * @param pos The index of the subnetwork. * @return The subnetwork details including address, enabled epoch, disabled epoch and enabled before disabled epoch. */ - function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint32, uint32, uint32) { + function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint48, uint48, uint48) { return _subnetworks.at(pos); } @@ -71,17 +72,17 @@ abstract contract BaseVaultManager is BaseManager { * @return An array of active subnetwork addresses. */ function activeSubnetworks() public view returns (uint160[] memory) { - return _subnetworks.getActive(getCurrentEpoch()); + return _subnetworks.getActive(getCaptureTimestamp()); } /* * @notice Checks if a given subnetwork was active at a specified epoch. - * @param epoch The epoch to check. + * @param timestamp The timestamp to check. * @param subnetwork The subnetwork to check. * @return A boolean indicating whether the subnetwork was active at the specified epoch. */ - function subnetworkWasActiveAt(uint32 epoch, uint96 subnetwork) public view returns (bool) { - return _subnetworks.wasActiveAt(epoch, uint160(subnetwork)); + function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) public view returns (bool) { + return _subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); } /* @@ -97,10 +98,18 @@ abstract contract BaseVaultManager is BaseManager { * @param pos The index position in the shared vaults array. * @return The address, enabled epoch, disabled epoch and enabled before disabled epoch of the vault. */ - function sharedVaultWithEpochsAt(uint256 pos) public view returns (address, uint32, uint32, uint32) { + function sharedVaultWithEpochsAt(uint256 pos) public view returns (address, uint48, uint48, uint48) { return _sharedVaults.at(pos); } + /* + * @notice Returns an array of active shared vaults for the current epoch. + * @return An array of active shared vault addresses. + */ + function activeSharedVaults() public view returns (address[] memory) { + return _sharedVaults.getActive(getCaptureTimestamp()); + } + /* * @notice Returns the length of operator vaults for a specific operator. * @param operator The address of the operator. @@ -119,11 +128,20 @@ abstract contract BaseVaultManager is BaseManager { function operatorVaultWithEpochsAt(address operator, uint256 pos) public view - returns (address, uint32, uint32, uint32) + returns (address, uint48, uint48, uint48) { return _operatorVaults[operator].at(pos); } + /* + * @notice Returns an array of active operator vaults for a specific operator in the current epoch. + * @param operator The address of the operator. + * @return An array of active operator vault addresses. + */ + function activeOperatorVaults(address operator) public view returns (address[] memory) { + return _operatorVaults[operator].getActive(getCaptureTimestamp()); + } + /* * @notice Converts stake to power for a vault. * @param vault The address of the vault. @@ -139,16 +157,19 @@ abstract contract BaseVaultManager is BaseManager { * @return An array of addresses representing the active vaults. */ function activeVaults() public view virtual returns (address[] memory) { - uint32 epoch = getCurrentEpoch(); - address[] memory activeSharedVaults = _sharedVaults.getActive(epoch); - address[] memory vaults = new address[](activeSharedVaults.length + _allOperatorVaults.length()); + uint48 timestamp = getCaptureTimestamp(); + address[] memory activeSharedVaults = _sharedVaults.getActive(timestamp); uint256 len = activeSharedVaults.length; - for (uint256 i; i < activeSharedVaults.length; ++i) { + address[] memory vaults = new address[](len + _vaultOperator.length()); + + for (uint256 i; i < len; ++i) { vaults[i] = activeSharedVaults[i]; } - for (uint256 i = 0; i < _allOperatorVaults.length(); ++i) { - (address vault, uint256 count) = _allOperatorVaults.at(i); - if (count > 0) { + + uint256 operatorVaultsLen = _vaultOperator.length(); + for (uint256 i; i < operatorVaultsLen; ++i) { + (address vault, address operator) = _vaultOperator.at(i); + if (_operatorVaults[operator].wasActiveAt(timestamp, vault)) { vaults[len++] = vault; } } @@ -166,9 +187,9 @@ abstract contract BaseVaultManager is BaseManager { * @return An array of addresses representing the active vaults. */ function activeVaults(address operator) public view virtual returns (address[] memory) { - uint32 epoch = getCurrentEpoch(); - address[] memory activeSharedVaults = _sharedVaults.getActive(epoch); - address[] memory activeOperatorVaults = _operatorVaults[operator].getActive(epoch); + uint48 timestamp = getCaptureTimestamp(); + address[] memory activeSharedVaults = _sharedVaults.getActive(timestamp); + address[] memory activeOperatorVaults = _operatorVaults[operator].getActive(timestamp); uint256 activeSharedVaultsLen = activeSharedVaults.length; address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults.length); @@ -184,34 +205,59 @@ abstract contract BaseVaultManager is BaseManager { /* * @notice Checks if a given vault was active at a specified epoch. - * @param epoch The epoch to check. + * @param timestamp The timestamp to check. * @param operator The address of operator. * @param vault The vault to check. * @return A boolean indicating whether the vault was active at the specified epoch. */ - function vaultWasActiveAt(uint32 epoch, address operator, address vault) public view returns (bool) { - return sharedVaultWasActiveAt(epoch, vault) || operatorVaultWasActiveAt(epoch, operator, vault); + function vaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { + return sharedVaultWasActiveAt(timestamp, vault) || operatorVaultWasActiveAt(timestamp, operator, vault); } /* * @notice Checks if a given shared vault was active at a specified epoch. - * @param epoch The epoch to check. + * @param timestamp The timestamp to check. * @param vault The vault to check. * @return A boolean indicating whether the shared vault was active at the specified epoch. */ - function sharedVaultWasActiveAt(uint32 epoch, address vault) public view returns (bool) { - return _sharedVaults.wasActiveAt(epoch, vault); + function sharedVaultWasActiveAt(uint48 timestamp, address vault) public view returns (bool) { + return _sharedVaults.wasActiveAt(timestamp, vault); } /* * @notice Checks if a given shared vault was active at a specified epoch. - * @param epoch The epoch to check. + * @param timestamp The timestamp to check. * @param operator The address of operator. * @param vault The vault to check. * @return A boolean indicating whether the shared vault was active at the specified epoch. */ - function operatorVaultWasActiveAt(uint32 epoch, address operator, address vault) public view returns (bool) { - return _operatorVaults[operator].wasActiveAt(epoch, vault); + function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { + return _operatorVaults[operator].wasActiveAt(timestamp, vault); + } + + /* + * @notice Returns the stake of an operator for a specific vault and subnetwork at current epoch. + * @param operator The address of the operator. + * @param vault The address of the vault. + * @param subnetwork The subnetwork identifier. + * @return The stake of the operator. + */ + function getOperatorStake(address operator, address vault, uint96 subnetwork) public view returns (uint256) { + uint48 timestamp = getCaptureTimestamp(); + bytes32 subnetworkId = NETWORK.subnetwork(subnetwork); + return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); + } + + /* + * @notice Returns the power of an operator for a specific vault and subnetwork at current epoch. + * @param operator The address of the operator. + * @param vault The address of the vault. + * @param subnetwork The subnetwork identifier. + * @return The power of the operator. + */ + function getOperatorPower(address operator, address vault, uint96 subnetwork) public view returns (uint256) { + uint256 stake = getOperatorStake(operator, vault, subnetwork); + return stakeToPower(vault, stake); } /* @@ -220,15 +266,14 @@ abstract contract BaseVaultManager is BaseManager { * @return The stake of the operator. */ function getOperatorStake(address operator) public view virtual returns (uint256 stake) { - uint48 timestamp = getCurrentEpochStart(); + uint48 timestamp = getCaptureTimestamp(); address[] memory vaults = activeVaults(operator); uint160[] memory subnetworks = activeSubnetworks(); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; for (uint256 j; j < subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); - stake += IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); + stake += getOperatorStake(operator, vault, uint96(subnetworks[j])); } } @@ -241,16 +286,14 @@ abstract contract BaseVaultManager is BaseManager { * @return The power of the operator. */ function getOperatorPower(address operator) public view virtual returns (uint256 power) { - uint48 timestamp = getCurrentEpochStart(); + uint48 timestamp = getCaptureTimestamp(); address[] memory vaults = activeVaults(operator); uint160[] memory subnetworks = activeSubnetworks(); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; for (uint256 j; j < subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); - uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetwork, operator, timestamp, ""); - power += stakeToPower(vault, stake); + power += getOperatorPower(operator, vault, uint96(subnetworks[j])); } } @@ -290,7 +333,7 @@ abstract contract BaseVaultManager is BaseManager { * @param subnetwork The identifier of the subnetwork to register. */ function _registerSubnetwork(uint96 subnetwork) internal { - _subnetworks.register(getCurrentEpoch(), uint160(subnetwork)); + _subnetworks.register(getCaptureTimestamp(), uint160(subnetwork)); } /* @@ -298,7 +341,7 @@ abstract contract BaseVaultManager is BaseManager { * @param subnetwork The identifier of the subnetwork to pause. */ function _pauseSubnetwork(uint96 subnetwork) internal { - _subnetworks.pause(getCurrentEpoch(), uint160(subnetwork)); + _subnetworks.pause(getCaptureTimestamp(), uint160(subnetwork)); } /* @@ -306,7 +349,7 @@ abstract contract BaseVaultManager is BaseManager { * @param subnetwork The identifier of the subnetwork to unpause. */ function _unpauseSubnetwork(uint96 subnetwork) internal { - _subnetworks.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); + _subnetworks.unpause(getCaptureTimestamp(), SLASHING_WINDOW, uint160(subnetwork)); } /* @@ -314,7 +357,7 @@ abstract contract BaseVaultManager is BaseManager { * @param subnetwork The identifier of the subnetwork to unregister. */ function _unregisterSubnetwork(uint96 subnetwork) internal { - _subnetworks.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, uint160(subnetwork)); + _subnetworks.unregister(getCaptureTimestamp(), SLASHING_WINDOW, uint160(subnetwork)); } /* @@ -323,10 +366,7 @@ abstract contract BaseVaultManager is BaseManager { */ function _registerSharedVault(address vault) internal { _validateVault(vault); - if (_allOperatorVaults.get(vault) > 0) { - revert VaultAlreadyRegistred(); - } - _sharedVaults.register(getCurrentEpoch(), vault); + _sharedVaults.register(getCaptureTimestamp(), vault); } /* @@ -339,8 +379,8 @@ abstract contract BaseVaultManager is BaseManager { if (_sharedVaults.contains(vault)) { revert VaultAlreadyRegistred(); } - _operatorVaults[operator].register(getCurrentEpoch(), vault); - _allOperatorVaults.set(vault, _allOperatorVaults.get(vault) + 2); + _operatorVaults[operator].register(getCaptureTimestamp(), vault); + _vaultOperator.set(vault, operator); } /* @@ -348,7 +388,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to pause. */ function _pauseSharedVault(address vault) internal { - _sharedVaults.pause(getCurrentEpoch(), vault); + _sharedVaults.pause(getCaptureTimestamp(), vault); } /* @@ -356,7 +396,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to unpause. */ function _unpauseSharedVault(address vault) internal { - _sharedVaults.unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); + _sharedVaults.unpause(getCaptureTimestamp(), SLASHING_WINDOW, vault); } /* @@ -365,8 +405,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to pause. */ function _pauseOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].pause(getCurrentEpoch(), vault); - _allOperatorVaults.set(vault, _allOperatorVaults.get(vault) - 1); + _operatorVaults[operator].pause(getCaptureTimestamp(), vault); } /* @@ -375,8 +414,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to unpause. */ function _unpauseOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].unpause(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); - _allOperatorVaults.set(vault, _allOperatorVaults.get(vault) + 1); + _operatorVaults[operator].unpause(getCaptureTimestamp(), SLASHING_WINDOW, vault); } /* @@ -384,7 +422,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to unregister. */ function _unregisterSharedVault(address vault) internal { - _sharedVaults.unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); + _sharedVaults.unregister(getCaptureTimestamp(), SLASHING_WINDOW, vault); } /* @@ -393,11 +431,8 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to unregister. */ function _unregisterOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].unregister(getCurrentEpoch(), IMMUTABLE_EPOCHS, vault); - _allOperatorVaults.set(vault, _allOperatorVaults.get(vault) - 1); - if (_allOperatorVaults.get(vault) == 0) { - _allOperatorVaults.remove(vault); - } + _operatorVaults[operator].unregister(getCaptureTimestamp(), SLASHING_WINDOW, vault); + _vaultOperator.remove(vault); } /* @@ -422,7 +457,7 @@ abstract contract BaseVaultManager is BaseManager { revert NotOperatorVault(); } - if (!vaultWasActiveAt(getEpochAt(timestamp), operator, vault)) { + if (!vaultWasActiveAt(timestamp, operator, vault)) { revert InactiveVaultSlash(); } @@ -477,6 +512,10 @@ abstract contract BaseVaultManager is BaseManager { revert VaultNotInitialized(); } + if (_vaultOperator.contains(vault)) { + revert VaultAlreadyRegistred(); + } + uint48 vaultEpoch = IVault(vault).epochDuration(); address slasher = IVault(vault).slasher(); diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 064169a..1f1ca17 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -25,9 +25,8 @@ library PauseableEnumerableSet { */ struct Inner { uint160 value; // The actual value. - uint32 enabledEpoch; // Epoch when the value was enabled. - uint32 disabledEpoch; // Epoch when the value was disabled. - uint32 prevEnabledEpoch; // Epoch when the value was enabled before disabled. + uint48 enabledTimestamp; // Timestamp when the value was enabled. + uint48 disabledTimestamp; // Timestamp when the value was disabled. } // Custom error messages @@ -51,21 +50,21 @@ library PauseableEnumerableSet { * @notice Returns the address and its active period at a given position in the AddressSet. * @param self The AddressSet storage. * @param pos The position in the set. - * @return The address, enabled epoch, disabled epoch and enabled before dsiabled epoch at the position. + * @return The address, enabled timestamp and disabled timestamp at the position. */ - function at(AddressSet storage self, uint256 pos) internal view returns (address, uint32, uint32, uint32) { - (uint160 value, uint32 enabledEpoch, uint32 disabledEpoch, uint32 prevEnabled) = self.set.at(pos); - return (address(value), enabledEpoch, disabledEpoch, prevEnabled); + function at(AddressSet storage self, uint256 pos) internal view returns (address, uint48, uint48) { + (uint160 value, uint48 enabledTimestamp, uint48 disabledTimestamp) = self.set.at(pos); + return (address(value), enabledTimestamp, disabledTimestamp); } /* - * @notice Retrieves all active addresses at a given epoch. + * @notice Retrieves all active addresses at a given timestamp. * @param self The AddressSet storage. - * @param epoch The epoch to check. + * @param timestamp The timestamp to check. * @return An array of active addresses. */ - function getActive(AddressSet storage self, uint32 epoch) internal view returns (address[] memory array) { - uint160[] memory uint160Array = self.set.getActive(epoch); + function getActive(AddressSet storage self, uint48 timestamp) internal view returns (address[] memory array) { + uint160[] memory uint160Array = self.set.getActive(timestamp); assembly ("memory-safe") { array := uint160Array @@ -75,55 +74,55 @@ library PauseableEnumerableSet { } /* - * @notice Checks if a given addr was active at a specified epoch. - * @param epoch The epoch to check. + * @notice Checks if a given addr was active at a specified timestamp. + * @param timestamp The timestamp to check. * @param addr The address to check. - * @return A boolean indicating whether the addr was active at the specified epoch. + * @return A boolean indicating whether the addr was active at the specified timestamp. */ - function wasActiveAt(AddressSet storage self, uint32 epoch, address addr) internal view returns (bool) { - return self.set.wasActiveAt(epoch, uint160(addr)); + function wasActiveAt(AddressSet storage self, uint48 timestamp, address addr) internal view returns (bool) { + return self.set.wasActiveAt(timestamp, uint160(addr)); } /* - * @notice Registers a new address at a given epoch. + * @notice Registers a new address at a given timestamp. * @param self The AddressSet storage. - * @param epoch The epoch when the address is added. + * @param timestamp The timestamp when the address is added. * @param addr The address to register. */ - function register(AddressSet storage self, uint32 epoch, address addr) internal { - self.set.register(epoch, uint160(addr)); + function register(AddressSet storage self, uint48 timestamp, address addr) internal { + self.set.register(timestamp, uint160(addr)); } /* - * @notice Pauses an address at a given epoch. + * @notice Pauses an address at a given timestamp. * @param self The AddressSet storage. - * @param epoch The epoch when the address is paused. + * @param timestamp The timestamp when the address is paused. * @param addr The address to pause. */ - function pause(AddressSet storage self, uint32 epoch, address addr) internal { - self.set.pause(epoch, uint160(addr)); + function pause(AddressSet storage self, uint48 timestamp, address addr) internal { + self.set.pause(timestamp, uint160(addr)); } /* * @notice Unpauses an address, re-enabling it after the immutable period. * @param self The AddressSet storage. - * @param epoch The current epoch. - * @param immutableEpochs The required immutable period before unpausing. + * @param timestamp The current timestamp. + * @param immutablePeriod The required immutable period before unpausing. * @param addr The address to unpause. */ - function unpause(AddressSet storage self, uint32 epoch, uint32 immutableEpochs, address addr) internal { - self.set.unpause(epoch, immutableEpochs, uint160(addr)); + function unpause(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address addr) internal { + self.set.unpause(timestamp, immutablePeriod, uint160(addr)); } /* * @notice Unregisters an address, removing it from the set. * @param self The AddressSet storage. - * @param epoch The current epoch. - * @param immutableEpochs The required immutable period before unregistering. + * @param timestamp The current timestamp. + * @param immutablePeriod The required immutable period before unregistering. * @param addr The address to unregister. */ - function unregister(AddressSet storage self, uint32 epoch, uint32 immutableEpochs, address addr) internal { - self.set.unregister(epoch, immutableEpochs, uint160(addr)); + function unregister(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address addr) internal { + self.set.unregister(timestamp, immutablePeriod, uint160(addr)); } /* @@ -149,23 +148,23 @@ library PauseableEnumerableSet { * @notice Returns the value and its active period at a given position in the Uint160Set. * @param self The Uint160Set storage. * @param pos The position in the set. - * @return The value, enabled epoch, disabled epoch and enabled before disabled epoch at the position. + * @return The value, enabled timestamp and disabled timestamp at the position. */ - function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint32, uint32, uint32) { + function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint48, uint48) { return self.array[pos].get(); } /* - * @notice Retrieves all active values at a given epoch. + * @notice Retrieves all active values at a given timestamp. * @param self The Uint160Set storage. - * @param epoch The epoch to check. + * @param timestamp The timestamp to check. * @return An array of active values. */ - function getActive(Uint160Set storage self, uint32 epoch) internal view returns (uint160[] memory) { + function getActive(Uint160Set storage self, uint48 timestamp) internal view returns (uint160[] memory) { uint160[] memory array = new uint160[](self.array.length); uint256 len = 0; for (uint256 i; i < self.array.length; ++i) { - if (!self.array[i].wasActiveAt(epoch)) { + if (!self.array[i].wasActiveAt(timestamp)) { continue; } array[len++] = self.array[i].value; @@ -179,80 +178,80 @@ library PauseableEnumerableSet { } /* - * @notice Checks if a given value was active at a specified epoch. - * @param epoch The epoch to check. + * @notice Checks if a given value was active at a specified timestamp. + * @param timestamp The timestamp to check. * @param value The value to check. - * @return A boolean indicating whether the value was active at the specified epoch. + * @return A boolean indicating whether the value was active at the specified timestamp. */ - function wasActiveAt(Uint160Set storage self, uint32 epoch, uint160 value) internal view returns (bool) { + function wasActiveAt(Uint160Set storage self, uint48 timestamp, uint160 value) internal view returns (bool) { if (self.positions[value] == 0) { return false; } - return self.array[self.positions[value] - 1].wasActiveAt(epoch); + return self.array[self.positions[value] - 1].wasActiveAt(timestamp); } /* - * @notice Registers a new Uint160 value at a given epoch. + * @notice Registers a new Uint160 value at a given timestamp. * @param self The Uint160Set storage. - * @param epoch The epoch when the value is added. + * @param timestamp The timestamp when the value is added. * @param value The Uint160 value to register. */ - function register(Uint160Set storage self, uint32 epoch, uint160 value) internal { + function register(Uint160Set storage self, uint48 timestamp, uint160 value) internal { if (self.positions[value] != 0) { revert AlreadyRegistered(); } uint256 pos = self.array.length; Inner storage element = self.array.push(); - element.set(epoch, value); + element.set(timestamp, value); self.positions[value] = pos + 1; } /* - * @notice Pauses a Uint160 value at a given epoch. + * @notice Pauses a Uint160 value at a given timestamp. * @param self The Uint160Set storage. - * @param epoch The epoch when the value is paused. + * @param timestamp The timestamp when the value is paused. * @param value The Uint160 value to pause. */ - function pause(Uint160Set storage self, uint32 epoch, uint160 value) internal { + function pause(Uint160Set storage self, uint48 timestamp, uint160 value) internal { if (self.positions[value] == 0) { revert NotRegistered(); } - self.array[self.positions[value] - 1].disable(epoch); + self.array[self.positions[value] - 1].disable(timestamp); } /* * @notice Unpauses a Uint160 value after the immutable period. * @param self The Uint160Set storage. - * @param epoch The current epoch. - * @param immutableEpochs The required immutable period before unpausing. + * @param timestamp The current timestamp. + * @param immutablePeriod The required immutable period before unpausing. * @param value The Uint160 value to unpause. */ - function unpause(Uint160Set storage self, uint32 epoch, uint32 immutableEpochs, uint160 value) internal { + function unpause(Uint160Set storage self, uint48 timestamp, uint48 immutablePeriod, uint160 value) internal { if (self.positions[value] == 0) { revert NotRegistered(); } - self.array[self.positions[value] - 1].validateUnpause(epoch, immutableEpochs); - self.array[self.positions[value] - 1].enable(epoch); + self.array[self.positions[value] - 1].validateUnpause(timestamp, immutablePeriod); + self.array[self.positions[value] - 1].enable(timestamp); } /* * @notice Unregisters a Uint160 value from the set. * @param self The Uint160Set storage. - * @param epoch The current epoch. - * @param immutableEpochs The required immutable period before unregistering. + * @param timestamp The current timestamp. + * @param immutablePeriod The required immutable period before unregistering. * @param value The Uint160 value to unregister. */ - function unregister(Uint160Set storage self, uint32 epoch, uint32 immutableEpochs, uint160 value) internal { + function unregister(Uint160Set storage self, uint48 timestamp, uint48 immutablePeriod, uint160 value) internal { if (self.positions[value] == 0) { revert NotRegistered(); } uint256 pos = self.positions[value] - 1; - self.array[pos].validateUnregister(epoch, immutableEpochs); + self.array[pos].validateUnregister(timestamp, immutablePeriod); if (self.array.length == 1 || self.array.length == pos + 1) { delete self.positions[value]; @@ -287,99 +286,97 @@ library PauseableEnumerableSet { } /* - * @notice @notice Returns the value and its active period from the Inner struct. + * @notice Returns the value and its active period from the Inner struct. * @param self The Inner struct. - * @return The value, enabled epoch, disabled epoch and enabled before disabled epoch. + * @return The value, enabled timestamp and disabled timestamp. */ - function get(Inner storage self) internal view returns (uint160, uint32, uint32, uint32) { - return (self.value, self.enabledEpoch, self.disabledEpoch, self.prevEnabledEpoch); + function get(Inner storage self) internal view returns (uint160, uint48, uint48) { + return (self.value, self.enabledTimestamp, self.disabledTimestamp); } /* - * @notice Sets the value and marks it as enabled at a given epoch. + * @notice Sets the value and marks it as enabled at a given timestamp. * @param self The Inner struct. - * @param epoch The epoch when the value is set. + * @param timestamp The timestamp when the value is set. * @param value The Uint160 value to store. */ - function set(Inner storage self, uint32 epoch, uint160 value) internal { + function set(Inner storage self, uint48 timestamp, uint160 value) internal { self.value = value; - self.enabledEpoch = epoch + 1; + self.enabledTimestamp = timestamp; } /* - * @notice Sets the address and marks it as enabled at a given epoch. + * @notice Sets the address and marks it as enabled at a given timestamp. * @param self The Inner struct. - * @param epoch The epoch when the address is set. + * @param timestamp The timestamp when the address is set. * @param addr The address to store. */ - function set(Inner storage self, uint32 epoch, address addr) internal { + function set(Inner storage self, uint48 timestamp, address addr) internal { self.value = uint160(addr); - self.enabledEpoch = epoch + 1; + self.enabledTimestamp = timestamp; } /* - * @notice Enables the value at a given epoch. + * @notice Enables the value at a given timestamp. * @param self The Inner struct. - * @param epoch The epoch when the value is enabled. + * @param timestamp The timestamp when the value is enabled. */ - function enable(Inner storage self, uint32 epoch) internal { - if (self.enabledEpoch != 0) { + function enable(Inner storage self, uint48 timestamp) internal { + if (self.enabledTimestamp != 0) { revert AlreadyEnabled(); } - self.enabledEpoch = epoch + 1; + self.enabledTimestamp = timestamp; } /* - * @notice Disables the value at a given epoch. + * @notice Disables the value at a given timestamp. * @param self The Inner struct. - * @param epoch The epoch when the value is disabled. + * @param timestamp The timestamp when the value is disabled. */ - function disable(Inner storage self, uint32 epoch) internal { - if (self.enabledEpoch == 0) { + function disable(Inner storage self, uint48 timestamp) internal { + if (self.enabledTimestamp == 0) { revert NotEnabled(); } - self.prevEnabledEpoch = self.enabledEpoch; - self.enabledEpoch = 0; - self.disabledEpoch = epoch; + self.enabledTimestamp = 0; + self.disabledTimestamp = timestamp; } /* - * @notice Checks if the value was active at a given epoch. + * @notice Checks if the value was active at a given timestamp. * @param self The Inner struct. - * @param epoch The epoch to check. - * @return True if the value was active at the epoch, false otherwise. + * @param timestamp The timestamp to check. + * @return True if the value was active at the timestamp, false otherwise. */ - function wasActiveAt(Inner storage self, uint32 epoch) internal view returns (bool) { - return (self.enabledEpoch != 0 && self.enabledEpoch <= epoch) - || (self.disabledEpoch >= epoch && self.prevEnabledEpoch <= epoch); + function wasActiveAt(Inner storage self, uint48 timestamp) internal view returns (bool) { + return self.enabledTimestamp != 0 && self.enabledTimestamp <= timestamp; } /* - * @notice Validates whether the value can be unpaused at a given epoch. + * @notice Validates whether the value can be unpaused at a given timestamp. * @param self The Inner struct. - * @param epoch The current epoch. - * @param immutableEpochs The immutable period that must pass before unpausing. + * @param timestamp The current timestamp. + * @param immutablePeriod The immutable period that must pass before unpausing. */ - function validateUnpause(Inner storage self, uint32 epoch, uint32 immutableEpochs) internal view { - if (self.disabledEpoch + immutableEpochs - 1 > epoch) { + function validateUnpause(Inner storage self, uint48 timestamp, uint48 immutablePeriod) internal view { + if (self.disabledTimestamp + immutablePeriod > timestamp) { revert ImmutablePeriodNotPassed(); } } /* - * @notice Validates whether the value can be unregistered at a given epoch. + * @notice Validates whether the value can be unregistered at a given timestamp. * @param self The Inner struct. - * @param epoch The current epoch. - * @param immutableEpochs The immutable period that must pass before unregistering. + * @param timestamp The current timestamp. + * @param immutablePeriod The immutable period that must pass before unregistering. */ - function validateUnregister(Inner storage self, uint32 epoch, uint32 immutableEpochs) internal view { - if (self.enabledEpoch != 0 || self.disabledEpoch == 0) { + function validateUnregister(Inner storage self, uint48 timestamp, uint48 immutablePeriod) internal view { + if (self.enabledTimestamp != 0 || self.disabledTimestamp == 0) { revert Enabled(); } - if (self.disabledEpoch + immutableEpochs > epoch) { + if (self.disabledTimestamp + immutablePeriod > timestamp) { revert ImmutablePeriodNotPassed(); } } From 25b08993371634e1583c0b4b3bfb332dafb4275f Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Sun, 10 Nov 2024 13:54:03 +0700 Subject: [PATCH 031/115] WIP: fixed almost all compilation issues --- src/BaseManager.sol | 2 +- .../SimplePosMiddleware.sol} | 50 ++++-- .../SqrtTaskMiddleware.sol | 13 +- .../BaseBLSKeyManager.sol | 0 .../BaseKeyManager.sol | 2 +- .../DefaultBLSKeyManager.sol | 0 .../DefaultKeyManager.sol | 0 .../BaseOperatorManager.sol | 8 +- .../DefaultOperatorManager.sol | 0 .../BaseVaultManager.sol | 146 +++++++++++++----- .../DefaultVaultManager.sol | 0 test/DefaultSDK.t.sol | 123 +++++++-------- test/mocks/ExtendedSimpleMiddleware.sol | 19 --- test/mocks/ExtendedSimplePosMiddleware.sol | 20 +++ 14 files changed, 239 insertions(+), 144 deletions(-) rename src/examples/{SimpleMiddleware.sol => simple-pos-network/SimplePosMiddleware.sol} (77%) rename src/examples/{ => sqrt-task-network}/SqrtTaskMiddleware.sol (90%) rename src/{KeyManagers => key-manager}/BaseBLSKeyManager.sol (100%) rename src/{KeyManagers => key-manager}/BaseKeyManager.sol (98%) rename src/{KeyManagers => key-manager}/DefaultBLSKeyManager.sol (100%) rename src/{KeyManagers => key-manager}/DefaultKeyManager.sol (100%) rename src/{OperatorManagers => operator-manager}/BaseOperatorManager.sol (93%) rename src/{OperatorManagers => operator-manager}/DefaultOperatorManager.sol (100%) rename src/{VaultManagers => vault-manager}/BaseVaultManager.sol (79%) rename src/{VaultManagers => vault-manager}/DefaultVaultManager.sol (100%) delete mode 100644 test/mocks/ExtendedSimpleMiddleware.sol create mode 100644 test/mocks/ExtendedSimplePosMiddleware.sol diff --git a/src/BaseManager.sol b/src/BaseManager.sol index 56f5df3..c86212e 100644 --- a/src/BaseManager.sol +++ b/src/BaseManager.sol @@ -5,7 +5,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -abstract contract BaseManager is Initializable, OwnableUpgradeable { +contract BaseManager is Initializable, OwnableUpgradeable { address public NETWORK; // Address of the network uint48 public SLASHING_WINDOW; // Duration of the slashing window address public VAULT_REGISTRY; // Address of the vault registry diff --git a/src/examples/SimpleMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol similarity index 77% rename from src/examples/SimpleMiddleware.sol rename to src/examples/simple-pos-network/SimplePosMiddleware.sol index 7836439..7e750a0 100644 --- a/src/examples/SimpleMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -9,11 +9,11 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {DefaultVaultManager} from "../VaultManagers/DefaultVaultManager.sol"; -import {DefaultOperatorManager} from "../OperatorManagers/DefaultOperatorManager.sol"; -import {DefaultKeyManager} from "../KeyManagers/DefaultKeyManager.sol"; +import {DefaultVaultManager} from "../../vault-manager/DefaultVaultManager.sol"; +import {DefaultOperatorManager} from "../../operator-manager/DefaultOperatorManager.sol"; +import {DefaultKeyManager} from "../../key-manager/DefaultKeyManager.sol"; -contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager { +contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager { using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key @@ -26,6 +26,9 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul bytes32 key; // Key associated with the validator } + uint48 public immutable EPOCH_DURATION; // Duration of each epoch + uint48 public immutable START_TIMESTAMP; // Start timestamp of the network + /* * @notice Constructor for initializing the SimpleMiddleware contract. * @param network The address of the network. @@ -45,7 +48,34 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul uint48 epochDuration, uint48 slashingWindow ) { - initialize(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + initialize(owner, network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + EPOCH_DURATION = epochDuration; + START_TIMESTAMP = Time.timestamp(); + } + + /* + * @notice Returns the capture timestamp for the current epoch. + * @return The capture timestamp. + */ + function getCaptureTimestamp() public view virtual override returns (uint48 timestamp) { + return START_TIMESTAMP + (Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION; + } + + /* + * @notice Returns the start timestamp for a given epoch. + * @param epoch The epoch number. + * @return The start timestamp. + */ + function getEpochStart(uint48 epoch) public view returns (uint48) { + return START_TIMESTAMP + epoch * EPOCH_DURATION; + } + + /* + * @notice Returns the current epoch. + * @return The current epoch. + */ + function getCurrentEpoch() public view returns (uint48) { + return (Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION; } /* @@ -79,7 +109,7 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul address operator = operators[i]; // Get the operator address bytes32 key = operatorKey(operator); // Get the key for the operator - if (key == bytes32(0) || !keyWasActiveAt(getCurrentEpoch(), key)) { + if (key == bytes32(0) || !keyWasActiveAt(getCaptureTimestamp(), key)) { continue; // Skip if the key is inactive } @@ -104,7 +134,7 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul * @param slashHints Hints for the slashing process. * @return An array of SlashResponse indicating the results of the slashing. */ - function slash(uint32 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) + function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) public onlyOwner returns (SlashResponse[] memory slashResponses) @@ -124,9 +154,9 @@ contract SimpleMiddleware is DefaultVaultManager, DefaultOperatorManager, Defaul revert InactiveOperatorSlash(); // Revert if the operator wasn't active } - uint256 totalStake = getOperatorStake(operator); // Get the total stake for the operator - address[] memory vaults = activeVaults(operator); // Get active vaults for the operator - uint160[] memory subnetworks = activeSubnetworks(); // Get active subnetworks + uint256 totalStake = getOperatorStakeAt(operator, epochStart); // Get the total stake for the operator + address[] memory vaults = activeVaultsAt(epochStart, operator); // Get active vaults for the operator + uint160[] memory subnetworks = activeSubnetworksAt(epochStart); // Get active subnetworks slashResponses = new SlashResponse[](vaults.length * subnetworks.length); // Initialize the array for slash responses uint256 len = 0; // Length counter diff --git a/src/examples/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol similarity index 90% rename from src/examples/SqrtTaskMiddleware.sol rename to src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 871fb4b..0579e94 100644 --- a/src/examples/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -10,9 +10,9 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {DefaultVaultManager} from "../VaultManagers/DefaultVaultManager.sol"; -import {DefaultOperatorManager} from "../OperatorManagers/DefaultOperatorManager.sol"; -import {DefaultKeyManager} from "../KeyManagers/DefaultKeyManager.sol"; +import {DefaultVaultManager} from "../../vault-manager/DefaultVaultManager.sol"; +import {DefaultOperatorManager} from "../../operator-manager/DefaultOperatorManager.sol"; +import {DefaultKeyManager} from "../../key-manager/DefaultKeyManager.sol"; contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager, EIP712 { using Subnetwork for address; @@ -42,15 +42,14 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa address vaultRegistry, address operatorNetOptin, address owner, - uint48 epochDuration, uint48 slashingWindow ) EIP712("SqrtTaskMiddleware", "1") { - initialize(owner, network, epochDuration, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + initialize(owner, network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); } function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { taskIndex = tasks.length; - tasks.push(Task({captureTimestamp: Time.timestamp() - 1, value: value, operator: operator, completed: false})); + tasks.push(Task({captureTimestamp: getCaptureTimestamp(), value: value, operator: operator, completed: false})); emit CreateTask(taskIndex); } @@ -119,7 +118,7 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa function _slash(uint256 taskIndex, bytes[] calldata stakeHints, bytes[] calldata slashHints) private { Task storage task = tasks[taskIndex]; - address[] memory vaults = activeVaults(task.operator); + address[] memory vaults = activeVaultsAt(task.captureTimestamp, task.operator); if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { revert InvalidHints(); diff --git a/src/KeyManagers/BaseBLSKeyManager.sol b/src/key-manager/BaseBLSKeyManager.sol similarity index 100% rename from src/KeyManagers/BaseBLSKeyManager.sol rename to src/key-manager/BaseBLSKeyManager.sol diff --git a/src/KeyManagers/BaseKeyManager.sol b/src/key-manager/BaseKeyManager.sol similarity index 98% rename from src/KeyManagers/BaseKeyManager.sol rename to src/key-manager/BaseKeyManager.sol index b8fbd5e..1edd4e9 100644 --- a/src/KeyManagers/BaseKeyManager.sol +++ b/src/key-manager/BaseKeyManager.sol @@ -71,7 +71,7 @@ abstract contract BaseKeyManager is BaseManager { _keyData[key].set(timestamp, operator); } - if (keyUpdateEpoch[operator] != timestamp) { + if (keyUpdateTimestamp[operator] != timestamp) { prevKeys[operator] = keys[operator]; keyUpdateTimestamp[operator] = timestamp; } diff --git a/src/KeyManagers/DefaultBLSKeyManager.sol b/src/key-manager/DefaultBLSKeyManager.sol similarity index 100% rename from src/KeyManagers/DefaultBLSKeyManager.sol rename to src/key-manager/DefaultBLSKeyManager.sol diff --git a/src/KeyManagers/DefaultKeyManager.sol b/src/key-manager/DefaultKeyManager.sol similarity index 100% rename from src/KeyManagers/DefaultKeyManager.sol rename to src/key-manager/DefaultKeyManager.sol diff --git a/src/OperatorManagers/BaseOperatorManager.sol b/src/operator-manager/BaseOperatorManager.sol similarity index 93% rename from src/OperatorManagers/BaseOperatorManager.sol rename to src/operator-manager/BaseOperatorManager.sol index 24dce31..2c6361d 100644 --- a/src/OperatorManagers/BaseOperatorManager.sol +++ b/src/operator-manager/BaseOperatorManager.sol @@ -34,7 +34,7 @@ abstract contract BaseOperatorManager is BaseManager { * @param pos The index position in the operators array. * @return The address, enabled epoch, disabled epoch and enabled before disabled epoch of the operator. */ - function operatorWithTimesAt(uint256 pos) public view returns (address, uint32, uint32, uint32) { + function operatorWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { return _operators.at(pos); } @@ -77,7 +77,7 @@ abstract contract BaseOperatorManager is BaseManager { * @param operator The address of the operator to pause. */ function _pauseOperator(address operator) internal { - _operators.pause(getCurrentEpoch(), operator); + _operators.pause(Time.timestamp(), operator); } /* @@ -85,7 +85,7 @@ abstract contract BaseOperatorManager is BaseManager { * @param operator The address of the operator to unpause. */ function _unpauseOperator(address operator) internal { - _operators.unpause(getCurrentEpoch(), SLASHING_WINDOW, operator); + _operators.unpause(Time.timestamp(), SLASHING_WINDOW, operator); } /* @@ -93,6 +93,6 @@ abstract contract BaseOperatorManager is BaseManager { * @param operator The address of the operator to unregister. */ function _unregisterOperator(address operator) internal { - _operators.unregister(getCurrentEpoch(), SLASHING_WINDOW, operator); + _operators.unregister(Time.timestamp(), SLASHING_WINDOW, operator); } } diff --git a/src/OperatorManagers/DefaultOperatorManager.sol b/src/operator-manager/DefaultOperatorManager.sol similarity index 100% rename from src/OperatorManagers/DefaultOperatorManager.sol rename to src/operator-manager/DefaultOperatorManager.sol diff --git a/src/VaultManagers/BaseVaultManager.sol b/src/vault-manager/BaseVaultManager.sol similarity index 79% rename from src/VaultManagers/BaseVaultManager.sol rename to src/vault-manager/BaseVaultManager.sol index ccb7b89..56ac612 100644 --- a/src/VaultManagers/BaseVaultManager.sol +++ b/src/vault-manager/BaseVaultManager.sol @@ -27,12 +27,8 @@ abstract contract BaseVaultManager is BaseManager { error NotVault(); error NotOperatorVault(); error VaultNotInitialized(); - error VaultNotRegistered(); error VaultAlreadyRegistred(); error VaultEpochTooShort(); - error TooOldEpoch(); - error InvalidEpoch(); - error InvalidSubnetworksCnt(); error InactiveVaultSlash(); error UnknownSlasherType(); error NonVetoSlasher(); @@ -61,25 +57,33 @@ abstract contract BaseVaultManager is BaseManager { /* * @notice Returns the subnetwork information at a specified position. * @param pos The index of the subnetwork. - * @return The subnetwork details including address, enabled epoch, disabled epoch and enabled before disabled epoch. + * @return The subnetwork details including address and timing information. */ - function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint48, uint48, uint48) { + function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint48, uint48) { return _subnetworks.at(pos); } /* - * @notice Returns an array of active subnetworks for the current epoch. + * @notice Returns an array of active subnetworks. * @return An array of active subnetwork addresses. */ function activeSubnetworks() public view returns (uint160[] memory) { return _subnetworks.getActive(getCaptureTimestamp()); } + /* + * @notice Returns an array of active subnetworks. + * @return An array of active subnetwork addresses. + */ + function activeSubnetworksAt(uint48 timestamp) public view returns (uint160[] memory) { + return _subnetworks.getActive(timestamp); + } + /* - * @notice Checks if a given subnetwork was active at a specified epoch. + * @notice Checks if a given subnetwork was active at a specified timestamp. * @param timestamp The timestamp to check. * @param subnetwork The subnetwork to check. - * @return A boolean indicating whether the subnetwork was active at the specified epoch. + * @return A boolean indicating whether the subnetwork was active at the specified timestamp. */ function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) public view returns (bool) { return _subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); @@ -94,16 +98,16 @@ abstract contract BaseVaultManager is BaseManager { } /* - * @notice Returns the address and epoch information of a shared vault at a specific position. + * @notice Returns the address and timing information of a shared vault at a specific position. * @param pos The index position in the shared vaults array. - * @return The address, enabled epoch, disabled epoch and enabled before disabled epoch of the vault. + * @return The address and timing information of the vault. */ - function sharedVaultWithEpochsAt(uint256 pos) public view returns (address, uint48, uint48, uint48) { + function sharedVaultWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { return _sharedVaults.at(pos); } /* - * @notice Returns an array of active shared vaults for the current epoch. + * @notice Returns an array of active shared vaults. * @return An array of active shared vault addresses. */ function activeSharedVaults() public view returns (address[] memory) { @@ -120,21 +124,21 @@ abstract contract BaseVaultManager is BaseManager { } /* - * @notice Returns the address and epoch information of an operator vault at a specific position. + * @notice Returns the address and timing information of an operator vault at a specific position. * @param operator The address of the operator. * @param pos The index position in the operator vaults array. - * @return The address, enabled epoch, disabled epoch and enabled before disabled of the vault. + * @return The address and timing information of the vault. */ - function operatorVaultWithEpochsAt(address operator, uint256 pos) + function operatorVaultWithTimesAt(address operator, uint256 pos) public view - returns (address, uint48, uint48, uint48) + returns (address, uint48, uint48) { return _operatorVaults[operator].at(pos); } /* - * @notice Returns an array of active operator vaults for a specific operator in the current epoch. + * @notice Returns an array of active operator vaults for a specific operator. * @param operator The address of the operator. * @return An array of active operator vault addresses. */ @@ -153,17 +157,41 @@ abstract contract BaseVaultManager is BaseManager { } /* - * @notice Returns the list of network's active vaults for the current epoch. + * @notice Returns the list of network's active vaults. * @return An array of addresses representing the active vaults. */ function activeVaults() public view virtual returns (address[] memory) { uint48 timestamp = getCaptureTimestamp(); - address[] memory activeSharedVaults = _sharedVaults.getActive(timestamp); - uint256 len = activeSharedVaults.length; + address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); + uint256 len = activeSharedVaults_.length; + address[] memory vaults = new address[](len + _vaultOperator.length()); + + for (uint256 i; i < len; ++i) { + vaults[i] = activeSharedVaults_[i]; + } + + uint256 operatorVaultsLen = _vaultOperator.length(); + for (uint256 i; i < operatorVaultsLen; ++i) { + (address vault, address operator) = _vaultOperator.at(i); + if (_operatorVaults[operator].wasActiveAt(timestamp, vault)) { + vaults[len++] = vault; + } + } + + assembly ("memory-safe") { + mstore(vaults, len) + } + + return vaults; + } + + function activeVaultsAt(uint48 timestamp) public view virtual returns (address[] memory) { + address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); + uint256 len = activeSharedVaults_.length; address[] memory vaults = new address[](len + _vaultOperator.length()); for (uint256 i; i < len; ++i) { - vaults[i] = activeSharedVaults[i]; + vaults[i] = activeSharedVaults_[i]; } uint256 operatorVaultsLen = _vaultOperator.length(); @@ -188,55 +216,77 @@ abstract contract BaseVaultManager is BaseManager { */ function activeVaults(address operator) public view virtual returns (address[] memory) { uint48 timestamp = getCaptureTimestamp(); - address[] memory activeSharedVaults = _sharedVaults.getActive(timestamp); - address[] memory activeOperatorVaults = _operatorVaults[operator].getActive(timestamp); + address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); + address[] memory activeOperatorVaults_ = _operatorVaults[operator].getActive(timestamp); - uint256 activeSharedVaultsLen = activeSharedVaults.length; - address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults.length); + uint256 activeSharedVaultsLen = activeSharedVaults_.length; + address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults_.length); for (uint256 i; i < activeSharedVaultsLen; ++i) { - vaults[i] = activeSharedVaults[i]; + vaults[i] = activeSharedVaults_[i]; } - for (uint256 i; i < activeOperatorVaults.length; ++i) { - vaults[activeSharedVaultsLen + i] = activeOperatorVaults[i]; + for (uint256 i; i < activeOperatorVaults_.length; ++i) { + vaults[activeSharedVaultsLen + i] = activeOperatorVaults_[i]; } return vaults; } /* - * @notice Checks if a given vault was active at a specified epoch. + * @notice Returns the list of active vaults for a specific operator. + * @param timestamp The timestamp to check. + * @param operator The address of the operator. + * @return An array of addresses representing the active vaults. + */ + function activeVaultsAt(uint48 timestamp, address operator) public view virtual returns (address[] memory) { + address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); + address[] memory activeOperatorVaults_ = _operatorVaults[operator].getActive(timestamp); + + uint256 activeSharedVaultsLen = activeSharedVaults_.length; + address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults_.length); + for (uint256 i; i < activeSharedVaultsLen; ++i) { + vaults[i] = activeSharedVaults_[i]; + } + for (uint256 i; i < activeOperatorVaults_.length; ++i) { + vaults[activeSharedVaultsLen + i] = activeOperatorVaults_[i]; + } + + return vaults; + } + + /* + * @notice Checks if a given vault was active at a specified timestamp. * @param timestamp The timestamp to check. * @param operator The address of operator. * @param vault The vault to check. - * @return A boolean indicating whether the vault was active at the specified epoch. + * @return A boolean indicating whether the vault was active at the specified timestamp. */ function vaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { return sharedVaultWasActiveAt(timestamp, vault) || operatorVaultWasActiveAt(timestamp, operator, vault); } /* - * @notice Checks if a given shared vault was active at a specified epoch. + * @notice Checks if a given shared vault was active at a specified timestamp. * @param timestamp The timestamp to check. * @param vault The vault to check. - * @return A boolean indicating whether the shared vault was active at the specified epoch. + * @return A boolean indicating whether the shared vault was active at the specified timestamp. */ function sharedVaultWasActiveAt(uint48 timestamp, address vault) public view returns (bool) { return _sharedVaults.wasActiveAt(timestamp, vault); } /* - * @notice Checks if a given shared vault was active at a specified epoch. + * @notice Checks if a given operator vault was active at a specified timestamp. * @param timestamp The timestamp to check. * @param operator The address of operator. * @param vault The vault to check. - * @return A boolean indicating whether the shared vault was active at the specified epoch. + * @return A boolean indicating whether the operator vault was active at the specified timestamp. */ function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { return _operatorVaults[operator].wasActiveAt(timestamp, vault); } /* - * @notice Returns the stake of an operator for a specific vault and subnetwork at current epoch. + * @notice Returns the stake of an operator for a specific vault and subnetwork. * @param operator The address of the operator. * @param vault The address of the vault. * @param subnetwork The subnetwork identifier. @@ -249,7 +299,7 @@ abstract contract BaseVaultManager is BaseManager { } /* - * @notice Returns the power of an operator for a specific vault and subnetwork at current epoch. + * @notice Returns the power of an operator for a specific vault and subnetwork. * @param operator The address of the operator. * @param vault The address of the vault. * @param subnetwork The subnetwork identifier. @@ -261,7 +311,7 @@ abstract contract BaseVaultManager is BaseManager { } /* - * @notice Returns the stake of an operator at a current epoch. + * @notice Returns the stake of an operator. * @param operator The address of the operator. * @return The stake of the operator. */ @@ -280,8 +330,22 @@ abstract contract BaseVaultManager is BaseManager { return stake; } + function getOperatorStakeAt(address operator, uint48 timestamp) public view virtual returns (uint256 stake) { + address[] memory vaults = activeVaults(operator); + uint160[] memory subnetworks = activeSubnetworks(); + + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + for (uint256 j; j < subnetworks.length; ++j) { + stake += getOperatorStake(operator, vault, uint96(subnetworks[j])); + } + } + + return stake; + } + /* - * @notice Returns the power of an operator at a current epoch. + * @notice Returns the power of an operator. * @param operator The address of the operator. * @return The power of the operator. */ @@ -301,7 +365,7 @@ abstract contract BaseVaultManager is BaseManager { } /* - * @notice Returns the total stake of multiple operators at a current epoch. + * @notice Returns the total stake of multiple operators. * @param operators The list of operator addresses. * @return The total stake of the operators. */ @@ -315,7 +379,7 @@ abstract contract BaseVaultManager is BaseManager { } /* - * @notice Returns the total power of multiple operators at a current epoch. + * @notice Returns the total power of multiple operators. * @param operators The list of operator addresses. * @return The total power of the operators. */ diff --git a/src/VaultManagers/DefaultVaultManager.sol b/src/vault-manager/DefaultVaultManager.sol similarity index 100% rename from src/VaultManagers/DefaultVaultManager.sol rename to src/vault-manager/DefaultVaultManager.sol diff --git a/test/DefaultSDK.t.sol b/test/DefaultSDK.t.sol index 83c136e..c796310 100644 --- a/test/DefaultSDK.t.sol +++ b/test/DefaultSDK.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; -import {ExtendedSimpleMiddleware} from "./mocks/ExtendedSimpleMiddleware.sol"; +import {SimplePosMiddleware} from "../src/examples/simple-pos-network/SimplePosMiddleware.sol"; +import {ExtendedSimplePosMiddleware} from "./mocks/ExtendedSimplePosMiddleware.sol"; import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; @@ -21,7 +22,7 @@ contract DefaultSDKTest is POCBaseTest { address network = address(0x123); - ExtendedSimpleMiddleware internal middleware; + ExtendedSimplePosMiddleware internal middleware; uint48 internal epochDuration = 600; // 10 minutes uint48 internal slashingWindow = 1200; // 20 minutes @@ -36,7 +37,7 @@ contract DefaultSDKTest is POCBaseTest { _deposit(vault3, alice, 1000 ether); // Initialize middleware contract - middleware = new ExtendedSimpleMiddleware( + middleware = new ExtendedSimplePosMiddleware( address(network), address(operatorRegistry), address(vaultFactory), @@ -192,71 +193,71 @@ contract DefaultSDKTest is POCBaseTest { assertEq(operatorKey, newKey3, "Operator's key was not updated correctly"); } - function testBLSKeys() public { - bytes memory key = "key"; - address operator = address(0x1337); + // function testBLSKeys() public { + // bytes memory key = "key"; + // address operator = address(0x1337); - bytes memory operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, "", "Operator's BLS key should be empty"); - address keyOperator = middleware.operatorByBLSKey(key); - assertEq(keyOperator, address(0), "BLS key's operator should be empty"); + // bytes memory operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, "", "Operator's BLS key should be empty"); + // address keyOperator = middleware.operatorByBLSKey(key); + // assertEq(keyOperator, address(0), "BLS key's operator should be empty"); - middleware.updateBLSKey(operator, key); - keyOperator = middleware.operatorByBLSKey(key); - assertEq(keyOperator, operator, "BLS key's operator was not updated correctly"); + // middleware.updateBLSKey(operator, key); + // keyOperator = middleware.operatorByBLSKey(key); + // assertEq(keyOperator, operator, "BLS key's operator was not updated correctly"); - // applies on next epoch - operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, "", "Operator's BLS key should be empty"); + // // applies on next epoch + // operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, "", "Operator's BLS key should be empty"); - skipEpoch(); - operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, key, "Operator's BLS key was not updated correctly"); + // skipEpoch(); + // operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, key, "Operator's BLS key was not updated correctly"); - // update key - bytes memory newKey = "newKey"; - middleware.updateBLSKey(operator, newKey); + // // update key + // bytes memory newKey = "newKey"; + // middleware.updateBLSKey(operator, newKey); - // can't update already active bls key twice - vm.expectRevert(); - middleware.updateBLSKey(operator, newKey); + // // can't update already active bls key twice + // vm.expectRevert(); + // middleware.updateBLSKey(operator, newKey); - keyOperator = middleware.operatorByBLSKey(key); - assertEq(keyOperator, operator, "BLS key's operator was not updated correctly"); + // keyOperator = middleware.operatorByBLSKey(key); + // assertEq(keyOperator, operator, "BLS key's operator was not updated correctly"); - // applies on next epoch - operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, key, "Operator's BLS key should be previous key"); + // // applies on next epoch + // operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, key, "Operator's BLS key should be previous key"); - skipEpoch(); - operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, newKey, "Operator's BLS key was not updated correctly"); + // skipEpoch(); + // operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, newKey, "Operator's BLS key was not updated correctly"); - bytes memory zeroKey = ""; - middleware.updateBLSKey(operator, zeroKey); - operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, newKey, "Operator's BLS key should be previous key"); + // bytes memory zeroKey = ""; + // middleware.updateBLSKey(operator, zeroKey); + // operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, newKey, "Operator's BLS key should be previous key"); - skipEpoch(); - operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, zeroKey, "Operator's BLS key was not updated correctly"); + // skipEpoch(); + // operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, zeroKey, "Operator's BLS key was not updated correctly"); - // can't set used bls key to another operator - vm.expectRevert(); - middleware.updateBLSKey(address(0x123123), key); + // // can't set used bls key to another operator + // vm.expectRevert(); + // middleware.updateBLSKey(address(0x123123), key); - // should apply update to latest updated bls key - bytes memory newKey2 = "newKey2"; - bytes memory newKey3 = "newKey3"; - middleware.updateBLSKey(operator, newKey2); - middleware.updateBLSKey(operator, newKey3); - operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, zeroKey, "Operator's BLS key should be previous key"); + // // should apply update to latest updated bls key + // bytes memory newKey2 = "newKey2"; + // bytes memory newKey3 = "newKey3"; + // middleware.updateBLSKey(operator, newKey2); + // middleware.updateBLSKey(operator, newKey3); + // operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, zeroKey, "Operator's BLS key should be previous key"); - skipEpoch(); - operatorKey = middleware.operatorBLSKey(operator); - assertEq(operatorKey, newKey3, "Operator's BLS key was not updated correctly"); - } + // skipEpoch(); + // operatorKey = middleware.operatorBLSKey(operator); + // assertEq(operatorKey, newKey3, "Operator's BLS key was not updated correctly"); + // } function testSubnetworks() public { skipEpoch(); // let first 0 subnetwork activate @@ -421,7 +422,7 @@ contract DefaultSDKTest is POCBaseTest { middleware.updateKey(operator1, key1); middleware.updateKey(operator2, key2); - ExtendedSimpleMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); + SimplePosMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); assertEq(validatorSet.length, 0, "valset length should be 0"); skipEpoch(); @@ -430,7 +431,7 @@ contract DefaultSDKTest is POCBaseTest { validatorSet = middleware.getValidatorSet(); assertEq(validatorSet.length, 2, "valset length should be 2"); for (uint256 i = 0; i < validatorSet.length; i++) { - ExtendedSimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + SimplePosMiddleware.ValidatorData memory validator = validatorSet[i]; if (validator.key == key1) { assertEq(validator.power, 1000 ether, "validator1 power should be 1000"); } else if (validator.key == key2) { @@ -462,7 +463,7 @@ contract DefaultSDKTest is POCBaseTest { validatorSet = middleware.getValidatorSet(); assertEq(validatorSet.length, 2, "valset length should be 2"); for (uint256 i = 0; i < validatorSet.length; i++) { - ExtendedSimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + SimplePosMiddleware.ValidatorData memory validator = validatorSet[i]; if (validator.key == key1) { assertEq(validator.power, 500 ether, "validator1 power should be 1000"); } else if (validator.key == key2) { @@ -477,7 +478,7 @@ contract DefaultSDKTest is POCBaseTest { validatorSet = middleware.getValidatorSet(); assertEq(validatorSet.length, 2, "valset length should be 2"); for (uint256 i = 0; i < validatorSet.length; i++) { - ExtendedSimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + SimplePosMiddleware.ValidatorData memory validator = validatorSet[i]; if (validator.key == key1) { assertEq(validator.power, 500 ether, "validator1 power should be 1000"); } else if (validator.key == key2) { @@ -490,7 +491,7 @@ contract DefaultSDKTest is POCBaseTest { validatorSet = middleware.getValidatorSet(); assertEq(validatorSet.length, 2, "valset length should be 2"); for (uint256 i = 0; i < validatorSet.length; i++) { - ExtendedSimpleMiddleware.ValidatorData memory validator = validatorSet[i]; + SimplePosMiddleware.ValidatorData memory validator = validatorSet[i]; if (validator.key == key1) { assertEq(validator.power, 500 ether, "validator1 power should be 1000"); } else if (validator.key == key2) { @@ -554,7 +555,7 @@ contract DefaultSDKTest is POCBaseTest { bytes[] memory slashHints = new bytes[](stakeHints.length); slashHints[0] = ""; - uint32 epoch = middleware.getCurrentEpoch(); + uint48 epoch = middleware.getCurrentEpoch(); uint256 amount = 100 ether; // Perform a slash on operator1 diff --git a/test/mocks/ExtendedSimpleMiddleware.sol b/test/mocks/ExtendedSimpleMiddleware.sol deleted file mode 100644 index 38dbfd8..0000000 --- a/test/mocks/ExtendedSimpleMiddleware.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.25; - -import {SimpleMiddleware} from "../../src/examples/SimpleMiddleware.sol"; -import {DefaultBLSKeyManager} from "../../src/KeyManagers/DefaultBLSKeyManager.sol"; - -contract ExtendedSimpleMiddleware is SimpleMiddleware, DefaultBLSKeyManager { - constructor( - address network, - address operatorRegistry, - address vaultRegistry, - address operatorNetOptin, - address owner, - uint48 epochDuration, - uint48 slashingWindow - ) - SimpleMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, owner, epochDuration, slashingWindow) - {} -} diff --git a/test/mocks/ExtendedSimplePosMiddleware.sol b/test/mocks/ExtendedSimplePosMiddleware.sol new file mode 100644 index 0000000..7665263 --- /dev/null +++ b/test/mocks/ExtendedSimplePosMiddleware.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {SimplePosMiddleware} from "../../src/examples/simple-pos-network/SimplePosMiddleware.sol"; +import {DefaultKeyManager} from "../../src/key-manager/DefaultKeyManager.sol"; + +contract ExtendedSimplePosMiddleware is SimplePosMiddleware { + constructor( + address network, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + address owner, + uint48 epochDuration, + uint48 slashingWindow + ) + SimplePosMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, owner, epochDuration, slashingWindow) + {} + +} From 2036ec1a0ed3eb6c67895dce9f33b8dbcbee3e0a Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Sun, 10 Nov 2024 14:33:27 +0700 Subject: [PATCH 032/115] WIP: bumped solc version up to 0.8.26 to avoid compilation error --- foundry.toml | 1 + src/BaseManager.sol | 2 +- .../SimplePosMiddleware.sol | 2 +- .../sqrt-task-network/SqrtTaskMiddleware.sol | 10 +- src/key-manager/BaseBLSKeyManager.sol | 2 +- src/key-manager/BaseKeyManager.sol | 2 +- src/key-manager/DefaultBLSKeyManager.sol | 2 +- src/key-manager/DefaultKeyManager.sol | 2 +- src/operator-manager/BaseOperatorManager.sol | 13 +- .../DefaultOperatorManager.sol | 2 +- src/vault-manager/BaseVaultManager.sol | 397 ++++++++++-------- src/vault-manager/DefaultVaultManager.sol | 2 +- test/mocks/ExtendedSimplePosMiddleware.sol | 2 +- 13 files changed, 256 insertions(+), 183 deletions(-) diff --git a/foundry.toml b/foundry.toml index 99d155a..529f204 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,4 +1,5 @@ [profile.default] +solc-version = "0.8.26" src = "src" out = "out" libs = ["lib"] diff --git a/src/BaseManager.sol b/src/BaseManager.sol index c86212e..522fe57 100644 --- a/src/BaseManager.sol +++ b/src/BaseManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 7e750a0..70ab94a 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 0579e94..356cf58 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; @@ -142,28 +142,28 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa /* * inheritdoc BaseMiddleware */ - function registerSubnetwork(uint96 subnetwork) public pure override { + function registerSubnetwork(uint96) public pure override { revert(); } /* * inheritdoc BaseMiddleware */ - function pauseSubnetwork(uint96 subnetwork) public pure override { + function pauseSubnetwork(uint96) public pure override { revert(); } /* * inheritdoc BaseMiddleware */ - function unpauseSubnetwork(uint96 subnetwork) public pure override { + function unpauseSubnetwork(uint96) public pure override { revert(); } /* * inheritdoc BaseMiddleware */ - function unregisterSubnetwork(uint96 subnetwork) public pure override { + function unregisterSubnetwork(uint96) public pure override { revert(); } } diff --git a/src/key-manager/BaseBLSKeyManager.sol b/src/key-manager/BaseBLSKeyManager.sol index ba08456..33a962d 100644 --- a/src/key-manager/BaseBLSKeyManager.sol +++ b/src/key-manager/BaseBLSKeyManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; diff --git a/src/key-manager/BaseKeyManager.sol b/src/key-manager/BaseKeyManager.sol index 1edd4e9..e94cf33 100644 --- a/src/key-manager/BaseKeyManager.sol +++ b/src/key-manager/BaseKeyManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; diff --git a/src/key-manager/DefaultBLSKeyManager.sol b/src/key-manager/DefaultBLSKeyManager.sol index a3bcf37..fa126f5 100644 --- a/src/key-manager/DefaultBLSKeyManager.sol +++ b/src/key-manager/DefaultBLSKeyManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {BaseBLSKeyManager} from "./BaseBLSKeyManager.sol"; diff --git a/src/key-manager/DefaultKeyManager.sol b/src/key-manager/DefaultKeyManager.sol index 32e9f2f..4fc11d5 100644 --- a/src/key-manager/DefaultKeyManager.sol +++ b/src/key-manager/DefaultKeyManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {BaseKeyManager} from "./BaseKeyManager.sol"; diff --git a/src/operator-manager/BaseOperatorManager.sol b/src/operator-manager/BaseOperatorManager.sol index 2c6361d..5bb426d 100644 --- a/src/operator-manager/BaseOperatorManager.sol +++ b/src/operator-manager/BaseOperatorManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; @@ -46,6 +46,15 @@ abstract contract BaseOperatorManager is BaseManager { return _operators.getActive(getCaptureTimestamp()); } + /* + * @notice Returns a list of active operators at a specific timestamp. + * @param timestamp The timestamp to check. + * @return An array of addresses representing the active operators. + */ + function activeOperatorsAt(uint48 timestamp) public view returns (address[] memory) { + return _operators.getActive(timestamp); + } + /* * @notice Checks if a given operator was active at a specified timestamp. * @param timestamp The timestamp to check. @@ -69,7 +78,7 @@ abstract contract BaseOperatorManager is BaseManager { revert OperatorNotOptedIn(); } - _operators.register(getCaptureTimestamp(), operator); + _operators.register(Time.timestamp(), operator); } /* diff --git a/src/operator-manager/DefaultOperatorManager.sol b/src/operator-manager/DefaultOperatorManager.sol index 9d8021e..242ad81 100644 --- a/src/operator-manager/DefaultOperatorManager.sol +++ b/src/operator-manager/DefaultOperatorManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {BaseOperatorManager} from "./BaseOperatorManager.sol"; diff --git a/src/vault-manager/BaseVaultManager.sol b/src/vault-manager/BaseVaultManager.sol index 56ac612..5c33af4 100644 --- a/src/vault-manager/BaseVaultManager.sol +++ b/src/vault-manager/BaseVaultManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; @@ -46,88 +46,95 @@ abstract contract BaseVaultManager is BaseManager { uint256 response; // if instant slashed amount else slash index } - /* - * @notice Returns the number of subnetworks registered. - * @return The count of registered subnetworks. + /** + * @notice Returns the number of subnetworks registered + * @return The count of registered subnetworks */ function subnetworksLength() public view returns (uint256) { return _subnetworks.length(); } - /* - * @notice Returns the subnetwork information at a specified position. - * @param pos The index of the subnetwork. - * @return The subnetwork details including address and timing information. + /** + * @notice Returns the subnetwork information at a specified position + * @param pos The index of the subnetwork + * @return The subnetwork address + * @return enableTime The time when the subnetwork was enabled + * @return disableTime The time when the subnetwork was disabled */ function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint48, uint48) { return _subnetworks.at(pos); } - /* - * @notice Returns an array of active subnetworks. - * @return An array of active subnetwork addresses. + /** + * @notice Returns an array of active subnetworks at the current capture timestamp + * @return An array of active subnetwork addresses */ function activeSubnetworks() public view returns (uint160[] memory) { return _subnetworks.getActive(getCaptureTimestamp()); } - /* - * @notice Returns an array of active subnetworks. - * @return An array of active subnetwork addresses. + /** + * @notice Returns an array of active subnetworks at a specific timestamp + * @param timestamp The timestamp to check activity at + * @return An array of active subnetwork addresses */ function activeSubnetworksAt(uint48 timestamp) public view returns (uint160[] memory) { return _subnetworks.getActive(timestamp); } - /* - * @notice Checks if a given subnetwork was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param subnetwork The subnetwork to check. - * @return A boolean indicating whether the subnetwork was active at the specified timestamp. + /** + * @notice Checks if a given subnetwork was active at a specified timestamp + * @param timestamp The timestamp to check + * @param subnetwork The subnetwork to check + * @return True if the subnetwork was active at the timestamp */ function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) public view returns (bool) { return _subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); } - /* - * @notice Returns the length of shared vaults. - * @return The number of shared vaults. + /** + * @notice Returns the number of shared vaults + * @return The count of shared vaults */ function sharedVaultsLength() public view returns (uint256) { return _sharedVaults.length(); } - /* - * @notice Returns the address and timing information of a shared vault at a specific position. - * @param pos The index position in the shared vaults array. - * @return The address and timing information of the vault. + /** + * @notice Returns the vault information at a specified position + * @param pos The index position in the shared vaults array + * @return The vault address + * @return enableTime The time when the vault was enabled + * @return disableTime The time when the vault was disabled */ function sharedVaultWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { return _sharedVaults.at(pos); } - /* - * @notice Returns an array of active shared vaults. - * @return An array of active shared vault addresses. + /** + * @notice Returns an array of active shared vaults at the current capture timestamp + * @return An array of active shared vault addresses */ function activeSharedVaults() public view returns (address[] memory) { return _sharedVaults.getActive(getCaptureTimestamp()); } - /* - * @notice Returns the length of operator vaults for a specific operator. - * @param operator The address of the operator. - * @return The number of vaults associated with the operator. + /** + * @notice Returns the number of vaults associated with an operator + * @param operator The address of the operator + * @return The count of vaults for the operator */ function operatorVaultsLength(address operator) public view returns (uint256) { return _operatorVaults[operator].length(); } - /* - * @notice Returns the address and timing information of an operator vault at a specific position. - * @param operator The address of the operator. - * @param pos The index position in the operator vaults array. - * @return The address and timing information of the vault. + /** + * @notice Returns the vault information at a specified position for an operator + * @param operator The address of the operator + * @param pos The index position in the operator vaults array + * @return The vault address + * @return enableTime The time when the vault was enabled + * @return disableTime The time when the vault was disabled */ function operatorVaultWithTimesAt(address operator, uint256 pos) public @@ -137,28 +144,29 @@ abstract contract BaseVaultManager is BaseManager { return _operatorVaults[operator].at(pos); } - /* - * @notice Returns an array of active operator vaults for a specific operator. - * @param operator The address of the operator. - * @return An array of active operator vault addresses. + /** + * @notice Returns an array of active vaults for a specific operator at the current capture timestamp + * @param operator The address of the operator + * @return An array of active vault addresses */ function activeOperatorVaults(address operator) public view returns (address[] memory) { return _operatorVaults[operator].getActive(getCaptureTimestamp()); } - /* - * @notice Converts stake to power for a vault. - * @param vault The address of the vault. - * @param stake The amount of stake to convert. - * @return The power calculated from the stake. + /** + * @notice Converts stake amount to power for a vault + * @param vault The address of the vault + * @param stake The amount of stake to convert + * @return The calculated power amount */ function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256) { + vault; return stake; } - /* - * @notice Returns the list of network's active vaults. - * @return An array of addresses representing the active vaults. + /** + * @notice Returns all active vaults at the current capture timestamp + * @return An array of active vault addresses */ function activeVaults() public view virtual returns (address[] memory) { uint48 timestamp = getCaptureTimestamp(); @@ -185,6 +193,11 @@ abstract contract BaseVaultManager is BaseManager { return vaults; } + /** + * @notice Returns all active vaults at a specific timestamp + * @param timestamp The timestamp to check activity at + * @return An array of active vault addresses + */ function activeVaultsAt(uint48 timestamp) public view virtual returns (address[] memory) { address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); uint256 len = activeSharedVaults_.length; @@ -209,10 +222,10 @@ abstract contract BaseVaultManager is BaseManager { return vaults; } - /* - * @notice Returns the list of active vaults for a specific operator. - * @param operator The address of the operator. - * @return An array of addresses representing the active vaults. + /** + * @notice Returns active vaults for a specific operator at the current capture timestamp + * @param operator The address of the operator + * @return An array of active vault addresses */ function activeVaults(address operator) public view virtual returns (address[] memory) { uint48 timestamp = getCaptureTimestamp(); @@ -231,11 +244,11 @@ abstract contract BaseVaultManager is BaseManager { return vaults; } - /* - * @notice Returns the list of active vaults for a specific operator. - * @param timestamp The timestamp to check. - * @param operator The address of the operator. - * @return An array of addresses representing the active vaults. + /** + * @notice Returns active vaults for a specific operator at a given timestamp + * @param timestamp The timestamp to check activity at + * @param operator The address of the operator + * @return An array of active vault addresses */ function activeVaultsAt(uint48 timestamp, address operator) public view virtual returns (address[] memory) { address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); @@ -253,44 +266,44 @@ abstract contract BaseVaultManager is BaseManager { return vaults; } - /* - * @notice Checks if a given vault was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param operator The address of operator. - * @param vault The vault to check. - * @return A boolean indicating whether the vault was active at the specified timestamp. + /** + * @notice Checks if a vault was active at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The address of operator + * @param vault The vault address to check + * @return True if the vault was active at the timestamp */ function vaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { return sharedVaultWasActiveAt(timestamp, vault) || operatorVaultWasActiveAt(timestamp, operator, vault); } - /* - * @notice Checks if a given shared vault was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param vault The vault to check. - * @return A boolean indicating whether the shared vault was active at the specified timestamp. + /** + * @notice Checks if a shared vault was active at a specific timestamp + * @param timestamp The timestamp to check + * @param vault The vault address to check + * @return True if the shared vault was active at the timestamp */ function sharedVaultWasActiveAt(uint48 timestamp, address vault) public view returns (bool) { return _sharedVaults.wasActiveAt(timestamp, vault); } - /* - * @notice Checks if a given operator vault was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param operator The address of operator. - * @param vault The vault to check. - * @return A boolean indicating whether the operator vault was active at the specified timestamp. + /** + * @notice Checks if an operator vault was active at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The address of operator + * @param vault The vault address to check + * @return True if the operator vault was active at the timestamp */ function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { return _operatorVaults[operator].wasActiveAt(timestamp, vault); } - /* - * @notice Returns the stake of an operator for a specific vault and subnetwork. - * @param operator The address of the operator. - * @param vault The address of the vault. - * @param subnetwork The subnetwork identifier. - * @return The stake of the operator. + /** + * @notice Gets the stake amount for an operator in a vault and subnetwork + * @param operator The address of the operator + * @param vault The address of the vault + * @param subnetwork The subnetwork identifier + * @return The stake amount */ function getOperatorStake(address operator, address vault, uint96 subnetwork) public view returns (uint256) { uint48 timestamp = getCaptureTimestamp(); @@ -298,25 +311,50 @@ abstract contract BaseVaultManager is BaseManager { return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); } - /* - * @notice Returns the power of an operator for a specific vault and subnetwork. - * @param operator The address of the operator. - * @param vault The address of the vault. - * @param subnetwork The subnetwork identifier. - * @return The power of the operator. + /** + * @notice Gets the stake amount for an operator in a vault and subnetwork at a specific timestamp + * @param operator The address of the operator + * @param vault The address of the vault + * @param subnetwork The subnetwork identifier + * @param timestamp The timestamp to check stake at + * @return The stake amount + */ + function getOperatorStakeAt(address operator, address vault, uint96 subnetwork, uint48 timestamp) public view returns (uint256) { + bytes32 subnetworkId = NETWORK.subnetwork(subnetwork); + return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); + } + + /** + * @notice Gets the power amount for an operator in a vault and subnetwork + * @param operator The address of the operator + * @param vault The address of the vault + * @param subnetwork The subnetwork identifier + * @return The power amount */ function getOperatorPower(address operator, address vault, uint96 subnetwork) public view returns (uint256) { uint256 stake = getOperatorStake(operator, vault, subnetwork); return stakeToPower(vault, stake); } - /* - * @notice Returns the stake of an operator. - * @param operator The address of the operator. - * @return The stake of the operator. + /** + * @notice Gets the power amount for an operator in a vault and subnetwork at a specific timestamp + * @param operator The address of the operator + * @param vault The address of the vault + * @param subnetwork The subnetwork identifier + * @param timestamp The timestamp to check power at + * @return The power amount + */ + function getOperatorPowerAt(address operator, address vault, uint96 subnetwork, uint48 timestamp) public view returns (uint256) { + uint256 stake = getOperatorStakeAt(operator, vault, subnetwork, timestamp); + return stakeToPower(vault, stake); + } + + /** + * @notice Gets the total stake amount for an operator across all vaults and subnetworks + * @param operator The address of the operator + * @return stake The total stake amount */ function getOperatorStake(address operator) public view virtual returns (uint256 stake) { - uint48 timestamp = getCaptureTimestamp(); address[] memory vaults = activeVaults(operator); uint160[] memory subnetworks = activeSubnetworks(); @@ -330,27 +368,32 @@ abstract contract BaseVaultManager is BaseManager { return stake; } + /** + * @notice Gets the total stake amount for an operator across all vaults and subnetworks at a specific timestamp + * @param operator The address of the operator + * @param timestamp The timestamp to check stake at + * @return stake The total stake amount + */ function getOperatorStakeAt(address operator, uint48 timestamp) public view virtual returns (uint256 stake) { - address[] memory vaults = activeVaults(operator); - uint160[] memory subnetworks = activeSubnetworks(); + address[] memory vaults = activeVaultsAt(timestamp, operator); + uint160[] memory subnetworks = activeSubnetworksAt(timestamp); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; for (uint256 j; j < subnetworks.length; ++j) { - stake += getOperatorStake(operator, vault, uint96(subnetworks[j])); + stake += getOperatorStakeAt(operator, vault, uint96(subnetworks[j]), timestamp); } } return stake; } - /* - * @notice Returns the power of an operator. - * @param operator The address of the operator. - * @return The power of the operator. + /** + * @notice Gets the total power amount for an operator across all vaults and subnetworks + * @param operator The address of the operator + * @return power The total power amount */ function getOperatorPower(address operator) public view virtual returns (uint256 power) { - uint48 timestamp = getCaptureTimestamp(); address[] memory vaults = activeVaults(operator); uint160[] memory subnetworks = activeSubnetworks(); @@ -364,10 +407,30 @@ abstract contract BaseVaultManager is BaseManager { return power; } - /* - * @notice Returns the total stake of multiple operators. - * @param operators The list of operator addresses. - * @return The total stake of the operators. + /** + * @notice Gets the total power amount for an operator across all vaults and subnetworks at a specific timestamp + * @param operator The address of the operator + * @param timestamp The timestamp to check power at + * @return power The total power amount + */ + function getOperatorPowerAt(address operator, uint48 timestamp) public view virtual returns (uint256 power) { + address[] memory vaults = activeVaultsAt(timestamp, operator); + uint160[] memory subnetworks = activeSubnetworksAt(timestamp); + + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + for (uint256 j; j < subnetworks.length; ++j) { + power += getOperatorPowerAt(operator, vault, uint96(subnetworks[j]), timestamp); + } + } + + return power; + } + + /** + * @notice Calculates the total stake for a list of operators + * @param operators Array of operator addresses + * @return stake The total stake amount */ function _totalStake(address[] memory operators) internal view returns (uint256 stake) { for (uint256 i; i < operators.length; ++i) { @@ -378,10 +441,10 @@ abstract contract BaseVaultManager is BaseManager { return stake; } - /* - * @notice Returns the total power of multiple operators. - * @param operators The list of operator addresses. - * @return The total power of the operators. + /** + * @notice Calculates the total power for a list of operators + * @param operators Array of operator addresses + * @return power The total power amount */ function _totalPower(address[] memory operators) internal view returns (uint256 power) { for (uint256 i; i < operators.length; ++i) { @@ -392,51 +455,51 @@ abstract contract BaseVaultManager is BaseManager { return power; } - /* - * @notice Registers a new subnetwork. - * @param subnetwork The identifier of the subnetwork to register. + /** + * @notice Registers a new subnetwork + * @param subnetwork The identifier of the subnetwork to register */ function _registerSubnetwork(uint96 subnetwork) internal { _subnetworks.register(getCaptureTimestamp(), uint160(subnetwork)); } - /* - * @notice Pauses a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to pause. + /** + * @notice Pauses a subnetwork + * @param subnetwork The identifier of the subnetwork to pause */ function _pauseSubnetwork(uint96 subnetwork) internal { _subnetworks.pause(getCaptureTimestamp(), uint160(subnetwork)); } - /* - * @notice Unpauses a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to unpause. + /** + * @notice Unpauses a subnetwork + * @param subnetwork The identifier of the subnetwork to unpause */ function _unpauseSubnetwork(uint96 subnetwork) internal { _subnetworks.unpause(getCaptureTimestamp(), SLASHING_WINDOW, uint160(subnetwork)); } - /* - * @notice Unregisters a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to unregister. + /** + * @notice Unregisters a subnetwork + * @param subnetwork The identifier of the subnetwork to unregister */ function _unregisterSubnetwork(uint96 subnetwork) internal { _subnetworks.unregister(getCaptureTimestamp(), SLASHING_WINDOW, uint160(subnetwork)); } - /* - * @notice Registers a new shared vault. - * @param vault The address of the vault to register. + /** + * @notice Registers a new shared vault + * @param vault The address of the vault to register */ function _registerSharedVault(address vault) internal { _validateVault(vault); _sharedVaults.register(getCaptureTimestamp(), vault); } - /* - * @notice Registers a new operator vault. - * @param operator The address of the operator. - * @param vault The address of the vault to register. + /** + * @notice Registers a new operator vault + * @param operator The address of the operator + * @param vault The address of the vault to register */ function _registerOperatorVault(address operator, address vault) internal { _validateVault(vault); @@ -447,67 +510,67 @@ abstract contract BaseVaultManager is BaseManager { _vaultOperator.set(vault, operator); } - /* - * @notice Pauses a shared vault. - * @param vault The address of the vault to pause. + /** + * @notice Pauses a shared vault + * @param vault The address of the vault to pause */ function _pauseSharedVault(address vault) internal { _sharedVaults.pause(getCaptureTimestamp(), vault); } - /* - * @notice Unpauses a shared vault. - * @param vault The address of the vault to unpause. + /** + * @notice Unpauses a shared vault + * @param vault The address of the vault to unpause */ function _unpauseSharedVault(address vault) internal { _sharedVaults.unpause(getCaptureTimestamp(), SLASHING_WINDOW, vault); } - /* - * @notice Pauses an operator vault. - * @param operator The address of the operator. - * @param vault The address of the vault to pause. + /** + * @notice Pauses an operator vault + * @param operator The address of the operator + * @param vault The address of the vault to pause */ function _pauseOperatorVault(address operator, address vault) internal { _operatorVaults[operator].pause(getCaptureTimestamp(), vault); } - /* - * @notice Unpauses an operator vault. - * @param operator The address of the operator. - * @param vault The address of the vault to unpause. + /** + * @notice Unpauses an operator vault + * @param operator The address of the operator + * @param vault The address of the vault to unpause */ function _unpauseOperatorVault(address operator, address vault) internal { _operatorVaults[operator].unpause(getCaptureTimestamp(), SLASHING_WINDOW, vault); } - /* - * @notice Unregisters a shared vault. - * @param vault The address of the vault to unregister. + /** + * @notice Unregisters a shared vault + * @param vault The address of the vault to unregister */ function _unregisterSharedVault(address vault) internal { _sharedVaults.unregister(getCaptureTimestamp(), SLASHING_WINDOW, vault); } - /* - * @notice Unregisters an operator vault. - * @param operator The address of the operator. - * @param vault The address of the vault to unregister. + /** + * @notice Unregisters an operator vault + * @param operator The address of the operator + * @param vault The address of the vault to unregister */ function _unregisterOperatorVault(address operator, address vault) internal { _operatorVaults[operator].unregister(getCaptureTimestamp(), SLASHING_WINDOW, vault); _vaultOperator.remove(vault); } - /* - * @notice Slashes a vault based on provided conditions. - * @param timestamp The timestamp when the slash occurs. - * @param vault The address of the vault. - * @param subnetwork The subnetwork identifier. - * @param operator The operator to slash. - * @param amount The amount to slash. - * @param hints Additional data for the slasher. - * @return A struct containing information about the slash response. + /** + * @notice Slashes a vault based on provided conditions + * @param timestamp The timestamp when the slash occurs + * @param vault The address of the vault + * @param subnetwork The subnetwork identifier + * @param operator The operator to slash + * @param amount The amount to slash + * @param hints Additional data for the slasher + * @return resp A struct containing information about the slash response */ function _slashVault( uint48 timestamp, @@ -543,12 +606,12 @@ abstract contract BaseVaultManager is BaseManager { } } - /* - * @notice Executes a veto-based slash for a vault. - * @param vault The address of the vault. - * @param slashIndex The index of the slash to execute. - * @param hints Additional data for the veto slasher. - * @return The amount that was slashed. + /** + * @notice Executes a veto-based slash for a vault + * @param vault The address of the vault + * @param slashIndex The index of the slash to execute + * @param hints Additional data for the veto slasher + * @return slashedAmount The amount that was slashed */ function _executeSlash(address vault, uint256 slashIndex, bytes calldata hints) internal @@ -563,9 +626,9 @@ abstract contract BaseVaultManager is BaseManager { return IVetoSlasher(slasher).executeSlash(slashIndex, hints); } - /* - * @notice Validates if the vault is properly initialized and registered. - * @param vault The address of the vault to validate. + /** + * @notice Validates if the vault is properly initialized and registered + * @param vault The address of the vault to validate */ function _validateVault(address vault) private view { if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { diff --git a/src/vault-manager/DefaultVaultManager.sol b/src/vault-manager/DefaultVaultManager.sol index cd5b643..e073409 100644 --- a/src/vault-manager/DefaultVaultManager.sol +++ b/src/vault-manager/DefaultVaultManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {BaseVaultManager} from "./BaseVaultManager.sol"; diff --git a/test/mocks/ExtendedSimplePosMiddleware.sol b/test/mocks/ExtendedSimplePosMiddleware.sol index 7665263..9c9014d 100644 --- a/test/mocks/ExtendedSimplePosMiddleware.sol +++ b/test/mocks/ExtendedSimplePosMiddleware.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.25; +pragma solidity ^0.8.25; import {SimplePosMiddleware} from "../../src/examples/simple-pos-network/SimplePosMiddleware.sol"; import {DefaultKeyManager} from "../../src/key-manager/DefaultKeyManager.sol"; From 3b86efd334d4acbc1899069d9232932bcb91ef12 Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Sun, 10 Nov 2024 16:13:58 +0700 Subject: [PATCH 033/115] WIP: reworked BaseKeyRegistry --- src/key-manager/BaseKeyManager.sol | 52 +-- src/libraries/PauseableEnumerableSet.sol | 442 ++++++++++++++++++++++- src/vault-manager/BaseVaultManager.sol | 24 +- 3 files changed, 469 insertions(+), 49 deletions(-) diff --git a/src/key-manager/BaseKeyManager.sol b/src/key-manager/BaseKeyManager.sol index e94cf33..c4f31f7 100644 --- a/src/key-manager/BaseKeyManager.sol +++ b/src/key-manager/BaseKeyManager.sol @@ -4,26 +4,27 @@ pragma solidity ^0.8.25; import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + abstract contract BaseKeyManager is BaseManager { - using PauseableEnumerableSet for PauseableEnumerableSet.Inner; + using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; error DuplicateKey(); error KeyAlreadyEnabled(); + error MaxDisabledKeysReached(); bytes32 private constant ZERO_BYTES32 = bytes32(0); + uint256 private constant MAX_DISABLED_KEYS = 1; - mapping(address => bytes32) public keys; // Mapping from operator addresses to their current keys - mapping(address => bytes32) public prevKeys; // Mapping from operator addresses to their previous keys - mapping(address => uint48) public keyUpdateTimestamp; // Mapping from operator addresses to the epoch of the last key update - mapping(bytes32 => PauseableEnumerableSet.Inner) internal _keyData; // Mapping from keys to their associated data - + mapping(address => PauseableEnumerableSet.Bytes32Set) internal keys; // Mapping from operator addresses to their current keys + mapping(bytes32 => address) internal keyToOperator; /** * @notice Returns the operator address associated with a given key * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ function operatorByKey(bytes32 key) public view returns (address) { - return _keyData[key].getAddress(); + return keyToOperator[key]; } /** @@ -33,11 +34,7 @@ abstract contract BaseKeyManager is BaseManager { * @return The key associated with the specified operator */ function operatorKey(address operator) public view returns (bytes32) { - if (keyUpdateTimestamp[operator] == getCaptureTimestamp()) { - return prevKeys[operator]; - } - - return keys[operator]; + return keys[operator].getActive(getCaptureTimestamp())[0]; } /** @@ -47,7 +44,7 @@ abstract contract BaseKeyManager is BaseManager { * @return A boolean indicating whether the key was active at the specified timestamp */ function keyWasActiveAt(uint48 timestamp, bytes32 key) public view returns (bool) { - return _keyData[key].wasActiveAt(timestamp); + return keys[keyToOperator[key]].wasActiveAt(timestamp, key); } /** @@ -57,25 +54,28 @@ abstract contract BaseKeyManager is BaseManager { * @param key The new key to associate with the operator */ function _updateKey(address operator, bytes32 key) internal { - uint48 timestamp = getCaptureTimestamp(); - - if (keys[operator] == key) { - revert KeyAlreadyEnabled(); - } - - if (_keyData[key].getAddress() != address(0) && _keyData[key].getAddress() != operator) { + if (keyToOperator[key] != address(0)) { revert DuplicateKey(); } - if (key != ZERO_BYTES32 && _keyData[key].getAddress() == address(0)) { - _keyData[key].set(timestamp, operator); + // try to remove disabled keys + keys[operator].prune(Time.timestamp(), SLASHING_WINDOW); + + // check if we have reached the max number of disabled keys + // this allow us to limit the number times we can change the key + if (keys[operator].length() > MAX_DISABLED_KEYS + 1) { + revert MaxDisabledKeysReached(); } - if (keyUpdateTimestamp[operator] != timestamp) { - prevKeys[operator] = keys[operator]; - keyUpdateTimestamp[operator] = timestamp; + // get the current active keys + bytes32[] memory activeKeys = keys[operator].getActive(Time.timestamp()); + + // pause the current active key if any + if (activeKeys.length > 0) { + keys[operator].pause(Time.timestamp(), activeKeys[0]); } - keys[operator] = key; + // register the new key + keys[operator].register(Time.timestamp(), key); } } diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 1f1ca17..852c649 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -4,6 +4,17 @@ pragma solidity ^0.8.25; library PauseableEnumerableSet { using PauseableEnumerableSet for Inner; using PauseableEnumerableSet for Uint160Set; + using PauseableEnumerableSet for Inner256; + using PauseableEnumerableSet for Uint256Set; + + // Custom error messages + error AlreadyRegistered(); // Thrown when trying to register an already registered value. + error NotRegistered(); // Thrown when trying to modify a value that's not registered. + error AlreadyEnabled(); // Thrown when enabling an already enabled value. + error NotEnabled(); // Thrown when disabling a value that's not enabled. + error Enabled(); // Thrown when trying to disable a value that's enabled. + error ImmutablePeriodNotPassed(); // Thrown when an action is attempted before immutable period passes. + /* * Struct for managing a set of Uint160 values. @@ -29,14 +40,6 @@ library PauseableEnumerableSet { uint48 disabledTimestamp; // Timestamp when the value was disabled. } - // Custom error messages - error AlreadyRegistered(); // Thrown when trying to register an already registered value. - error NotRegistered(); // Thrown when trying to modify a value that's not registered. - error AlreadyEnabled(); // Thrown when enabling an already enabled value. - error NotEnabled(); // Thrown when disabling a value that's not enabled. - error Enabled(); // Thrown when trying to disable a value that's enabled. - error ImmutablePeriodNotPassed(); // Thrown when an action is attempted before immutable period passes. - /* * @notice Returns the length of the AddressSet. * @param self The AddressSet storage. @@ -335,11 +338,10 @@ library PauseableEnumerableSet { * @param timestamp The timestamp when the value is disabled. */ function disable(Inner storage self, uint48 timestamp) internal { - if (self.enabledTimestamp == 0) { + if (self.disabledTimestamp != 0) { revert NotEnabled(); } - self.enabledTimestamp = 0; self.disabledTimestamp = timestamp; } @@ -350,7 +352,8 @@ library PauseableEnumerableSet { * @return True if the value was active at the timestamp, false otherwise. */ function wasActiveAt(Inner storage self, uint48 timestamp) internal view returns (bool) { - return self.enabledTimestamp != 0 && self.enabledTimestamp <= timestamp; + return self.enabledTimestamp <= timestamp && + (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); } /* @@ -380,4 +383,421 @@ library PauseableEnumerableSet { revert ImmutablePeriodNotPassed(); } } + + /* + * Struct for managing a set of Uint160 values. + */ + struct Uint256Set { + Inner256[] array; + mapping(uint256 => uint256) positions; // Maps value to its index + 1. + } + + /* + * Struct for managing a set of addresses. + */ + struct Bytes32Set { + Uint256Set set; + } + + /* + * Struct for managing value and its active status. + */ + struct Inner256 { + uint256 value; // The actual value. + uint48 enabledTimestamp; // Timestamp when the value was enabled. + uint48 disabledTimestamp; // Timestamp when the value was disabled. + } + + /* + * @notice Returns the length of the AddressSet. + * @param self The AddressSet storage. + * @return The number of elements in the set. + */ + function length(Bytes32Set storage self) internal view returns (uint256) { + return self.set.length(); + } + + /* + * @notice Returns the address and its active period at a given position in the AddressSet. + * @param self The AddressSet storage. + * @param pos The position in the set. + * @return The address, enabled timestamp and disabled timestamp at the position. + */ + function at(Bytes32Set storage self, uint256 pos) internal view returns (bytes32, uint48, uint48) { + (uint256 value, uint48 enabledTimestamp, uint48 disabledTimestamp) = self.set.at(pos); + return (bytes32(value), enabledTimestamp, disabledTimestamp); + } + + /* + * @notice Retrieves all active addresses at a given timestamp. + * @param self The AddressSet storage. + * @param timestamp The timestamp to check. + * @return An array of active addresses. + */ + function getActive(Bytes32Set storage self, uint48 timestamp) internal view returns (bytes32[] memory array) { + uint256[] memory uint256Array = self.set.getActive(timestamp); + + assembly ("memory-safe") { + array := uint256Array + } + + return array; + } + + /* + * @notice Checks if a given addr was active at a specified timestamp. + * @param timestamp The timestamp to check. + * @param addr The address to check. + * @return A boolean indicating whether the addr was active at the specified timestamp. + */ + function wasActiveAt(Bytes32Set storage self, uint48 timestamp, bytes32 key) internal view returns (bool) { + return self.set.wasActiveAt(timestamp, uint256(key)); + } + + /* + * @notice Registers a new address at a given timestamp. + * @param self The AddressSet storage. + * @param timestamp The timestamp when the address is added. + * @param addr The address to register. + */ + function register(Bytes32Set storage self, uint48 timestamp, bytes32 key) internal { + self.set.register(timestamp, uint256(key)); + } + + /* + * @notice Pauses an address at a given timestamp. + * @param self The AddressSet storage. + * @param timestamp The timestamp when the address is paused. + * @param addr The address to pause. + */ + function pause(Bytes32Set storage self, uint48 timestamp, bytes32 key) internal { + self.set.pause(timestamp, uint256(key)); + } + + /* + * @notice Unpauses an address, re-enabling it after the immutable period. + * @param self The AddressSet storage. + * @param timestamp The current timestamp. + * @param immutablePeriod The required immutable period before unpausing. + * @param addr The address to unpause. + */ + function unpause(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 key) internal { + self.set.unpause(timestamp, immutablePeriod, uint256(key)); + } + + /* + * @notice Unregisters an address, removing it from the set. + * @param self The AddressSet storage. + * @param timestamp The current timestamp. + * @param immutablePeriod The required immutable period before unregistering. + * @param addr The address to unregister. + */ + function unregister(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 key) internal { + self.set.unregister(timestamp, immutablePeriod, uint256(key)); + } + + /* + * @notice Prunes the set by removing disabled values that have passed the immutable period. + * @param self The AddressSet storage. + * @param timestamp The current timestamp. + * @param immutablePeriod The immutable period that must pass before unregistering. + */ + function prune(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod) internal { + self.set.prune(timestamp, immutablePeriod); + } + + /* + * @notice Checks if an address is contained in the AddressSet. + * @param self The AddressSet storage. + * @param addr The address to check. + * @return True if the address is in the set, false otherwise. + */ + function contains(Bytes32Set storage self, bytes32 key) internal view returns (bool) { + return self.set.contains(uint256(key)); + } + + /* + * @notice Returns the number of elements in the Uint160Set. + * @param self The Uint160Set storage. + * @return The number of elements. + */ + function length(Uint256Set storage self) internal view returns (uint256) { + return self.array.length; + } + + /* + * @notice Returns the value and its active period at a given position in the Uint160Set. + * @param self The Uint160Set storage. + * @param pos The position in the set. + * @return The value, enabled timestamp and disabled timestamp at the position. + */ + function at(Uint256Set storage self, uint256 pos) internal view returns (uint256, uint48, uint48) { + return self.array[pos].get(); + } + + /* + * @notice Retrieves all active values at a given timestamp. + * @param self The Uint160Set storage. + * @param timestamp The timestamp to check. + * @return An array of active values. + */ + function getActive(Uint256Set storage self, uint48 timestamp) internal view returns (uint256[] memory) { + uint256[] memory array = new uint256[](self.array.length); + uint256 len = 0; + for (uint256 i; i < self.array.length; ++i) { + if (!self.array[i].wasActiveAt(timestamp)) { + continue; + } + array[len++] = self.array[i].value; + } + + assembly ("memory-safe") { + mstore(array, len) + } + + return array; + } + + /* + * @notice Checks if a given value was active at a specified timestamp. + * @param timestamp The timestamp to check. + * @param value The value to check. + * @return A boolean indicating whether the value was active at the specified timestamp. + */ + function wasActiveAt(Uint256Set storage self, uint48 timestamp, uint256 value) internal view returns (bool) { + if (self.positions[value] == 0) { + return false; + } + + return self.array[self.positions[value] - 1].wasActiveAt(timestamp); + } + + /* + * @notice Registers a new Uint160 value at a given timestamp. + * @param self The Uint160Set storage. + * @param timestamp The timestamp when the value is added. + * @param value The Uint160 value to register. + */ + function register(Uint256Set storage self, uint48 timestamp, uint256 value) internal { + if (self.positions[value] != 0) { + revert AlreadyRegistered(); + } + + uint256 pos = self.array.length; + Inner256 storage element = self.array.push(); + element.set(timestamp, value); + self.positions[value] = pos + 1; + } + + /* + * @notice Pauses a Uint160 value at a given timestamp. + * @param self The Uint160Set storage. + * @param timestamp The timestamp when the value is paused. + * @param value The Uint160 value to pause. + */ + function pause(Uint256Set storage self, uint48 timestamp, uint256 value) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + self.array[self.positions[value] - 1].disable(timestamp); + } + + /* + * @notice Unpauses a Uint160 value after the immutable period. + * @param self The Uint160Set storage. + * @param timestamp The current timestamp. + * @param immutablePeriod The required immutable period before unpausing. + * @param value The Uint160 value to unpause. + */ + function unpause(Uint256Set storage self, uint48 timestamp, uint48 immutablePeriod, uint256 value) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + self.array[self.positions[value] - 1].validateUnpause(timestamp, immutablePeriod); + self.array[self.positions[value] - 1].enable(timestamp); + } + + /* + * @notice Unregisters a Uint160 value from the set. + * @param self The Uint160Set storage. + * @param timestamp The current timestamp. + * @param immutablePeriod The required immutable period before unregistering. + * @param value The Uint160 value to unregister. + */ + function unregister(Uint256Set storage self, uint48 timestamp, uint48 immutablePeriod, uint256 value) internal { + if (self.positions[value] == 0) { + revert NotRegistered(); + } + + uint256 pos = self.positions[value] - 1; + self.array[pos].validateUnregister(timestamp, immutablePeriod); + + if (self.array.length == 1 || self.array.length == pos + 1) { + delete self.positions[value]; + self.array.pop(); + return; + } + + self.array[pos] = self.array[self.array.length - 1]; + self.array.pop(); + + delete self.positions[value]; + self.positions[self.array[pos].value] = pos + 1; + } + + /* + * @notice Prunes the set by removing disabled values that have passed the immutable period. + * @param self The Uint160Set storage. + * @param timestamp The current timestamp. + * @param immutablePeriod The immutable period that must pass before unregistering. + */ + function prune(Uint256Set storage self, uint48 timestamp, uint48 immutablePeriod) internal { + // Start from end to avoid shifting elements during unregister + for (uint256 i = self.array.length; i > 0;) { + unchecked { --i; } + + if (!self.array[i].checkUnregister(timestamp, immutablePeriod)) { + continue; + } + + self.unregister(timestamp, immutablePeriod, self.array[i].value); + } + } + + /* + * @notice Checks if a Uint160 value is contained in the Uint160Set. + * @param self The Uint160Set storage. + * @param value The Uint160 value to check. + * @return True if the value is in the set, false otherwise. + */ + function contains(Uint256Set storage self, uint256 value) internal view returns (bool) { + return self.positions[value] != 0; + } + + /* + * @notice Returns the address stored in the Inner struct. + * @param self The Inner struct + * @return The stored Uint160 as address + */ + function getBytes32(Inner256 storage self) internal view returns (bytes32) { + return bytes32(self.value); + } + + /* + * @notice Returns the value and its active period from the Inner struct. + * @param self The Inner struct. + * @return The value, enabled timestamp and disabled timestamp. + */ + function get(Inner256 storage self) internal view returns (uint256, uint48, uint48) { + return (self.value, self.enabledTimestamp, self.disabledTimestamp); + } + + /* + * @notice Sets the value and marks it as enabled at a given timestamp. + * @param self The Inner struct. + * @param timestamp The timestamp when the value is set. + * @param value The Uint160 value to store. + */ + function set(Inner256 storage self, uint48 timestamp, uint256 value) internal { + self.value = value; + self.enabledTimestamp = timestamp; + } + + /* + * @notice Sets the address and marks it as enabled at a given timestamp. + * @param self The Inner struct. + * @param timestamp The timestamp when the address is set. + * @param addr The address to store. + */ + function set(Inner256 storage self, uint48 timestamp, bytes32 key) internal { + self.value = uint256(key); + self.enabledTimestamp = timestamp; + } + + /* + * @notice Enables the value at a given timestamp. + * @param self The Inner struct. + * @param timestamp The timestamp when the value is enabled. + */ + function enable(Inner256 storage self, uint48 timestamp) internal { + if (self.enabledTimestamp != 0) { + revert AlreadyEnabled(); + } + + self.enabledTimestamp = timestamp; + } + + /* + * @notice Disables the value at a given timestamp. + * @param self The Inner struct. + * @param timestamp The timestamp when the value is disabled. + */ + function disable(Inner256 storage self, uint48 timestamp) internal { + if (self.disabledTimestamp != 0) { + revert NotEnabled(); + } + + self.disabledTimestamp = timestamp; + } + + /* + * @notice Checks if the value was active at a given timestamp. + * @param self The Inner struct. + * @param timestamp The timestamp to check. + * @return True if the value was active at the timestamp, false otherwise. + */ + function wasActiveAt(Inner256 storage self, uint48 timestamp) internal view returns (bool) { + return self.enabledTimestamp <= timestamp && + (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); + } + + /* + * @notice Validates whether the value can be unpaused at a given timestamp. + * @param self The Inner struct. + * @param timestamp The current timestamp. + * @param immutablePeriod The immutable period that must pass before unpausing. + */ + function validateUnpause(Inner256 storage self, uint48 timestamp, uint48 immutablePeriod) internal view { + if (self.disabledTimestamp + immutablePeriod > timestamp) { + revert ImmutablePeriodNotPassed(); + } + } + + /* + * @notice Validates whether the value can be unregistered at a given timestamp. + * @param self The Inner struct. + * @param timestamp The current timestamp. + * @param immutablePeriod The immutable period that must pass before unregistering. + */ + function validateUnregister(Inner256 storage self, uint48 timestamp, uint48 immutablePeriod) internal view { + if (self.enabledTimestamp != 0 || self.disabledTimestamp == 0) { + revert Enabled(); + } + + if (self.disabledTimestamp + immutablePeriod > timestamp) { + revert ImmutablePeriodNotPassed(); + } + } + + + /* + * @notice Checks if the value can be unregistered at a given timestamp. + * @param self The Inner struct. + * @param timestamp The current timestamp. + * @param immutablePeriod The immutable period that must pass before unregistering. + * @return True if the value can be unregistered, false otherwise. + */ + function checkUnregister(Inner256 storage self, uint48 timestamp, uint48 immutablePeriod) internal view returns(bool) { + if (self.enabledTimestamp != 0 || self.disabledTimestamp == 0) { + return false; + } + + if (self.disabledTimestamp + immutablePeriod > timestamp) { + return false; + } + + return true; + } } diff --git a/src/vault-manager/BaseVaultManager.sol b/src/vault-manager/BaseVaultManager.sol index 5c33af4..a0203d1 100644 --- a/src/vault-manager/BaseVaultManager.sol +++ b/src/vault-manager/BaseVaultManager.sol @@ -460,7 +460,7 @@ abstract contract BaseVaultManager is BaseManager { * @param subnetwork The identifier of the subnetwork to register */ function _registerSubnetwork(uint96 subnetwork) internal { - _subnetworks.register(getCaptureTimestamp(), uint160(subnetwork)); + _subnetworks.register(Time.timestamp(), uint160(subnetwork)); } /** @@ -468,7 +468,7 @@ abstract contract BaseVaultManager is BaseManager { * @param subnetwork The identifier of the subnetwork to pause */ function _pauseSubnetwork(uint96 subnetwork) internal { - _subnetworks.pause(getCaptureTimestamp(), uint160(subnetwork)); + _subnetworks.pause(Time.timestamp(), uint160(subnetwork)); } /** @@ -476,7 +476,7 @@ abstract contract BaseVaultManager is BaseManager { * @param subnetwork The identifier of the subnetwork to unpause */ function _unpauseSubnetwork(uint96 subnetwork) internal { - _subnetworks.unpause(getCaptureTimestamp(), SLASHING_WINDOW, uint160(subnetwork)); + _subnetworks.unpause(Time.timestamp(), SLASHING_WINDOW, uint160(subnetwork)); } /** @@ -484,7 +484,7 @@ abstract contract BaseVaultManager is BaseManager { * @param subnetwork The identifier of the subnetwork to unregister */ function _unregisterSubnetwork(uint96 subnetwork) internal { - _subnetworks.unregister(getCaptureTimestamp(), SLASHING_WINDOW, uint160(subnetwork)); + _subnetworks.unregister(Time.timestamp(), SLASHING_WINDOW, uint160(subnetwork)); } /** @@ -493,7 +493,7 @@ abstract contract BaseVaultManager is BaseManager { */ function _registerSharedVault(address vault) internal { _validateVault(vault); - _sharedVaults.register(getCaptureTimestamp(), vault); + _sharedVaults.register(Time.timestamp(), vault); } /** @@ -506,7 +506,7 @@ abstract contract BaseVaultManager is BaseManager { if (_sharedVaults.contains(vault)) { revert VaultAlreadyRegistred(); } - _operatorVaults[operator].register(getCaptureTimestamp(), vault); + _operatorVaults[operator].register(Time.timestamp(), vault); _vaultOperator.set(vault, operator); } @@ -515,7 +515,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to pause */ function _pauseSharedVault(address vault) internal { - _sharedVaults.pause(getCaptureTimestamp(), vault); + _sharedVaults.pause(Time.timestamp(), vault); } /** @@ -523,7 +523,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to unpause */ function _unpauseSharedVault(address vault) internal { - _sharedVaults.unpause(getCaptureTimestamp(), SLASHING_WINDOW, vault); + _sharedVaults.unpause(Time.timestamp(), SLASHING_WINDOW, vault); } /** @@ -532,7 +532,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to pause */ function _pauseOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].pause(getCaptureTimestamp(), vault); + _operatorVaults[operator].pause(Time.timestamp(), vault); } /** @@ -541,7 +541,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to unpause */ function _unpauseOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].unpause(getCaptureTimestamp(), SLASHING_WINDOW, vault); + _operatorVaults[operator].unpause(Time.timestamp(), SLASHING_WINDOW, vault); } /** @@ -549,7 +549,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to unregister */ function _unregisterSharedVault(address vault) internal { - _sharedVaults.unregister(getCaptureTimestamp(), SLASHING_WINDOW, vault); + _sharedVaults.unregister(Time.timestamp(), SLASHING_WINDOW, vault); } /** @@ -558,7 +558,7 @@ abstract contract BaseVaultManager is BaseManager { * @param vault The address of the vault to unregister */ function _unregisterOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].unregister(getCaptureTimestamp(), SLASHING_WINDOW, vault); + _operatorVaults[operator].unregister(Time.timestamp(), SLASHING_WINDOW, vault); _vaultOperator.remove(vault); } From 7756d0e34c611a770c80f2d3bb2127f670883391 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Sun, 10 Nov 2024 18:29:16 +0700 Subject: [PATCH 034/115] fix: now compile tests --- foundry.toml | 2 +- src/BaseManager.sol | 7 +--- .../SimplePosMiddleware.sol | 8 ++--- src/key-manager/BaseKeyManager.sol | 3 +- src/libraries/PauseableEnumerableSet.sol | 34 +++++++++++-------- src/vault-manager/BaseVaultManager.sol | 22 +++++++----- test/DefaultSDK.t.sol | 1 - test/mocks/ExtendedSimplePosMiddleware.sol | 11 ++++-- 8 files changed, 49 insertions(+), 39 deletions(-) diff --git a/foundry.toml b/foundry.toml index 529f204..1a4fc90 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc-version = "0.8.26" +solc-version = "0.8.25" src = "src" out = "out" libs = ["lib"] diff --git a/src/BaseManager.sol b/src/BaseManager.sol index 522fe57..4a36c19 100644 --- a/src/BaseManager.sol +++ b/src/BaseManager.sol @@ -15,10 +15,6 @@ contract BaseManager is Initializable, OwnableUpgradeable { uint64 public constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type uint64 public constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type - constructor() { - _disableInitializers(); - } - /* * @notice initalizer of the BaseManager contract. * @param owner The address of the contract owner. @@ -37,7 +33,6 @@ contract BaseManager is Initializable, OwnableUpgradeable { address operatorRegistry, address operatorNetOptIn ) public virtual initializer { - __Ownable_init(owner); NETWORK = network; @@ -52,7 +47,7 @@ contract BaseManager is Initializable, OwnableUpgradeable { * @dev Returns block.timestamp - 1 by default but can be overrided * @return The current capture timestamp */ - function getCaptureTimestamp() public virtual view returns(uint48 timestamp) { + function getCaptureTimestamp() public view virtual returns (uint48 timestamp) { return Time.timestamp() - 1; } } diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 70ab94a..b3bf040 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -56,7 +56,7 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def /* * @notice Returns the capture timestamp for the current epoch. * @return The capture timestamp. - */ + */ function getCaptureTimestamp() public view virtual override returns (uint48 timestamp) { return START_TIMESTAMP + (Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION; } @@ -65,7 +65,7 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def * @notice Returns the start timestamp for a given epoch. * @param epoch The epoch number. * @return The start timestamp. - */ + */ function getEpochStart(uint48 epoch) public view returns (uint48) { return START_TIMESTAMP + epoch * EPOCH_DURATION; } @@ -117,7 +117,7 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def validatorSet[len++] = ValidatorData(power, key); // Store the validator data } - assembly ("memory-safe") { + assembly { mstore(validatorSet, len) // Update the length of the array } } @@ -189,7 +189,7 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def } } - assembly ("memory-safe") { + assembly { mstore(slashResponses, len) // Update the length of the slash responses } } diff --git a/src/key-manager/BaseKeyManager.sol b/src/key-manager/BaseKeyManager.sol index c4f31f7..61ea511 100644 --- a/src/key-manager/BaseKeyManager.sol +++ b/src/key-manager/BaseKeyManager.sol @@ -23,6 +23,7 @@ abstract contract BaseKeyManager is BaseManager { * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ + function operatorByKey(bytes32 key) public view returns (address) { return keyToOperator[key]; } @@ -58,7 +59,7 @@ abstract contract BaseKeyManager is BaseManager { revert DuplicateKey(); } - // try to remove disabled keys + // try to remove disabled keys keys[operator].prune(Time.timestamp(), SLASHING_WINDOW); // check if we have reached the max number of disabled keys diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 852c649..08dd487 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -15,7 +15,6 @@ library PauseableEnumerableSet { error Enabled(); // Thrown when trying to disable a value that's enabled. error ImmutablePeriodNotPassed(); // Thrown when an action is attempted before immutable period passes. - /* * Struct for managing a set of Uint160 values. */ @@ -69,7 +68,7 @@ library PauseableEnumerableSet { function getActive(AddressSet storage self, uint48 timestamp) internal view returns (address[] memory array) { uint160[] memory uint160Array = self.set.getActive(timestamp); - assembly ("memory-safe") { + assembly { array := uint160Array } @@ -173,7 +172,7 @@ library PauseableEnumerableSet { array[len++] = self.array[i].value; } - assembly ("memory-safe") { + assembly { mstore(array, len) } @@ -352,8 +351,8 @@ library PauseableEnumerableSet { * @return True if the value was active at the timestamp, false otherwise. */ function wasActiveAt(Inner storage self, uint48 timestamp) internal view returns (bool) { - return self.enabledTimestamp <= timestamp && - (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); + return + self.enabledTimestamp <= timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); } /* @@ -384,7 +383,7 @@ library PauseableEnumerableSet { } } - /* + /* * Struct for managing a set of Uint160 values. */ struct Uint256Set { @@ -437,7 +436,7 @@ library PauseableEnumerableSet { function getActive(Bytes32Set storage self, uint48 timestamp) internal view returns (bytes32[] memory array) { uint256[] memory uint256Array = self.set.getActive(timestamp); - assembly ("memory-safe") { + assembly { array := uint256Array } @@ -504,7 +503,7 @@ library PauseableEnumerableSet { */ function prune(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod) internal { self.set.prune(timestamp, immutablePeriod); - } + } /* * @notice Checks if an address is contained in the AddressSet. @@ -551,7 +550,7 @@ library PauseableEnumerableSet { array[len++] = self.array[i].value; } - assembly ("memory-safe") { + assembly { mstore(array, len) } @@ -656,8 +655,10 @@ library PauseableEnumerableSet { function prune(Uint256Set storage self, uint48 timestamp, uint48 immutablePeriod) internal { // Start from end to avoid shifting elements during unregister for (uint256 i = self.array.length; i > 0;) { - unchecked { --i; } - + unchecked { + --i; + } + if (!self.array[i].checkUnregister(timestamp, immutablePeriod)) { continue; } @@ -749,8 +750,8 @@ library PauseableEnumerableSet { * @return True if the value was active at the timestamp, false otherwise. */ function wasActiveAt(Inner256 storage self, uint48 timestamp) internal view returns (bool) { - return self.enabledTimestamp <= timestamp && - (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); + return + self.enabledTimestamp <= timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); } /* @@ -781,7 +782,6 @@ library PauseableEnumerableSet { } } - /* * @notice Checks if the value can be unregistered at a given timestamp. * @param self The Inner struct. @@ -789,7 +789,11 @@ library PauseableEnumerableSet { * @param immutablePeriod The immutable period that must pass before unregistering. * @return True if the value can be unregistered, false otherwise. */ - function checkUnregister(Inner256 storage self, uint48 timestamp, uint48 immutablePeriod) internal view returns(bool) { + function checkUnregister(Inner256 storage self, uint48 timestamp, uint48 immutablePeriod) + internal + view + returns (bool) + { if (self.enabledTimestamp != 0 || self.disabledTimestamp == 0) { return false; } diff --git a/src/vault-manager/BaseVaultManager.sol b/src/vault-manager/BaseVaultManager.sol index a0203d1..5a3db5d 100644 --- a/src/vault-manager/BaseVaultManager.sol +++ b/src/vault-manager/BaseVaultManager.sol @@ -136,11 +136,7 @@ abstract contract BaseVaultManager is BaseManager { * @return enableTime The time when the vault was enabled * @return disableTime The time when the vault was disabled */ - function operatorVaultWithTimesAt(address operator, uint256 pos) - public - view - returns (address, uint48, uint48) - { + function operatorVaultWithTimesAt(address operator, uint256 pos) public view returns (address, uint48, uint48) { return _operatorVaults[operator].at(pos); } @@ -186,7 +182,7 @@ abstract contract BaseVaultManager is BaseManager { } } - assembly ("memory-safe") { + assembly { mstore(vaults, len) } @@ -215,7 +211,7 @@ abstract contract BaseVaultManager is BaseManager { } } - assembly ("memory-safe") { + assembly { mstore(vaults, len) } @@ -319,7 +315,11 @@ abstract contract BaseVaultManager is BaseManager { * @param timestamp The timestamp to check stake at * @return The stake amount */ - function getOperatorStakeAt(address operator, address vault, uint96 subnetwork, uint48 timestamp) public view returns (uint256) { + function getOperatorStakeAt(address operator, address vault, uint96 subnetwork, uint48 timestamp) + public + view + returns (uint256) + { bytes32 subnetworkId = NETWORK.subnetwork(subnetwork); return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); } @@ -344,7 +344,11 @@ abstract contract BaseVaultManager is BaseManager { * @param timestamp The timestamp to check power at * @return The power amount */ - function getOperatorPowerAt(address operator, address vault, uint96 subnetwork, uint48 timestamp) public view returns (uint256) { + function getOperatorPowerAt(address operator, address vault, uint96 subnetwork, uint48 timestamp) + public + view + returns (uint256) + { uint256 stake = getOperatorStakeAt(operator, vault, subnetwork, timestamp); return stakeToPower(vault, stake); } diff --git a/test/DefaultSDK.t.sol b/test/DefaultSDK.t.sol index c796310..72a3085 100644 --- a/test/DefaultSDK.t.sol +++ b/test/DefaultSDK.t.sol @@ -13,7 +13,6 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract DefaultSDKTest is POCBaseTest { using Subnetwork for bytes32; diff --git a/test/mocks/ExtendedSimplePosMiddleware.sol b/test/mocks/ExtendedSimplePosMiddleware.sol index 9c9014d..2c5350f 100644 --- a/test/mocks/ExtendedSimplePosMiddleware.sol +++ b/test/mocks/ExtendedSimplePosMiddleware.sol @@ -14,7 +14,14 @@ contract ExtendedSimplePosMiddleware is SimplePosMiddleware { uint48 epochDuration, uint48 slashingWindow ) - SimplePosMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, owner, epochDuration, slashingWindow) + SimplePosMiddleware( + network, + operatorRegistry, + vaultRegistry, + operatorNetOptin, + owner, + epochDuration, + slashingWindow + ) {} - } From 99ce066b4317ee8d877df451c4576737aad51d87 Mon Sep 17 00:00:00 2001 From: gm256 Date: Mon, 11 Nov 2024 07:47:35 +0400 Subject: [PATCH 035/115] fixed capture timestamp --- src/examples/simple-pos-network/SimplePosMiddleware.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index b3bf040..950b3ab 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -58,7 +58,7 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def * @return The capture timestamp. */ function getCaptureTimestamp() public view virtual override returns (uint48 timestamp) { - return START_TIMESTAMP + (Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION; + return START_TIMESTAMP + ((Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION) * EPOCH_DURATION; } /* From 10fb317d6b831c3285ebdb85c9fb075db474d806 Mon Sep 17 00:00:00 2001 From: gm256 Date: Mon, 11 Nov 2024 07:48:59 +0400 Subject: [PATCH 036/115] fixed capture timestamp --- .../simple-pos-network/SimplePosMiddleware.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 950b3ab..b919111 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -53,14 +53,6 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def START_TIMESTAMP = Time.timestamp(); } - /* - * @notice Returns the capture timestamp for the current epoch. - * @return The capture timestamp. - */ - function getCaptureTimestamp() public view virtual override returns (uint48 timestamp) { - return START_TIMESTAMP + ((Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION) * EPOCH_DURATION; - } - /* * @notice Returns the start timestamp for a given epoch. * @param epoch The epoch number. @@ -78,6 +70,14 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def return (Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION; } + /* + * @notice Returns the capture timestamp for the current epoch. + * @return The capture timestamp. + */ + function getCaptureTimestamp() public view virtual override returns (uint48 timestamp) { + return getEpochStart(getCurrentEpoch()); + } + /* * @notice Returns the total stake for the active operators in the current epoch. * @return The total stake amount. From 11855930c24597d579724434b9be1fb0267eb1f8 Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Mon, 11 Nov 2024 14:02:17 +0700 Subject: [PATCH 037/115] WIP: new structure with extensions --- foundry.toml | 2 +- src/BaseManager.sol | 11 +- src/access-manager/AccessManager.sol | 20 +++ .../SimplePosMiddleware.sol | 125 +++++++++--------- .../sqrt-task-network/SqrtTaskMiddleware.sol | 42 ++---- ...aseBLSKeyManager.sol => BLSKeyManager.sol} | 2 +- src/key-manager/DefaultBLSKeyManager.sol | 16 --- src/key-manager/DefaultKeyManager.sol | 16 --- .../{BaseKeyManager.sol => KeyManager.sol} | 18 +-- src/libraries/PauseableEnumerableSet.sol | 2 +- src/middleware/BaseMiddleware.sol | 62 +++++++++ src/middleware/extensions/Operators.sol | 35 +++++ src/middleware/extensions/SharedVaults.sol | 33 +++++ .../DefaultOperatorManager.sol | 38 ------ ...peratorManager.sol => OperatorManager.sol} | 2 +- src/vault-manager/DefaultVaultManager.sol | 122 ----------------- ...{BaseVaultManager.sol => VaultManager.sol} | 12 +- test/mocks/ExtendedSimplePosMiddleware.sol | 2 +- 18 files changed, 245 insertions(+), 315 deletions(-) create mode 100644 src/access-manager/AccessManager.sol rename src/key-manager/{BaseBLSKeyManager.sol => BLSKeyManager.sol} (98%) delete mode 100644 src/key-manager/DefaultBLSKeyManager.sol delete mode 100644 src/key-manager/DefaultKeyManager.sol rename src/key-manager/{BaseKeyManager.sol => KeyManager.sol} (81%) create mode 100644 src/middleware/BaseMiddleware.sol create mode 100644 src/middleware/extensions/Operators.sol create mode 100644 src/middleware/extensions/SharedVaults.sol delete mode 100644 src/operator-manager/DefaultOperatorManager.sol rename src/operator-manager/{BaseOperatorManager.sol => OperatorManager.sol} (98%) delete mode 100644 src/vault-manager/DefaultVaultManager.sol rename src/vault-manager/{BaseVaultManager.sol => VaultManager.sol} (98%) diff --git a/foundry.toml b/foundry.toml index 1a4fc90..529f204 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc-version = "0.8.25" +solc-version = "0.8.26" src = "src" out = "out" libs = ["lib"] diff --git a/src/BaseManager.sol b/src/BaseManager.sol index 4a36c19..84ae88d 100644 --- a/src/BaseManager.sol +++ b/src/BaseManager.sol @@ -5,7 +5,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -contract BaseManager is Initializable, OwnableUpgradeable { +abstract contract BaseManager is Initializable, OwnableUpgradeable { address public NETWORK; // Address of the network uint48 public SLASHING_WINDOW; // Duration of the slashing window address public VAULT_REGISTRY; // Address of the vault registry @@ -17,7 +17,6 @@ contract BaseManager is Initializable, OwnableUpgradeable { /* * @notice initalizer of the BaseManager contract. - * @param owner The address of the contract owner. * @param network The address of the network. * @param epochDuration The duration of each epoch. * @param slashingWindow The duration of the slashing window. @@ -26,12 +25,12 @@ contract BaseManager is Initializable, OwnableUpgradeable { * @param operatorNetOptIn The address of the operator network opt-in service. */ function initialize( - address owner, address network, uint48 slashingWindow, address vaultRegistry, address operatorRegistry, - address operatorNetOptIn + address operatorNetOptIn, + address owner ) public virtual initializer { __Ownable_init(owner); @@ -50,4 +49,8 @@ contract BaseManager is Initializable, OwnableUpgradeable { function getCaptureTimestamp() public view virtual returns (uint48 timestamp) { return Time.timestamp() - 1; } + + function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256 power) { + return stake; + } } diff --git a/src/access-manager/AccessManager.sol b/src/access-manager/AccessManager.sol new file mode 100644 index 0000000..f1cea9b --- /dev/null +++ b/src/access-manager/AccessManager.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import {BaseManager} from "../BaseManager.sol"; + +abstract contract AccessManager is BaseManager { + modifier checkAccess() { + _checkAccess(msg.sender, msg.sig); + _; + } + + /** + * @notice Checks if the user has access to the given selector. + * @param caller The address to check access for. + * @param selector The selector to check access for. + */ + function _checkAccess(address caller, bytes4 selector) internal view {} +} diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index b919111..f805d43 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -9,11 +9,14 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {DefaultVaultManager} from "../../vault-manager/DefaultVaultManager.sol"; -import {DefaultOperatorManager} from "../../operator-manager/DefaultOperatorManager.sol"; -import {DefaultKeyManager} from "../../key-manager/DefaultKeyManager.sol"; +import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; +import {Operators} from "../../middleware/extensions/Operators.sol"; -contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager { +import {KeyManager} from "../../key-manager/KeyManager.sol"; + + +contract SimplePosMiddleware is SharedVaults, Operators, KeyManager { using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key @@ -47,8 +50,7 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def address owner, uint48 epochDuration, uint48 slashingWindow - ) { - initialize(owner, network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + ) BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) { EPOCH_DURATION = epochDuration; START_TIMESTAMP = Time.timestamp(); } @@ -108,7 +110,7 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def for (uint256 i; i < operators.length; ++i) { address operator = operators[i]; // Get the operator address - bytes32 key = operatorKey(operator); // Get the key for the operator + bytes32 key = abi.decode(operatorKey(operator), (bytes32)); // Get the key for the operator if (key == bytes32(0) || !keyWasActiveAt(getCaptureTimestamp(), key)) { continue; // Skip if the key is inactive } @@ -117,7 +119,7 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def validatorSet[len++] = ValidatorData(power, key); // Store the validator data } - assembly { + assembly ("memory-safe") { mstore(validatorSet, len) // Update the length of the array } } @@ -139,58 +141,59 @@ contract SimplePosMiddleware is DefaultVaultManager, DefaultOperatorManager, Def onlyOwner returns (SlashResponse[] memory slashResponses) { - uint48 epochStart = getEpochStart(epoch); // Get the start timestamp for the epoch - address operator = operatorByKey(key); // Get the operator associated with the key - - if (operator == address(0)) { - revert NotExistKeySlash(); // Revert if the operator does not exist - } - - if (!keyWasActiveAt(epoch, key)) { - revert InactiveKeySlash(); // Revert if the key is inactive - } - - if (!operatorWasActiveAt(epoch, operator)) { - revert InactiveOperatorSlash(); // Revert if the operator wasn't active - } - - uint256 totalStake = getOperatorStakeAt(operator, epochStart); // Get the total stake for the operator - address[] memory vaults = activeVaultsAt(epochStart, operator); // Get active vaults for the operator - uint160[] memory subnetworks = activeSubnetworksAt(epochStart); // Get active subnetworks - - slashResponses = new SlashResponse[](vaults.length * subnetworks.length); // Initialize the array for slash responses - uint256 len = 0; // Length counter - - if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { - revert InvalidHints(); // Revert if the hints do not match in length - } - - for (uint256 i; i < vaults.length; ++i) { - if (stakeHints[i].length != subnetworks.length) { - revert InvalidHints(); // Revert if the stake hints do not match the subnetworks - } - - address vault = vaults[i]; // Get the vault address - for (uint256 j = 0; j < subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); // Get the subnetwork - uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( - subnetwork, - operator, - epochStart, - stakeHints[i][j] // Get the stake at the specified subnetwork - ); - - uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); // Calculate the slashing amount - if (slashAmount == 0) { - continue; // Skip if the slashing amount is zero - } - - slashResponses[len++] = _slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); // Execute the slashing - } - } - - assembly { - mstore(slashResponses, len) // Update the length of the slash responses - } + // uint48 epochStart = getEpochStart(epoch); // Get the start timestamp for the epoch + // address operator = operatorByKey(abi.encode(key)); // Get the operator associated with the key + + + // if (operator == address(0)) { + // revert NotExistKeySlash(); // Revert if the operator does not exist + // } + + // if (!keyWasActiveAt(epoch, key)) { + // revert InactiveKeySlash(); // Revert if the key is inactive + // } + + // if (!operatorWasActiveAt(epoch, operator)) { + // revert InactiveOperatorSlash(); // Revert if the operator wasn't active + // } + + // uint256 totalStake = getOperatorStakeAt(operator, epochStart); // Get the total stake for the operator + // address[] memory vaults = activeVaultsAt(epochStart, operator); // Get active vaults for the operator + // uint160[] memory subnetworks = activeSubnetworksAt(epochStart); // Get active subnetworks + + // slashResponses = new SlashResponse[](vaults.length * subnetworks.length); // Initialize the array for slash responses + // uint256 len = 0; // Length counter + + // if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { + // revert InvalidHints(); // Revert if the hints do not match in length + // } + + // for (uint256 i; i < vaults.length; ++i) { + // if (stakeHints[i].length != subnetworks.length) { + // revert InvalidHints(); // Revert if the stake hints do not match the subnetworks + // } + + // address vault = vaults[i]; // Get the vault address + // for (uint256 j = 0; j < subnetworks.length; ++j) { + // bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); // Get the subnetwork + // uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( + // subnetwork, + // operator, + // epochStart, + // stakeHints[i][j] // Get the stake at the specified subnetwork + // ); + + // uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); // Calculate the slashing amount + // if (slashAmount == 0) { + // continue; // Skip if the slashing amount is zero + // } + + // slashResponses[len++] = _slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); // Execute the slashing + // } + // } + + // assembly ("memory-safe") { + // mstore(slashResponses, len) // Update the length of the slash responses + // } } } diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 356cf58..d69ee17 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -10,11 +10,14 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {DefaultVaultManager} from "../../vault-manager/DefaultVaultManager.sol"; -import {DefaultOperatorManager} from "../../operator-manager/DefaultOperatorManager.sol"; -import {DefaultKeyManager} from "../../key-manager/DefaultKeyManager.sol"; +import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; +import {Operators} from "../../middleware/extensions/Operators.sol"; -contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, DefaultKeyManager, EIP712 { +import {KeyManager} from "../../key-manager/KeyManager.sol"; + + +contract SqrtTaskMiddleware is SharedVaults, Operators, KeyManager, EIP712 { using Subnetwork for address; using Math for uint256; @@ -43,8 +46,7 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa address operatorNetOptin, address owner, uint48 slashingWindow - ) EIP712("SqrtTaskMiddleware", "1") { - initialize(owner, network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + ) EIP712("SqrtTaskMiddleware", "1") BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) { } function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { @@ -138,32 +140,4 @@ contract SqrtTaskMiddleware is DefaultVaultManager, DefaultOperatorManager, Defa _slashVault(task.captureTimestamp, vault, subnetwork, task.operator, slashAmount, slashHints[i]); } } - - /* - * inheritdoc BaseMiddleware - */ - function registerSubnetwork(uint96) public pure override { - revert(); - } - - /* - * inheritdoc BaseMiddleware - */ - function pauseSubnetwork(uint96) public pure override { - revert(); - } - - /* - * inheritdoc BaseMiddleware - */ - function unpauseSubnetwork(uint96) public pure override { - revert(); - } - - /* - * inheritdoc BaseMiddleware - */ - function unregisterSubnetwork(uint96) public pure override { - revert(); - } } diff --git a/src/key-manager/BaseBLSKeyManager.sol b/src/key-manager/BLSKeyManager.sol similarity index 98% rename from src/key-manager/BaseBLSKeyManager.sol rename to src/key-manager/BLSKeyManager.sol index 33a962d..acef4fb 100644 --- a/src/key-manager/BaseBLSKeyManager.sol +++ b/src/key-manager/BLSKeyManager.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.25; import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BaseBLSKeyManager is BaseManager { +abstract contract BLSKeyManager is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateBLSKey(); diff --git a/src/key-manager/DefaultBLSKeyManager.sol b/src/key-manager/DefaultBLSKeyManager.sol deleted file mode 100644 index fa126f5..0000000 --- a/src/key-manager/DefaultBLSKeyManager.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {BaseBLSKeyManager} from "./BaseBLSKeyManager.sol"; - -abstract contract DefaultBLSKeyManager is BaseBLSKeyManager { - /* - * @notice Updates the BLS key associated with an operator. - * If the new key already exists, a DuplicateBLSKey error is thrown. - * @param operator The address of the operator whose BLS key is to be updated. - * @param key The new BLS key to associate with the operator. - */ - function updateBLSKey(address operator, bytes memory key) public virtual onlyOwner { - _updateBLSKey(operator, key); - } -} diff --git a/src/key-manager/DefaultKeyManager.sol b/src/key-manager/DefaultKeyManager.sol deleted file mode 100644 index 4fc11d5..0000000 --- a/src/key-manager/DefaultKeyManager.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {BaseKeyManager} from "./BaseKeyManager.sol"; - -abstract contract DefaultKeyManager is BaseKeyManager { - /* - * @notice Updates the key associated with an operator. - * If the new key already exists, a DuplicateKey error is thrown. - * @param operator The address of the operator whose key is to be updated. - * @param key The new key to associate with the operator. - */ - function updateKey(address operator, bytes32 key) public virtual onlyOwner { - _updateKey(operator, key); - } -} diff --git a/src/key-manager/BaseKeyManager.sol b/src/key-manager/KeyManager.sol similarity index 81% rename from src/key-manager/BaseKeyManager.sol rename to src/key-manager/KeyManager.sol index 61ea511..b18c9bc 100644 --- a/src/key-manager/BaseKeyManager.sol +++ b/src/key-manager/KeyManager.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseManager} from "../BaseManager.sol"; +import {BaseMiddleware} from "../middleware/BaseMiddleware.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -abstract contract BaseKeyManager is BaseManager { +abstract contract KeyManager is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; error DuplicateKey(); @@ -24,8 +24,8 @@ abstract contract BaseKeyManager is BaseManager { * @return The address of the operator linked to the specified key */ - function operatorByKey(bytes32 key) public view returns (address) { - return keyToOperator[key]; + function operatorByKey(bytes memory key) public view override returns (address) { + return keyToOperator[abi.decode(key, (bytes32))]; } /** @@ -34,8 +34,8 @@ abstract contract BaseKeyManager is BaseManager { * @param operator The address of the operator * @return The key associated with the specified operator */ - function operatorKey(address operator) public view returns (bytes32) { - return keys[operator].getActive(getCaptureTimestamp())[0]; + function operatorKey(address operator) public view override returns (bytes memory) { + return abi.encode(keys[operator].getActive(getCaptureTimestamp())[0]); } /** @@ -52,9 +52,11 @@ abstract contract BaseKeyManager is BaseManager { * @notice Updates the key associated with an operator * @dev Reverts if the key is already enabled or if another operator is using it * @param operator The address of the operator whose key is to be updated - * @param key The new key to associate with the operator + * @param key_ The new key to associate with the operator */ - function _updateKey(address operator, bytes32 key) internal { + function _updateKey(address operator, bytes memory key_) internal override { + bytes32 key = abi.decode(key_, (bytes32)); + if (keyToOperator[key] != address(0)) { revert DuplicateKey(); } diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 08dd487..13ffbc5 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -281,7 +281,7 @@ library PauseableEnumerableSet { /* * @notice Returns the address stored in the Inner struct. * @param self The Inner struct - * @return The stored Uint160 as address + * @return The stored Uint160 as address */ function getAddress(Inner storage self) internal view returns (address) { return address(self.value); diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol new file mode 100644 index 0000000..f01f46a --- /dev/null +++ b/src/middleware/BaseMiddleware.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +import {VaultManager} from "../vault-manager/VaultManager.sol"; +import {OperatorManager} from "../operator-manager/OperatorManager.sol"; +import {AccessManager} from "../access-manager/AccessManager.sol"; + + +abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager { + using Subnetwork for address; + + /* + * @notice Constructor for initializing the BaseMiddleware contract. + * @param network The address of the network. + * @param operatorRegistry The address of the operator registry. + * @param vaultRegistry The address of the vault registry. + * @param operatorNetOptin The address of the operator network opt-in service. + * @param epochDuration The duration of each epoch. + * @param slashingWindow The duration of the slashing window + */ + constructor( + address network, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + uint48 slashingWindow, + address owner + ) { + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, owner); + } + + /** + * @notice Updates the key associated with an operator + * @param operator The address of the operator + * @param key The key to update + */ + function _updateKey(address operator, bytes memory key) internal virtual; + + /** + * @notice Returns the operator address associated with a given key + * @param key The key for which to find the associated operator + * @return The address of the operator linked to the specified key + */ + + function operatorByKey(bytes memory key) public view virtual returns (address); + + /** + * @notice Returns the current or previous key for a given operator + * @dev Returns the previous key if the key was updated in the current epoch + * @param operator The address of the operator + * @return The key associated with the specified operator + */ + function operatorKey(address operator) public view virtual returns (bytes memory); +} diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/Operators.sol new file mode 100644 index 0000000..0bb89aa --- /dev/null +++ b/src/middleware/extensions/Operators.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {BaseMiddleware} from "../BaseMiddleware.sol"; + +abstract contract Operators is BaseMiddleware { + function registerOperator(address operator, bytes memory key, address vault) public checkAccess { + _beforeRegisterOperator(operator, key, vault); + _registerOperator(operator); + _updateKey(operator, key); + _registerOperatorVault(operator, vault); + } + + function unregisterOperator(address operator) public checkAccess { + _beforeUnregisterOperator(operator); + _unregisterOperator(operator); + } + + function pauseOperator(address operator) public checkAccess { + _beforePauseOperator(operator); + _pauseOperator(operator); + } + + function unpauseOperator(address operator) public checkAccess { + _beforeUnpauseOperator(operator); + _unpauseOperator(operator); + } + + function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} + function _beforeUnregisterOperator(address operator) internal virtual {} + function _beforePauseOperator(address operator) internal virtual {} + function _beforeUnpauseOperator(address operator) internal virtual {} +} \ No newline at end of file diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol new file mode 100644 index 0000000..89d7773 --- /dev/null +++ b/src/middleware/extensions/SharedVaults.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {BaseMiddleware} from "../BaseMiddleware.sol"; + +abstract contract SharedVaults is BaseMiddleware { + function registerSharedVault(address sharedVault, bytes memory udata) public checkAccess { + _beforeRegisterSharedVault(sharedVault, udata); + _registerSharedVault(sharedVault); + } + + function pauseSharedVault(address sharedVault, bytes memory udata) public checkAccess { + _beforePauseSharedVault(sharedVault, udata); + _pauseSharedVault(sharedVault); + } + + function unpauseSharedVault(address sharedVault, bytes memory udata) public checkAccess { + _beforeUnpauseSharedVault(sharedVault, udata); + _unpauseSharedVault(sharedVault); + } + + function unregisterSharedVault(address sharedVault, bytes memory udata) public checkAccess { + _beforeUnregisterSharedVault(sharedVault, udata); + _unregisterSharedVault(sharedVault); + } + + function _beforeRegisterSharedVault(address sharedVault, bytes memory udata) internal virtual {} + function _beforePauseSharedVault(address sharedVault, bytes memory udata) internal virtual {} + function _beforeUnpauseSharedVault(address sharedVault, bytes memory udata) internal virtual {} + function _beforeUnregisterSharedVault(address sharedVault, bytes memory udata) internal virtual {} +} diff --git a/src/operator-manager/DefaultOperatorManager.sol b/src/operator-manager/DefaultOperatorManager.sol deleted file mode 100644 index 242ad81..0000000 --- a/src/operator-manager/DefaultOperatorManager.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {BaseOperatorManager} from "./BaseOperatorManager.sol"; - -abstract contract DefaultOperatorManager is BaseOperatorManager { - /* - * @notice Registers a new operator. - * @param operator The address of the operator to register. - */ - function registerOperator(address operator) public virtual onlyOwner { - _registerOperator(operator); - } - - /* - * @notice Pauses a registered operator. - * @param operator The address of the operator to pause. - */ - function pauseOperator(address operator) public virtual onlyOwner { - _pauseOperator(operator); - } - - /* - * @notice Unpauses a paused operator. - * @param operator The address of the operator to unpause. - */ - function unpauseOperator(address operator) public virtual onlyOwner { - _unpauseOperator(operator); - } - - /* - * @notice Unregisters an operator. - * @param operator The address of the operator to unregister. - */ - function unregisterOperator(address operator) public virtual onlyOwner { - _unregisterOperator(operator); - } -} diff --git a/src/operator-manager/BaseOperatorManager.sol b/src/operator-manager/OperatorManager.sol similarity index 98% rename from src/operator-manager/BaseOperatorManager.sol rename to src/operator-manager/OperatorManager.sol index 5bb426d..5ee4ebb 100644 --- a/src/operator-manager/BaseOperatorManager.sol +++ b/src/operator-manager/OperatorManager.sol @@ -11,7 +11,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BaseOperatorManager is BaseManager { +abstract contract OperatorManager is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; error NotOperator(); diff --git a/src/vault-manager/DefaultVaultManager.sol b/src/vault-manager/DefaultVaultManager.sol deleted file mode 100644 index e073409..0000000 --- a/src/vault-manager/DefaultVaultManager.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {BaseVaultManager} from "./BaseVaultManager.sol"; - -abstract contract DefaultVaultManager is BaseVaultManager { - /* - * @notice Registers a new subnetwork. - * @param subnetwork The identifier of the subnetwork to register. - */ - function registerSubnetwork(uint96 subnetwork) public virtual onlyOwner { - _registerSubnetwork(subnetwork); - } - - /* - * @notice Pauses a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to pause. - */ - function pauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { - _pauseSubnetwork(subnetwork); - } - - /* - * @notice Unpauses a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to unpause. - */ - function unpauseSubnetwork(uint96 subnetwork) public virtual onlyOwner { - _unpauseSubnetwork(subnetwork); - } - - /* - * @notice Unregisters a specified subnetwork. - * @param subnetwork The identifier of the subnetwork to unregister. - */ - function unregisterSubnetwork(uint96 subnetwork) public virtual onlyOwner { - _unregisterSubnetwork(subnetwork); - } - - /* - * @notice Registers a new shared vault. - * @param vault The address of the vault to register. - */ - function registerSharedVault(address vault) public virtual onlyOwner { - _registerSharedVault(vault); - } - - /* - * @notice Registers a new operator vault. - * @param operator The address of the operator. - * @param vault The address of the vault to register. - */ - function registerOperatorVault(address operator, address vault) public virtual onlyOwner { - _registerOperatorVault(operator, vault); - } - - /* - * @notice Pauses a shared vault. - * @param vault The address of the vault to pause. - */ - function pauseSharedVault(address vault) public virtual onlyOwner { - _pauseSharedVault(vault); - } - - /* - * @notice Unpauses a shared vault. - * @param vault The address of the vault to unpause. - */ - function unpauseSharedVault(address vault) public virtual onlyOwner { - _unpauseSharedVault(vault); - } - - /* - * @notice Pauses an operator vault. - * @param operator The address of the operator. - * @param vault The address of the vault to pause. - */ - function pauseOperatorVault(address operator, address vault) public virtual onlyOwner { - _pauseOperatorVault(operator, vault); - } - - /* - * @notice Unpauses an operator vault. - * @param operator The address of the operator. - * @param vault The address of the vault to unpause. - */ - function unpauseOperatorVault(address operator, address vault) public virtual onlyOwner { - _unpauseOperatorVault(operator, vault); - } - - /* - * @notice Unregisters a shared vault. - * @param vault The address of the vault to unregister. - */ - function unregisterSharedVault(address vault) public virtual onlyOwner { - _unregisterSharedVault(vault); - } - - /* - * @notice Unregisters an operator vault. - * @param operator The address of the operator. - * @param vault The address of the vault to unregister. - */ - function unregisterOperatorVault(address operator, address vault) public virtual onlyOwner { - _unregisterOperatorVault(operator, vault); - } - - /* - * @notice Executes a veto-based slash for a vault. - * @param vault The address of the vault. - * @param slashIndex The index of the slash to execute. - * @param hints Additional data for the veto slasher. - * @return The amount that was slashed. - */ - function executeSlash(address vault, uint256 slashIndex, bytes calldata hints) - public - virtual - onlyOwner - returns (uint256 slashedAmount) - { - return _executeSlash(vault, slashIndex, hints); - } -} diff --git a/src/vault-manager/BaseVaultManager.sol b/src/vault-manager/VaultManager.sol similarity index 98% rename from src/vault-manager/BaseVaultManager.sol rename to src/vault-manager/VaultManager.sol index 5a3db5d..3629535 100644 --- a/src/vault-manager/BaseVaultManager.sol +++ b/src/vault-manager/VaultManager.sol @@ -17,7 +17,7 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {BaseManager} from "../BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BaseVaultManager is BaseManager { +abstract contract VaultManager is BaseManager { using EnumerableMap for EnumerableMap.AddressToUintMap; using EnumerableMap for EnumerableMap.AddressToAddressMap; using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; @@ -149,16 +149,6 @@ abstract contract BaseVaultManager is BaseManager { return _operatorVaults[operator].getActive(getCaptureTimestamp()); } - /** - * @notice Converts stake amount to power for a vault - * @param vault The address of the vault - * @param stake The amount of stake to convert - * @return The calculated power amount - */ - function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256) { - vault; - return stake; - } /** * @notice Returns all active vaults at the current capture timestamp diff --git a/test/mocks/ExtendedSimplePosMiddleware.sol b/test/mocks/ExtendedSimplePosMiddleware.sol index 2c5350f..5fb4a95 100644 --- a/test/mocks/ExtendedSimplePosMiddleware.sol +++ b/test/mocks/ExtendedSimplePosMiddleware.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.25; import {SimplePosMiddleware} from "../../src/examples/simple-pos-network/SimplePosMiddleware.sol"; -import {DefaultKeyManager} from "../../src/key-manager/DefaultKeyManager.sol"; +import {KeyManager} from "../../src/key-manager/KeyManager.sol"; contract ExtendedSimplePosMiddleware is SimplePosMiddleware { constructor( From 35873afc7ac861e0caaa78345f849af89bd4c93e Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Mon, 11 Nov 2024 15:08:41 +0700 Subject: [PATCH 038/115] refactor: reorganized and add selfresgiterOM --- .../SimplePosMiddleware.sol | 6 ++-- .../sqrt-task-network/SqrtTaskMiddleware.sol | 11 ++++--- .../BLSKeyStorage.sol} | 4 +-- .../KeyStorage.sol} | 2 +- .../AccessManager.sol | 6 +--- src/{ => managers}/BaseManager.sol | 0 .../OperatorManager.sol | 2 +- .../VaultManager.sol | 3 +- src/middleware/BaseMiddleware.sol | 8 ++--- src/middleware/extensions/Operators.sol | 2 +- .../extensions/SelfRegisterOperators.sol | 33 +++++++++++++++++++ src/middleware/extensions/SharedVaults.sol | 2 -- test/mocks/ExtendedSimplePosMiddleware.sol | 1 - 13 files changed, 51 insertions(+), 29 deletions(-) rename src/{key-manager/BLSKeyManager.sol => key-storage/BLSKeyStorage.sol} (96%) rename src/{key-manager/KeyManager.sol => key-storage/KeyStorage.sol} (98%) rename src/{access-manager => managers}/AccessManager.sol (67%) rename src/{ => managers}/BaseManager.sol (100%) rename src/{operator-manager => managers}/OperatorManager.sol (98%) rename src/{vault-manager => managers}/VaultManager.sol (99%) create mode 100644 src/middleware/extensions/SelfRegisterOperators.sol diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index f805d43..cc3a33a 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -13,10 +13,9 @@ import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/Operators.sol"; -import {KeyManager} from "../../key-manager/KeyManager.sol"; +import {KeyStorage} from "../../key-storage/KeyStorage.sol"; - -contract SimplePosMiddleware is SharedVaults, Operators, KeyManager { +contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage { using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key @@ -144,7 +143,6 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyManager { // uint48 epochStart = getEpochStart(epoch); // Get the start timestamp for the epoch // address operator = operatorByKey(abi.encode(key)); // Get the operator associated with the key - // if (operator == address(0)) { // revert NotExistKeySlash(); // Revert if the operator does not exist // } diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index d69ee17..ff2fe61 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -14,10 +14,9 @@ import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/Operators.sol"; -import {KeyManager} from "../../key-manager/KeyManager.sol"; +import {KeyStorage} from "../../key-storage/KeyStorage.sol"; - -contract SqrtTaskMiddleware is SharedVaults, Operators, KeyManager, EIP712 { +contract SqrtTaskMiddleware is SharedVaults, Operators, KeyStorage, EIP712 { using Subnetwork for address; using Math for uint256; @@ -46,8 +45,10 @@ contract SqrtTaskMiddleware is SharedVaults, Operators, KeyManager, EIP712 { address operatorNetOptin, address owner, uint48 slashingWindow - ) EIP712("SqrtTaskMiddleware", "1") BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) { - } + ) + EIP712("SqrtTaskMiddleware", "1") + BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) + {} function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { taskIndex = tasks.length; diff --git a/src/key-manager/BLSKeyManager.sol b/src/key-storage/BLSKeyStorage.sol similarity index 96% rename from src/key-manager/BLSKeyManager.sol rename to src/key-storage/BLSKeyStorage.sol index acef4fb..206d71a 100644 --- a/src/key-manager/BLSKeyManager.sol +++ b/src/key-storage/BLSKeyStorage.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseManager} from "../BaseManager.sol"; +import {BaseManager} from "../managers/BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BLSKeyManager is BaseManager { +abstract contract BlsKeyStorage is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; error DuplicateBLSKey(); diff --git a/src/key-manager/KeyManager.sol b/src/key-storage/KeyStorage.sol similarity index 98% rename from src/key-manager/KeyManager.sol rename to src/key-storage/KeyStorage.sol index b18c9bc..17bc4d3 100644 --- a/src/key-manager/KeyManager.sol +++ b/src/key-storage/KeyStorage.sol @@ -6,7 +6,7 @@ import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -abstract contract KeyManager is BaseMiddleware { +abstract contract KeyStorage is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; error DuplicateKey(); diff --git a/src/access-manager/AccessManager.sol b/src/managers/AccessManager.sol similarity index 67% rename from src/access-manager/AccessManager.sol rename to src/managers/AccessManager.sol index f1cea9b..e11dedd 100644 --- a/src/access-manager/AccessManager.sol +++ b/src/managers/AccessManager.sol @@ -1,11 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -import {BaseManager} from "../BaseManager.sol"; - -abstract contract AccessManager is BaseManager { +abstract contract AccessManager { modifier checkAccess() { _checkAccess(msg.sender, msg.sig); _; diff --git a/src/BaseManager.sol b/src/managers/BaseManager.sol similarity index 100% rename from src/BaseManager.sol rename to src/managers/BaseManager.sol diff --git a/src/operator-manager/OperatorManager.sol b/src/managers/OperatorManager.sol similarity index 98% rename from src/operator-manager/OperatorManager.sol rename to src/managers/OperatorManager.sol index 5ee4ebb..66c8344 100644 --- a/src/operator-manager/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -8,7 +8,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {BaseManager} from "../BaseManager.sol"; +import {BaseManager} from "./BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; abstract contract OperatorManager is BaseManager { diff --git a/src/vault-manager/VaultManager.sol b/src/managers/VaultManager.sol similarity index 99% rename from src/vault-manager/VaultManager.sol rename to src/managers/VaultManager.sol index 3629535..4f782d9 100644 --- a/src/vault-manager/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -14,7 +14,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import {BaseManager} from "../BaseManager.sol"; +import {BaseManager} from "./BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; abstract contract VaultManager is BaseManager { @@ -149,7 +149,6 @@ abstract contract VaultManager is BaseManager { return _operatorVaults[operator].getActive(getCaptureTimestamp()); } - /** * @notice Returns all active vaults at the current capture timestamp * @return An array of active vault addresses diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index f01f46a..3a76478 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -9,10 +9,9 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {VaultManager} from "../vault-manager/VaultManager.sol"; -import {OperatorManager} from "../operator-manager/OperatorManager.sol"; -import {AccessManager} from "../access-manager/AccessManager.sol"; - +import {VaultManager} from "../managers/VaultManager.sol"; +import {OperatorManager} from "../managers/OperatorManager.sol"; +import {AccessManager} from "../managers/AccessManager.sol"; abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager { using Subnetwork for address; @@ -49,7 +48,6 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ - function operatorByKey(bytes memory key) public view virtual returns (address); /** diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/Operators.sol index 0bb89aa..ebc1f91 100644 --- a/src/middleware/extensions/Operators.sol +++ b/src/middleware/extensions/Operators.sol @@ -32,4 +32,4 @@ abstract contract Operators is BaseMiddleware { function _beforeUnregisterOperator(address operator) internal virtual {} function _beforePauseOperator(address operator) internal virtual {} function _beforeUnpauseOperator(address operator) internal virtual {} -} \ No newline at end of file +} diff --git a/src/middleware/extensions/SelfRegisterOperators.sol b/src/middleware/extensions/SelfRegisterOperators.sol new file mode 100644 index 0000000..f9e1bbc --- /dev/null +++ b/src/middleware/extensions/SelfRegisterOperators.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseMiddleware} from "../BaseMiddleware.sol"; + +abstract contract Operators is BaseMiddleware { + function registerOperator(address operator, bytes memory key, address vault) public checkAccess { + _beforeRegisterOperator(operator, key, vault); + _registerOperator(operator); + _updateKey(operator, key); + _registerOperatorVault(operator, vault); + } + + function unregisterOperator(address operator) public checkAccess { + _beforeUnregisterOperator(operator); + _unregisterOperator(operator); + } + + function pauseOperator(address operator) public checkAccess { + _beforePauseOperator(operator); + _pauseOperator(operator); + } + + function unpauseOperator(address operator) public checkAccess { + _beforeUnpauseOperator(operator); + _unpauseOperator(operator); + } + + function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} + function _beforeUnregisterOperator(address operator) internal virtual {} + function _beforePauseOperator(address operator) internal virtual {} + function _beforeUnpauseOperator(address operator) internal virtual {} +} diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 89d7773..0851ed7 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract SharedVaults is BaseMiddleware { diff --git a/test/mocks/ExtendedSimplePosMiddleware.sol b/test/mocks/ExtendedSimplePosMiddleware.sol index 5fb4a95..2856854 100644 --- a/test/mocks/ExtendedSimplePosMiddleware.sol +++ b/test/mocks/ExtendedSimplePosMiddleware.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.25; import {SimplePosMiddleware} from "../../src/examples/simple-pos-network/SimplePosMiddleware.sol"; -import {KeyManager} from "../../src/key-manager/KeyManager.sol"; contract ExtendedSimplePosMiddleware is SimplePosMiddleware { constructor( From e7d8efb4b50b888871420312907033913b7cbbd9 Mon Sep 17 00:00:00 2001 From: gm256 Date: Tue, 12 Nov 2024 07:37:33 +0400 Subject: [PATCH 039/115] fixed set --- src/libraries/PauseableEnumerableSet.sol | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 08dd487..25b9de2 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -305,6 +305,7 @@ library PauseableEnumerableSet { function set(Inner storage self, uint48 timestamp, uint160 value) internal { self.value = value; self.enabledTimestamp = timestamp; + self.disabledTimestamp = 0; } /* @@ -316,6 +317,7 @@ library PauseableEnumerableSet { function set(Inner storage self, uint48 timestamp, address addr) internal { self.value = uint160(addr); self.enabledTimestamp = timestamp; + self.disabledTimestamp = 0; } /* @@ -329,6 +331,7 @@ library PauseableEnumerableSet { } self.enabledTimestamp = timestamp; + self.disabledTimestamp = 0; } /* @@ -340,8 +343,12 @@ library PauseableEnumerableSet { if (self.disabledTimestamp != 0) { revert NotEnabled(); } + if (self.enabledTimestamp == 0) { + revert NotEnabled(); + } self.disabledTimestamp = timestamp; + self.enabledTimestamp = 0; } /* @@ -352,7 +359,7 @@ library PauseableEnumerableSet { */ function wasActiveAt(Inner storage self, uint48 timestamp) internal view returns (bool) { return - self.enabledTimestamp <= timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); + self.enabledTimestamp < timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); } /* @@ -728,6 +735,7 @@ library PauseableEnumerableSet { } self.enabledTimestamp = timestamp; + self.disabledTimestamp = 0; } /* @@ -751,7 +759,7 @@ library PauseableEnumerableSet { */ function wasActiveAt(Inner256 storage self, uint48 timestamp) internal view returns (bool) { return - self.enabledTimestamp <= timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); + self.enabledTimestamp < timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); } /* From a656bc0e9362be9d64a4ba1cbda99e00f0e886c9 Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Tue, 12 Nov 2024 10:58:45 +0700 Subject: [PATCH 040/115] merge --- .../SimplePosMiddleware.sol | 92 ++++++++++--------- src/key-storage/KeyStorage.sol | 3 +- src/middleware/extensions/SharedVaults.sol | 24 ++--- 3 files changed, 63 insertions(+), 56 deletions(-) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index cc3a33a..eed2c91 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -138,60 +138,66 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage { function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) public onlyOwner - returns (SlashResponse[] memory slashResponses) { - // uint48 epochStart = getEpochStart(epoch); // Get the start timestamp for the epoch - // address operator = operatorByKey(abi.encode(key)); // Get the operator associated with the key + uint48 epochStart = getEpochStart(epoch); + address operator = operatorByKey(abi.encode(key)); +<<<<<<< Updated upstream // if (operator == address(0)) { // revert NotExistKeySlash(); // Revert if the operator does not exist // } +======= + _checkCanSlash(epoch, key); +>>>>>>> Stashed changes - // if (!keyWasActiveAt(epoch, key)) { - // revert InactiveKeySlash(); // Revert if the key is inactive - // } + uint256 totalStake = getOperatorStakeAt(operator, epochStart); + address[] memory vaults = activeVaultsAt(epochStart, operator); + uint160[] memory subnetworks = activeSubnetworksAt(epochStart); - // if (!operatorWasActiveAt(epoch, operator)) { - // revert InactiveOperatorSlash(); // Revert if the operator wasn't active - // } + // Validate hints lengths upfront + if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { + revert InvalidHints(); + } - // uint256 totalStake = getOperatorStakeAt(operator, epochStart); // Get the total stake for the operator - // address[] memory vaults = activeVaultsAt(epochStart, operator); // Get active vaults for the operator - // uint160[] memory subnetworks = activeSubnetworksAt(epochStart); // Get active subnetworks + for (uint256 i; i < vaults.length; ++i) { + if (stakeHints[i].length != subnetworks.length) { + revert InvalidHints(); + } + + address vault = vaults[i]; + for (uint256 j; j < subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); + uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( + subnetwork, + operator, + epochStart, + stakeHints[i][j] + ); + + uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); + if (slashAmount == 0) { + continue; + } + + _slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); + } + } + } - // slashResponses = new SlashResponse[](vaults.length * subnetworks.length); // Initialize the array for slash responses - // uint256 len = 0; // Length counter + function _checkCanSlash(uint48 epoch, bytes32 key) internal view { + address operator = operatorByKey(abi.encode(key)); // Get the operator associated with the key + uint48 epochStart = getEpochStart(epoch); // Get the start timestamp for the epoch - // if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { - // revert InvalidHints(); // Revert if the hints do not match in length - // } + if (operator == address(0)) { + revert NotExistKeySlash(); // Revert if the operator does not exist + } - // for (uint256 i; i < vaults.length; ++i) { - // if (stakeHints[i].length != subnetworks.length) { - // revert InvalidHints(); // Revert if the stake hints do not match the subnetworks - // } - - // address vault = vaults[i]; // Get the vault address - // for (uint256 j = 0; j < subnetworks.length; ++j) { - // bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); // Get the subnetwork - // uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( - // subnetwork, - // operator, - // epochStart, - // stakeHints[i][j] // Get the stake at the specified subnetwork - // ); - - // uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); // Calculate the slashing amount - // if (slashAmount == 0) { - // continue; // Skip if the slashing amount is zero - // } - - // slashResponses[len++] = _slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); // Execute the slashing - // } - // } + if (!keyWasActiveAt(epochStart, key)) { + revert InactiveKeySlash(); // Revert if the key is inactive + } - // assembly ("memory-safe") { - // mstore(slashResponses, len) // Update the length of the slash responses - // } + if (!operatorWasActiveAt(epochStart, operator)) { + revert InactiveOperatorSlash(); // Revert if the operator wasn't active + } } } diff --git a/src/key-storage/KeyStorage.sol b/src/key-storage/KeyStorage.sol index 17bc4d3..bdd09da 100644 --- a/src/key-storage/KeyStorage.sol +++ b/src/key-storage/KeyStorage.sol @@ -17,7 +17,8 @@ abstract contract KeyStorage is BaseMiddleware { uint256 private constant MAX_DISABLED_KEYS = 1; mapping(address => PauseableEnumerableSet.Bytes32Set) internal keys; // Mapping from operator addresses to their current keys - mapping(bytes32 => address) internal keyToOperator; + mapping(bytes32 => address) internal keyToOperator; // Mapping from keys to operator addresses + /** * @notice Returns the operator address associated with a given key * @param key The key for which to find the associated operator diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 0851ed7..77347ff 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -4,28 +4,28 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract SharedVaults is BaseMiddleware { - function registerSharedVault(address sharedVault, bytes memory udata) public checkAccess { - _beforeRegisterSharedVault(sharedVault, udata); + function registerSharedVault(address sharedVault) public checkAccess { + _beforeRegisterSharedVault(sharedVault); _registerSharedVault(sharedVault); } - function pauseSharedVault(address sharedVault, bytes memory udata) public checkAccess { - _beforePauseSharedVault(sharedVault, udata); + function pauseSharedVault(address sharedVault) public checkAccess { + _beforePauseSharedVault(sharedVault); _pauseSharedVault(sharedVault); } - function unpauseSharedVault(address sharedVault, bytes memory udata) public checkAccess { - _beforeUnpauseSharedVault(sharedVault, udata); + function unpauseSharedVault(address sharedVault) public checkAccess { + _beforeUnpauseSharedVault(sharedVault); _unpauseSharedVault(sharedVault); } - function unregisterSharedVault(address sharedVault, bytes memory udata) public checkAccess { - _beforeUnregisterSharedVault(sharedVault, udata); + function unregisterSharedVault(address sharedVault) public checkAccess { + _beforeUnregisterSharedVault(sharedVault); _unregisterSharedVault(sharedVault); } - function _beforeRegisterSharedVault(address sharedVault, bytes memory udata) internal virtual {} - function _beforePauseSharedVault(address sharedVault, bytes memory udata) internal virtual {} - function _beforeUnpauseSharedVault(address sharedVault, bytes memory udata) internal virtual {} - function _beforeUnregisterSharedVault(address sharedVault, bytes memory udata) internal virtual {} + function _beforeRegisterSharedVault(address sharedVault) internal virtual {} + function _beforePauseSharedVault(address sharedVault) internal virtual {} + function _beforeUnpauseSharedVault(address sharedVault) internal virtual {} + function _beforeUnregisterSharedVault(address sharedVault) internal virtual {} } From 5ce6284064d2a3a62e995450f11cd0e7cdba4496 Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Tue, 12 Nov 2024 11:00:14 +0700 Subject: [PATCH 041/115] feat: add sumberworks extension --- .../SimplePosMiddleware.sol | 8 ++--- src/middleware/extensions/Subnetworks.sol | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 src/middleware/extensions/Subnetworks.sol diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index eed2c91..d7757e2 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -15,6 +15,7 @@ import {Operators} from "../../middleware/extensions/Operators.sol"; import {KeyStorage} from "../../key-storage/KeyStorage.sol"; + contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage { using Subnetwork for address; @@ -138,17 +139,12 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage { function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) public onlyOwner + returns (SlashResponse[] memory slashResponses) { uint48 epochStart = getEpochStart(epoch); address operator = operatorByKey(abi.encode(key)); -<<<<<<< Updated upstream - // if (operator == address(0)) { - // revert NotExistKeySlash(); // Revert if the operator does not exist - // } -======= _checkCanSlash(epoch, key); ->>>>>>> Stashed changes uint256 totalStake = getOperatorStakeAt(operator, epochStart); address[] memory vaults = activeVaultsAt(epochStart, operator); diff --git a/src/middleware/extensions/Subnetworks.sol b/src/middleware/extensions/Subnetworks.sol new file mode 100644 index 0000000..4250bf1 --- /dev/null +++ b/src/middleware/extensions/Subnetworks.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {BaseMiddleware} from "../BaseMiddleware.sol"; + +abstract contract Subnetworks is BaseMiddleware { + function registerSubnetwork(uint96 subnetwork) public checkAccess { + _beforeRegisterSubnetwork(subnetwork); + _registerSubnetwork(subnetwork); + } + + function pauseSubnetwork(uint96 subnetwork) public checkAccess { + _beforePauseSubnetwork(subnetwork); + _pauseSubnetwork(subnetwork); + } + + function unpauseSubnetwork(uint96 subnetwork) public checkAccess { + _beforeUnpauseSubnetwork(subnetwork); + _unpauseSubnetwork(subnetwork); + } + + function unregisterSubnetwork(uint96 subnetwork) public checkAccess { + _beforeUnregisterSubnetwork(subnetwork); + _unregisterSubnetwork(subnetwork); + } + + function _beforeRegisterSubnetwork(uint96 subnetwork) internal virtual {} + function _beforePauseSubnetwork(uint96 subnetwork) internal virtual {} + function _beforeUnpauseSubnetwork(uint96 subnetwork) internal virtual {} + function _beforeUnregisterSubnetwork(uint96 subnetwork) internal virtual {} +} From d3b78103a60bfa10f21009f84550211ac0ca5b6d Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Tue, 12 Nov 2024 11:46:56 +0700 Subject: [PATCH 042/115] feat: add oz access managed --- .../simple-pos-network/SimplePosMiddleware.sol | 2 -- src/key-storage/BLSKeyStorage.sol | 8 ++++---- src/managers/AccessManager.sol | 6 ++---- src/managers/BaseManager.sol | 1 + src/middleware/extensions/Operators.sol | 2 -- src/middleware/extensions/OzAccessManaged.sol | 16 ++++++++++++++++ 6 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 src/middleware/extensions/OzAccessManaged.sol diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index d7757e2..14ea129 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -134,12 +134,10 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage { * @param amount The amount to slash. * @param stakeHints Hints for determining stakes. * @param slashHints Hints for the slashing process. - * @return An array of SlashResponse indicating the results of the slashing. */ function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) public onlyOwner - returns (SlashResponse[] memory slashResponses) { uint48 epochStart = getEpochStart(epoch); address operator = operatorByKey(abi.encode(key)); diff --git a/src/key-storage/BLSKeyStorage.sol b/src/key-storage/BLSKeyStorage.sol index 206d71a..9bc3302 100644 --- a/src/key-storage/BLSKeyStorage.sol +++ b/src/key-storage/BLSKeyStorage.sol @@ -22,7 +22,7 @@ abstract contract BlsKeyStorage is BaseManager { * @param key The BLS key for which to find the associated operator * @return The address of the operator linked to the specified BLS key */ - function operatorByBLSKey(bytes memory key) public view returns (address) { + function operatorByKey(bytes memory key) public view returns (address) { return _blsKeyData[key].getAddress(); } @@ -32,7 +32,7 @@ abstract contract BlsKeyStorage is BaseManager { * @param operator The address of the operator * @return The BLS key associated with the specified operator */ - function operatorBLSKey(address operator) public view returns (bytes memory) { + function operatorKey(address operator) public view returns (bytes memory) { if (blsKeyUpdateTimestamp[operator] == getCaptureTimestamp()) { return prevBLSKeys[operator]; } @@ -46,7 +46,7 @@ abstract contract BlsKeyStorage is BaseManager { * @param key The BLS key to check * @return A boolean indicating whether the BLS key was active at the specified timestamp */ - function blsKeyWasActiveAt(uint48 timestamp, bytes memory key) public view returns (bool) { + function keyWasActiveAt(uint48 timestamp, bytes memory key) public view returns (bool) { return _blsKeyData[key].wasActiveAt(timestamp); } @@ -56,7 +56,7 @@ abstract contract BlsKeyStorage is BaseManager { * @param operator The address of the operator whose BLS key is to be updated * @param key The new BLS key to associate with the operator */ - function _updateBLSKey(address operator, bytes memory key) internal { + function _updateKey(address operator, bytes memory key) internal { uint48 timestamp = getCaptureTimestamp(); if (keccak256(blsKeys[operator]) == keccak256(key)) { diff --git a/src/managers/AccessManager.sol b/src/managers/AccessManager.sol index e11dedd..dc92009 100644 --- a/src/managers/AccessManager.sol +++ b/src/managers/AccessManager.sol @@ -3,14 +3,12 @@ pragma solidity ^0.8.25; abstract contract AccessManager { modifier checkAccess() { - _checkAccess(msg.sender, msg.sig); + _checkAccess(); _; } /** * @notice Checks if the user has access to the given selector. - * @param caller The address to check access for. - * @param selector The selector to check access for. */ - function _checkAccess(address caller, bytes4 selector) internal view {} + function _checkAccess() internal virtual {} } diff --git a/src/managers/BaseManager.sol b/src/managers/BaseManager.sol index 84ae88d..cd4368c 100644 --- a/src/managers/BaseManager.sol +++ b/src/managers/BaseManager.sol @@ -51,6 +51,7 @@ abstract contract BaseManager is Initializable, OwnableUpgradeable { } function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256 power) { + vault; return stake; } } diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/Operators.sol index ebc1f91..f9e1bbc 100644 --- a/src/middleware/extensions/Operators.sol +++ b/src/middleware/extensions/Operators.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract Operators is BaseMiddleware { diff --git a/src/middleware/extensions/OzAccessManaged.sol b/src/middleware/extensions/OzAccessManaged.sol new file mode 100644 index 0000000..9d7fd9f --- /dev/null +++ b/src/middleware/extensions/OzAccessManaged.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; + +import {BaseMiddleware} from "../BaseMiddleware.sol"; + +abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { + constructor(address authority) { + __AccessManaged_init(authority); + } + + function _checkAccess() internal override { + _checkCanCall(msg.sender, msg.data); + } +} From 4ba578c2bfdbb816210f27d8eeb4b3c8df5ec5ae Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Tue, 12 Nov 2024 11:52:50 +0700 Subject: [PATCH 043/115] fix: selfregisteroperator --- src/middleware/extensions/SelfRegisterOperators.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/middleware/extensions/SelfRegisterOperators.sol b/src/middleware/extensions/SelfRegisterOperators.sol index f9e1bbc..af08933 100644 --- a/src/middleware/extensions/SelfRegisterOperators.sol +++ b/src/middleware/extensions/SelfRegisterOperators.sol @@ -4,24 +4,24 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract Operators is BaseMiddleware { - function registerOperator(address operator, bytes memory key, address vault) public checkAccess { + function registerOperator(address operator, bytes memory key, address vault) public { _beforeRegisterOperator(operator, key, vault); _registerOperator(operator); _updateKey(operator, key); _registerOperatorVault(operator, vault); } - function unregisterOperator(address operator) public checkAccess { + function unregisterOperator(address operator) public { _beforeUnregisterOperator(operator); _unregisterOperator(operator); } - function pauseOperator(address operator) public checkAccess { + function pauseOperator(address operator) public { _beforePauseOperator(operator); _pauseOperator(operator); } - function unpauseOperator(address operator) public checkAccess { + function unpauseOperator(address operator) public { _beforeUnpauseOperator(operator); _unpauseOperator(operator); } From 8799115fdd06879c1dc3a2b1d2a293b0f9283a84 Mon Sep 17 00:00:00 2001 From: gm256 Date: Tue, 12 Nov 2024 09:34:49 +0400 Subject: [PATCH 044/115] temp_commit --- src/middleware/extensions/Operators.sol | 6 +- test/DefaultSDK.t.sol | 85 ++++++++++++++++--------- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/Operators.sol index ebc1f91..5b6c7b8 100644 --- a/src/middleware/extensions/Operators.sol +++ b/src/middleware/extensions/Operators.sol @@ -5,12 +5,16 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {console} from "forge-std/console.sol"; + abstract contract Operators is BaseMiddleware { function registerOperator(address operator, bytes memory key, address vault) public checkAccess { _beforeRegisterOperator(operator, key, vault); _registerOperator(operator); _updateKey(operator, key); - _registerOperatorVault(operator, vault); + if (vault != address(0)) { + _registerOperatorVault(operator, vault); + } } function unregisterOperator(address operator) public checkAccess { diff --git a/test/DefaultSDK.t.sol b/test/DefaultSDK.t.sol index 72a3085..9522d0e 100644 --- a/test/DefaultSDK.t.sol +++ b/test/DefaultSDK.t.sol @@ -14,6 +14,8 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; +import {console} from "forge-std/console.sol"; + contract DefaultSDKTest is POCBaseTest { using Subnetwork for bytes32; using Subnetwork for address; @@ -23,6 +25,7 @@ contract DefaultSDKTest is POCBaseTest { ExtendedSimplePosMiddleware internal middleware; + uint48 internal epochDuration = 600; // 10 minutes uint48 internal slashingWindow = 1200; // 20 minutes @@ -46,6 +49,7 @@ contract DefaultSDKTest is POCBaseTest { slashingWindow ); + _registerNetwork(network, address(middleware)); } @@ -54,65 +58,88 @@ contract DefaultSDKTest is POCBaseTest { uint256 operatorsLength = middleware.operatorsLength(); assertEq(operatorsLength, 0, "Operators length should be 0"); + console.log("A"); + // can't register without registration vm.expectRevert(); - middleware.registerOperator(operator); + middleware.registerOperator(operator, "0x5", address(0)); + console.log("B"); _registerOperator(operator); - +console.log("C"); // can't register without opt-in vm.expectRevert(); - middleware.registerOperator(operator); - - _optInOperatorNetwork(operator, network); - middleware.registerOperator(operator); - + middleware.registerOperator(operator, "0x5", address(0)); +console.log("D"); + // Need to set operator as msg.sender since _optInOperatorNetwork uses vm.startPrank(user) + // and operator needs to call optIn themselves + vm.startPrank(operator); + operatorNetworkOptInService.optIn(network); + console.log("DD"); + vm.stopPrank(); + bytes memory key = hex"0000000000000000000000000000000000000000000000000000000000000005"; + middleware.registerOperator(operator, key, address(0)); + + (address op, uint48 s, uint48 f) = middleware.operatorWithTimesAt(0); +console.log("E"); operatorsLength = middleware.operatorsLength(); assertEq(operatorsLength, 1, "Operators length should be 1"); - +console.log("F"); // can't register twice vm.expectRevert(); - middleware.registerOperator(operator); - + middleware.registerOperator(operator, "", address(0)); +console.log("G"); // activates on next epoch address[] memory operators = middleware.activeOperators(); - assertEq(operators.length, 0, "Active operators length should be 0"); + assertEq(operators.length, 0, "1 Active operators length should be 0"); skipEpoch(); operators = middleware.activeOperators(); - assertEq(operators.length, 1, "Active operators length should be 1"); - + assertEq(operators.length, 1, "2 Active operators length should be 1"); +console.log("H"); // pause middleware.pauseOperator(operator); - +console.log("I"); // can't pause twice vm.expectRevert(); middleware.pauseOperator(operator); + console.log("J"); // pause applies on next epoch operators = middleware.activeOperators(); - assertEq(operators.length, 1, "Active operators length should be 1"); - + assertEq(operators.length, 1, "3 Active operators length should be 1"); +console.log("K"); // can't unpause right now, minumum one epoch before immutable period passed vm.expectRevert(); middleware.unpauseOperator(operator); - - skipEpoch(); +console.log("L"); + skipImmutablePeriod(); + skipImmutablePeriod(); operators = middleware.activeOperators(); - assertEq(operators.length, 0, "Active operators length should be 0"); + assertEq(operators.length, 0, "4 Active operators length should be 0"); + (op, s, f) = middleware.operatorWithTimesAt(0); + console.log(s, f, middleware.getCaptureTimestamp(), Time.timestamp()); + + +console.log("M"); // unpause middleware.unpauseOperator(operator); + (op, s, f) = middleware.operatorWithTimesAt(0); + console.log(s, f, middleware.getCaptureTimestamp(), Time.timestamp()); + + console.log(middleware.operatorWasActiveAt(middleware.getCaptureTimestamp(), operator)); +console.log("N"); // unpause applies on next epoch operators = middleware.activeOperators(); - assertEq(operators.length, 0, "Active operators length should be 0"); + assertEq(operators.length, 0, "5 Active operators length should be 0"); skipEpoch(); operators = middleware.activeOperators(); - assertEq(operators.length, 1, "Active operators length should be 1"); - + assertEq(operators.length, 1, "6 Active operators length should be 1"); +console.log("O"); // pause and unregister middleware.pauseOperator(operator); - +console.log("P"); // can't unregister before immutable period passed vm.expectRevert(); middleware.unregisterOperator(operator); @@ -121,12 +148,12 @@ contract DefaultSDKTest is POCBaseTest { middleware.unregisterOperator(operator); skipEpoch(); middleware.unregisterOperator(operator); - +console.log("Q"); operatorsLength = middleware.operatorsLength(); - assertEq(operatorsLength, 0, "Operators length should be 0"); + assertEq(operatorsLength, 0, "7 Operators length should be 0"); } - function testKeys() public { + /*function testKeys() public { bytes32 key = keccak256("key"); address operator = address(0x1337); @@ -190,7 +217,7 @@ contract DefaultSDKTest is POCBaseTest { skipEpoch(); operatorKey = middleware.operatorKey(operator); assertEq(operatorKey, newKey3, "Operator's key was not updated correctly"); - } + }*/ // function testBLSKeys() public { // bytes memory key = "key"; @@ -258,7 +285,7 @@ contract DefaultSDKTest is POCBaseTest { // assertEq(operatorKey, newKey3, "Operator's BLS key was not updated correctly"); // } - function testSubnetworks() public { + /*function testSubnetworks() public { skipEpoch(); // let first 0 subnetwork activate uint96 subnetwork = 1; @@ -573,7 +600,7 @@ contract DefaultSDKTest is POCBaseTest { skipImmutablePeriod(); vm.expectRevert(); middleware.slash(epoch, key1, amount, stakeHints, slashHints); - } + }*/ function skipEpoch() private { vm.warp(block.timestamp + epochDuration); From 358c1483636b5825e78bdb69b26527bf0494f4ca Mon Sep 17 00:00:00 2001 From: Algys Ievlev Date: Tue, 12 Nov 2024 12:37:34 +0700 Subject: [PATCH 045/115] chore: rename key storages --- .../SimplePosMiddleware.sol | 4 +- .../sqrt-task-network/SqrtTaskMiddleware.sol | 4 +- .../{KeyStorage.sol => KeyStorage256.sol} | 2 +- ...{BLSKeyStorage.sol => KeyStorageBytes.sol} | 44 +++++++++---------- 4 files changed, 27 insertions(+), 27 deletions(-) rename src/key-storage/{KeyStorage.sol => KeyStorage256.sol} (98%) rename src/key-storage/{BLSKeyStorage.sol => KeyStorageBytes.sol} (57%) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 14ea129..e5451bb 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -13,10 +13,10 @@ import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/Operators.sol"; -import {KeyStorage} from "../../key-storage/KeyStorage.sol"; +import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; -contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage { +contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index ff2fe61..1c751bc 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -14,9 +14,9 @@ import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/Operators.sol"; -import {KeyStorage} from "../../key-storage/KeyStorage.sol"; +import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; -contract SqrtTaskMiddleware is SharedVaults, Operators, KeyStorage, EIP712 { +contract SqrtTaskMiddleware is SharedVaults, Operators, KeyStorage256, EIP712 { using Subnetwork for address; using Math for uint256; diff --git a/src/key-storage/KeyStorage.sol b/src/key-storage/KeyStorage256.sol similarity index 98% rename from src/key-storage/KeyStorage.sol rename to src/key-storage/KeyStorage256.sol index bdd09da..5302fbc 100644 --- a/src/key-storage/KeyStorage.sol +++ b/src/key-storage/KeyStorage256.sol @@ -6,7 +6,7 @@ import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -abstract contract KeyStorage is BaseMiddleware { +abstract contract KeyStorage256 is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; error DuplicateKey(); diff --git a/src/key-storage/BLSKeyStorage.sol b/src/key-storage/KeyStorageBytes.sol similarity index 57% rename from src/key-storage/BLSKeyStorage.sol rename to src/key-storage/KeyStorageBytes.sol index 9bc3302..1d709ed 100644 --- a/src/key-storage/BLSKeyStorage.sol +++ b/src/key-storage/KeyStorageBytes.sol @@ -4,18 +4,18 @@ pragma solidity ^0.8.25; import {BaseManager} from "../managers/BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract BlsKeyStorage is BaseManager { +abstract contract KeyStorageBytes is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.Inner; - error DuplicateBLSKey(); - error BLSKeyAlreadyEnabled(); + error DuplicateKey(); + error KeyAlreadyEnabled(); bytes32 private constant ZERO_BYTES_HASH = keccak256(""); // Constant representing an empty hash - mapping(address => bytes) public blsKeys; // Mapping from operator addresses to their BLS keys - mapping(address => bytes) public prevBLSKeys; // Mapping from operator addresses to their previous BLS keys - mapping(address => uint48) public blsKeyUpdateTimestamp; // Mapping from operator addresses to the timestamp of the last BLS key update - mapping(bytes => PauseableEnumerableSet.Inner) internal _blsKeyData; // Mapping from BLS keys to their associated data + mapping(address => bytes) public keys; // Mapping from operator addresses to their BLS keys + mapping(address => bytes) public prevKeys; // Mapping from operator addresses to their previous keys + mapping(address => uint48) public keyUpdateTimestamp; // Mapping from operator addresses to the timestamp of the last key update + mapping(bytes => PauseableEnumerableSet.Inner) internal _keyData; // Mapping from keys to their associated data /** * @notice Returns the operator address associated with a given BLS key @@ -23,7 +23,7 @@ abstract contract BlsKeyStorage is BaseManager { * @return The address of the operator linked to the specified BLS key */ function operatorByKey(bytes memory key) public view returns (address) { - return _blsKeyData[key].getAddress(); + return _keyData[key].getAddress(); } /** @@ -33,11 +33,11 @@ abstract contract BlsKeyStorage is BaseManager { * @return The BLS key associated with the specified operator */ function operatorKey(address operator) public view returns (bytes memory) { - if (blsKeyUpdateTimestamp[operator] == getCaptureTimestamp()) { - return prevBLSKeys[operator]; + if (keyUpdateTimestamp[operator] == getCaptureTimestamp()) { + return prevKeys[operator]; } - return blsKeys[operator]; + return keys[operator]; } /** @@ -47,7 +47,7 @@ abstract contract BlsKeyStorage is BaseManager { * @return A boolean indicating whether the BLS key was active at the specified timestamp */ function keyWasActiveAt(uint48 timestamp, bytes memory key) public view returns (bool) { - return _blsKeyData[key].wasActiveAt(timestamp); + return _keyData[key].wasActiveAt(timestamp); } /** @@ -59,23 +59,23 @@ abstract contract BlsKeyStorage is BaseManager { function _updateKey(address operator, bytes memory key) internal { uint48 timestamp = getCaptureTimestamp(); - if (keccak256(blsKeys[operator]) == keccak256(key)) { - revert BLSKeyAlreadyEnabled(); + if (keccak256(keys[operator]) == keccak256(key)) { + revert KeyAlreadyEnabled(); } - if (_blsKeyData[key].getAddress() != address(0) && _blsKeyData[key].getAddress() != operator) { - revert DuplicateBLSKey(); + if (_keyData[key].getAddress() != address(0) && _keyData[key].getAddress() != operator) { + revert DuplicateKey(); } - if (keccak256(key) != ZERO_BYTES_HASH && _blsKeyData[key].getAddress() == address(0)) { - _blsKeyData[key].set(timestamp, operator); + if (keccak256(key) != ZERO_BYTES_HASH && _keyData[key].getAddress() == address(0)) { + _keyData[key].set(timestamp, operator); } - if (blsKeyUpdateTimestamp[operator] != timestamp) { - prevBLSKeys[operator] = blsKeys[operator]; - blsKeyUpdateTimestamp[operator] = timestamp; + if (keyUpdateTimestamp[operator] != timestamp) { + prevKeys[operator] = keys[operator]; + keyUpdateTimestamp[operator] = timestamp; } - blsKeys[operator] = key; + keys[operator] = key; } } From dbf47e9afc2c262294830f98d0d0190fd2d04d82 Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Tue, 12 Nov 2024 12:39:50 +0700 Subject: [PATCH 046/115] feat: add ed25519 sig --- src/key-storage/KeyStorage256.sol | 1 - src/libraries/Ed25519.sol | 1565 +++++++++++++++++++++++++++++ 2 files changed, 1565 insertions(+), 1 deletion(-) create mode 100644 src/libraries/Ed25519.sol diff --git a/src/key-storage/KeyStorage256.sol b/src/key-storage/KeyStorage256.sol index 5302fbc..3ab62eb 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/key-storage/KeyStorage256.sol @@ -24,7 +24,6 @@ abstract contract KeyStorage256 is BaseMiddleware { * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ - function operatorByKey(bytes memory key) public view override returns (address) { return keyToOperator[abi.decode(key, (bytes32))]; } diff --git a/src/libraries/Ed25519.sol b/src/libraries/Ed25519.sol new file mode 100644 index 0000000..399802d --- /dev/null +++ b/src/libraries/Ed25519.sol @@ -0,0 +1,1565 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// +// Original code by Near Foundation, 2021. +pragma solidity ^0.8; + +contract Ed25519 { + // Computes (v^(2^250-1), v^11) mod p + function pow22501(uint256 v) private pure returns (uint256 p22501, uint256 p11) { + p11 = mulmod(v, v, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod(p11, p11, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod( + mulmod(p22501, p22501, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), + v, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + p11 = mulmod(p22501, p11, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod( + mulmod(p11, p11, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), + p22501, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 a = mulmod(p22501, p22501, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(p22501, p22501, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 b = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(p22501, p22501, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + } + + function check(bytes32 k, bytes32 r, bytes32 s, bytes32 m1, bytes9 m2) public pure returns (bool) { + unchecked { + uint256 hh; + // Step 1: compute SHA-512(R, A, M) + { + uint256[5][16] memory kk = [ + [ + uint256(0x428a2f98_d728ae22), + uint256(0xe49b69c1_9ef14ad2), + uint256(0x27b70a85_46d22ffc), + uint256(0x19a4c116_b8d2d0c8), + uint256(0xca273ece_ea26619c) + ], + [ + uint256(0x71374491_23ef65cd), + uint256(0xefbe4786_384f25e3), + uint256(0x2e1b2138_5c26c926), + uint256(0x1e376c08_5141ab53), + uint256(0xd186b8c7_21c0c207) + ], + [ + uint256(0xb5c0fbcf_ec4d3b2f), + uint256(0xfc19dc6_8b8cd5b5), + uint256(0x4d2c6dfc_5ac42aed), + uint256(0x2748774c_df8eeb99), + uint256(0xeada7dd6_cde0eb1e) + ], + [ + uint256(0xe9b5dba5_8189dbbc), + uint256(0x240ca1cc_77ac9c65), + uint256(0x53380d13_9d95b3df), + uint256(0x34b0bcb5_e19b48a8), + uint256(0xf57d4f7f_ee6ed178) + ], + [ + uint256(0x3956c25b_f348b538), + uint256(0x2de92c6f_592b0275), + uint256(0x650a7354_8baf63de), + uint256(0x391c0cb3_c5c95a63), + uint256(0x6f067aa_72176fba) + ], + [ + uint256(0x59f111f1_b605d019), + uint256(0x4a7484aa_6ea6e483), + uint256(0x766a0abb_3c77b2a8), + uint256(0x4ed8aa4a_e3418acb), + uint256(0xa637dc5_a2c898a6) + ], + [ + uint256(0x923f82a4_af194f9b), + uint256(0x5cb0a9dc_bd41fbd4), + uint256(0x81c2c92e_47edaee6), + uint256(0x5b9cca4f_7763e373), + uint256(0x113f9804_bef90dae) + ], + [ + uint256(0xab1c5ed5_da6d8118), + uint256(0x76f988da_831153b5), + uint256(0x92722c85_1482353b), + uint256(0x682e6ff3_d6b2b8a3), + uint256(0x1b710b35_131c471b) + ], + [ + uint256(0xd807aa98_a3030242), + uint256(0x983e5152_ee66dfab), + uint256(0xa2bfe8a1_4cf10364), + uint256(0x748f82ee_5defb2fc), + uint256(0x28db77f5_23047d84) + ], + [ + uint256(0x12835b01_45706fbe), + uint256(0xa831c66d_2db43210), + uint256(0xa81a664b_bc423001), + uint256(0x78a5636f_43172f60), + uint256(0x32caab7b_40c72493) + ], + [ + uint256(0x243185be_4ee4b28c), + uint256(0xb00327c8_98fb213f), + uint256(0xc24b8b70_d0f89791), + uint256(0x84c87814_a1f0ab72), + uint256(0x3c9ebe0a_15c9bebc) + ], + [ + uint256(0x550c7dc3_d5ffb4e2), + uint256(0xbf597fc7_beef0ee4), + uint256(0xc76c51a3_0654be30), + uint256(0x8cc70208_1a6439ec), + uint256(0x431d67c4_9c100d4c) + ], + [ + uint256(0x72be5d74_f27b896f), + uint256(0xc6e00bf3_3da88fc2), + uint256(0xd192e819_d6ef5218), + uint256(0x90befffa_23631e28), + uint256(0x4cc5d4be_cb3e42b6) + ], + [ + uint256(0x80deb1fe_3b1696b1), + uint256(0xd5a79147_930aa725), + uint256(0xd6990624_5565a910), + uint256(0xa4506ceb_de82bde9), + uint256(0x597f299c_fc657e2a) + ], + [ + uint256(0x9bdc06a7_25c71235), + uint256(0x6ca6351_e003826f), + uint256(0xf40e3585_5771202a), + uint256(0xbef9a3f7_b2c67915), + uint256(0x5fcb6fab_3ad6faec) + ], + [ + uint256(0xc19bf174_cf692694), + uint256(0x14292967_0a0e6e70), + uint256(0x106aa070_32bbd1b8), + uint256(0xc67178f2_e372532b), + uint256(0x6c44198c_4a475817) + ] + ]; + uint256 w0 = (uint256(r) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000_ffffffff_ffffffff) + | ((uint256(r) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64) + | ((uint256(r) & 0xffffffff_ffffffff_00000000_00000000) << 64); + uint256 w1 = (uint256(k) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000_ffffffff_ffffffff) + | ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64) + | ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000) << 64); + uint256 w2 = (uint256(m1) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000_ffffffff_ffffffff) + | ((uint256(m1) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64) + | ((uint256(m1) & 0xffffffff_ffffffff_00000000_00000000) << 64); + uint256 w3 = ( + uint256(bytes32(m2)) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000_00000000_00000000 + ) | ((uint256(bytes32(m2)) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64) + | 0x800000_00000000_00000000_00000348; + uint256 a = 0x6a09e667_f3bcc908; + uint256 b = 0xbb67ae85_84caa73b; + uint256 c = 0x3c6ef372_fe94f82b; + uint256 d = 0xa54ff53a_5f1d36f1; + uint256 e = 0x510e527f_ade682d1; + uint256 f = 0x9b05688c_2b3e6c1f; + uint256 g = 0x1f83d9ab_fb41bd6b; + uint256 h = 0x5be0cd19_137e2179; + for (uint256 i = 0;; i++) { + // Round 16 * i + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[0][i]; + temp1 += w0 >> 192; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 1 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[1][i]; + temp1 += w0 >> 64; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 2 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[2][i]; + temp1 += w0 >> 128; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 3 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[3][i]; + temp1 += w0; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 4 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[4][i]; + temp1 += w1 >> 192; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 5 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[5][i]; + temp1 += w1 >> 64; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 6 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[6][i]; + temp1 += w1 >> 128; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 7 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[7][i]; + temp1 += w1; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 8 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[8][i]; + temp1 += w2 >> 192; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 9 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[9][i]; + temp1 += w2 >> 64; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 10 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[10][i]; + temp1 += w2 >> 128; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 11 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[11][i]; + temp1 += w2; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 12 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[12][i]; + temp1 += w3 >> 192; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 13 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[13][i]; + temp1 += w3 >> 64; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 14 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[14][i]; + temp1 += w3 >> 128; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + // Round 16 * i + 15 + { + uint256 temp1; + uint256 temp2; + e &= 0xffffffff_ffffffff; + { + uint256 ss = e | (e << 64); + uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); + uint256 ch = (e & (f ^ g)) ^ g; + temp1 = h + s1 + ch; + } + temp1 += kk[15][i]; + temp1 += w3; + a &= 0xffffffff_ffffffff; + { + uint256 ss = a | (a << 64); + uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); + uint256 maj = (a & (b | c)) | (b & c); + temp2 = s0 + maj; + } + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + if (i == 4) { + break; + } + // Message expansion + uint256 t0 = w0; + uint256 t1 = w1; + { + uint256 t2 = w2; + uint256 t3 = w3; + { + uint256 n1 = t0 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + n1 += ((t2 & 0xffffffff_ffffffff_00000000_00000000) << 128) + | ((t2 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64); + { + uint256 u1 = ((t0 & 0xffffffff_ffffffff_00000000_00000000) << 64) + | ((t0 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 128); + uint256 uu1 = u1 | (u1 << 64); + n1 += ((uu1 << 63) ^ (uu1 << 56) ^ (u1 << 57)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + } + { + uint256 v1 = t3 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + uint256 vv1 = v1 | (v1 << 64); + n1 += ((vv1 << 45) ^ (vv1 << 3) ^ (v1 << 58)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + } + n1 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + uint256 n2 = t0 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + n2 += ((t2 & 0xffffffff_ffffffff) << 128) | (t3 >> 192); + { + uint256 u2 = ((t0 & 0xffffffff_ffffffff) << 128) | (t1 >> 192); + uint256 uu2 = u2 | (u2 << 64); + n2 += ((uu2 >> 1) ^ (uu2 >> 8) ^ (u2 >> 7)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + } + { + uint256 vv2 = n1 | (n1 >> 64); + n2 += ((vv2 >> 19) ^ (vv2 >> 61) ^ (n1 >> 70)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + } + n2 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + t0 = n1 | n2; + } + { + uint256 n1 = t1 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + n1 += ((t3 & 0xffffffff_ffffffff_00000000_00000000) << 128) + | ((t3 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64); + { + uint256 u1 = ((t1 & 0xffffffff_ffffffff_00000000_00000000) << 64) + | ((t1 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 128); + uint256 uu1 = u1 | (u1 << 64); + n1 += ((uu1 << 63) ^ (uu1 << 56) ^ (u1 << 57)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + } + { + uint256 v1 = t0 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + uint256 vv1 = v1 | (v1 << 64); + n1 += ((vv1 << 45) ^ (vv1 << 3) ^ (v1 << 58)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + } + n1 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + uint256 n2 = t1 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + n2 += ((t3 & 0xffffffff_ffffffff) << 128) | (t0 >> 192); + { + uint256 u2 = ((t1 & 0xffffffff_ffffffff) << 128) | (t2 >> 192); + uint256 uu2 = u2 | (u2 << 64); + n2 += ((uu2 >> 1) ^ (uu2 >> 8) ^ (u2 >> 7)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + } + { + uint256 vv2 = n1 | (n1 >> 64); + n2 += ((vv2 >> 19) ^ (vv2 >> 61) ^ (n1 >> 70)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + } + n2 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + t1 = n1 | n2; + } + { + uint256 n1 = t2 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + n1 += ((t0 & 0xffffffff_ffffffff_00000000_00000000) << 128) + | ((t0 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64); + { + uint256 u1 = ((t2 & 0xffffffff_ffffffff_00000000_00000000) << 64) + | ((t2 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 128); + uint256 uu1 = u1 | (u1 << 64); + n1 += ((uu1 << 63) ^ (uu1 << 56) ^ (u1 << 57)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + } + { + uint256 v1 = t1 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + uint256 vv1 = v1 | (v1 << 64); + n1 += ((vv1 << 45) ^ (vv1 << 3) ^ (v1 << 58)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + } + n1 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + uint256 n2 = t2 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + n2 += ((t0 & 0xffffffff_ffffffff) << 128) | (t1 >> 192); + { + uint256 u2 = ((t2 & 0xffffffff_ffffffff) << 128) | (t3 >> 192); + uint256 uu2 = u2 | (u2 << 64); + n2 += ((uu2 >> 1) ^ (uu2 >> 8) ^ (u2 >> 7)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + } + { + uint256 vv2 = n1 | (n1 >> 64); + n2 += ((vv2 >> 19) ^ (vv2 >> 61) ^ (n1 >> 70)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + } + n2 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + t2 = n1 | n2; + } + { + uint256 n1 = t3 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + n1 += ((t1 & 0xffffffff_ffffffff_00000000_00000000) << 128) + | ((t1 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64); + { + uint256 u1 = ((t3 & 0xffffffff_ffffffff_00000000_00000000) << 64) + | ((t3 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 128); + uint256 uu1 = u1 | (u1 << 64); + n1 += ((uu1 << 63) ^ (uu1 << 56) ^ (u1 << 57)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + } + { + uint256 v1 = t2 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + uint256 vv1 = v1 | (v1 << 64); + n1 += ((vv1 << 45) ^ (vv1 << 3) ^ (v1 << 58)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + } + n1 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; + uint256 n2 = t3 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + n2 += ((t1 & 0xffffffff_ffffffff) << 128) | (t2 >> 192); + { + uint256 u2 = ((t3 & 0xffffffff_ffffffff) << 128) | (t0 >> 192); + uint256 uu2 = u2 | (u2 << 64); + n2 += ((uu2 >> 1) ^ (uu2 >> 8) ^ (u2 >> 7)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + } + { + uint256 vv2 = n1 | (n1 >> 64); + n2 += ((vv2 >> 19) ^ (vv2 >> 61) ^ (n1 >> 70)) + & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + } + n2 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; + t3 = n1 | n2; + } + w3 = t3; + w2 = t2; + } + w1 = t1; + w0 = t0; + } + uint256 h0 = ((a + 0x6a09e667_f3bcc908) & 0xffffffff_ffffffff) + | (((b + 0xbb67ae85_84caa73b) & 0xffffffff_ffffffff) << 64) + | (((c + 0x3c6ef372_fe94f82b) & 0xffffffff_ffffffff) << 128) | ((d + 0xa54ff53a_5f1d36f1) << 192); + h0 = ((h0 & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) + | ((h0 & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8); + h0 = ((h0 & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) + | ((h0 & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16); + h0 = ((h0 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) + | ((h0 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32); + uint256 h1 = ((e + 0x510e527f_ade682d1) & 0xffffffff_ffffffff) + | (((f + 0x9b05688c_2b3e6c1f) & 0xffffffff_ffffffff) << 64) + | (((g + 0x1f83d9ab_fb41bd6b) & 0xffffffff_ffffffff) << 128) | ((h + 0x5be0cd19_137e2179) << 192); + h1 = ((h1 & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) + | ((h1 & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8); + h1 = ((h1 & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) + | ((h1 & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16); + h1 = ((h1 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) + | ((h1 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32); + hh = addmod( + h0, + mulmod( + h1, + 0xfffffff_ffffffff_ffffffff_fffffffe_c6ef5bf4_737dcf70_d6ec3174_8d98951d, + 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed + ), + 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed + ); + } + // Step 2: unpack k + k = bytes32( + ((uint256(k) & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) + | ((uint256(k) & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8) + ); + k = bytes32( + ((uint256(k) & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) + | ((uint256(k) & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16) + ); + k = bytes32( + ((uint256(k) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) + | ((uint256(k) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32) + ); + k = bytes32( + ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) + | ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> 64) + ); + k = bytes32((uint256(k) << 128) | (uint256(k) >> 128)); + uint256 ky = uint256(k) & 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff; + uint256 kx; + { + uint256 ky2 = mulmod(ky, ky, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 u = addmod( + ky2, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffec, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 v = mulmod( + ky2, + 0x52036cee_2b6ffe73_8cc74079_7779e898_00700a4d_4141d8ab_75eb4dca_135978a3, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ) + 1; + uint256 t = mulmod(u, v, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + (kx,) = pow22501(t); + kx = mulmod(kx, kx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + kx = mulmod( + u, + mulmod( + mulmod(kx, kx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), + t, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + t = mulmod( + mulmod(kx, kx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), + v, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + if (t != u) { + if (t != 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - u) { + return false; + } + kx = mulmod( + kx, + 0x2b832480_4fc1df0b_2b4d0099_3dfbd7a7_2f431806_ad2fe478_c4ee1b27_4a0ea0b0, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + } + if ((kx & 1) != uint256(k) >> 255) { + kx = 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - kx; + } + // Verify s + s = bytes32( + ((uint256(s) & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) + | ((uint256(s) & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8) + ); + s = bytes32( + ((uint256(s) & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) + | ((uint256(s) & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16) + ); + s = bytes32( + ((uint256(s) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) + | ((uint256(s) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32) + ); + s = bytes32( + ((uint256(s) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) + | ((uint256(s) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> 64) + ); + s = bytes32((uint256(s) << 128) | (uint256(s) >> 128)); + if (uint256(s) >= 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed) { + return false; + } + uint256 vx; + uint256 vu; + uint256 vy; + uint256 vv; + // Step 3: compute multiples of k + uint256[8][3][2] memory tables; + { + uint256 ks = ky + kx; + uint256 kd = ky + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - kx; + uint256 k2dt = mulmod( + mulmod(kx, ky, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), + 0x2406d9dc_56dffce7_198e80f2_eef3d130_00e0149a_8283b156_ebd69b94_26b2f159, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 kky = ky; + uint256 kkx = kx; + uint256 kku = 1; + uint256 kkv = 1; + { + uint256 xx = + mulmod(kkx, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 yy = + mulmod(kky, kku, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 zz = + mulmod(kku, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 xx2 = + mulmod(xx, xx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 yy2 = + mulmod(yy, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 xxyy = + mulmod(xx, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 zz2 = + mulmod(zz, zz, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + kkx = xxyy + xxyy; + kku = yy2 - xx2 + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + kky = xx2 + yy2; + kkv = addmod( + zz2 + zz2, + 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + { + uint256 xx = + mulmod(kkx, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 yy = + mulmod(kky, kku, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 zz = + mulmod(kku, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 xx2 = + mulmod(xx, xx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 yy2 = + mulmod(yy, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 xxyy = + mulmod(xx, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 zz2 = + mulmod(zz, zz, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + kkx = xxyy + xxyy; + kku = yy2 - xx2 + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + kky = xx2 + yy2; + kkv = addmod( + zz2 + zz2, + 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + { + uint256 xx = + mulmod(kkx, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 yy = + mulmod(kky, kku, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 zz = + mulmod(kku, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 xx2 = + mulmod(xx, xx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 yy2 = + mulmod(yy, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 xxyy = + mulmod(xx, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 zz2 = + mulmod(zz, zz, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + kkx = xxyy + xxyy; + kku = yy2 - xx2 + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + kky = xx2 + yy2; + kkv = addmod( + zz2 + zz2, + 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 cprod = 1; + uint256[8][3][2] memory tables_ = tables; + for (uint256 i = 0;; i++) { + uint256 cs; + uint256 cd; + uint256 ct; + uint256 c2z; + { + uint256 cx = + mulmod(kkx, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 cy = + mulmod(kky, kku, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 cz = + mulmod(kku, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + ct = mulmod(kkx, kky, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + cs = cy + cx; + cd = cy - cx + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + c2z = cz + cz; + } + tables_[1][0][i] = cs; + tables_[1][1][i] = cd; + tables_[1][2][i] = mulmod( + ct, + 0x2406d9dc_56dffce7_198e80f2_eef3d130_00e0149a_8283b156_ebd69b94_26b2f159, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + tables_[0][0][i] = c2z; + tables_[0][1][i] = cprod; + cprod = + mulmod(cprod, c2z, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + if (i == 7) { + break; + } + uint256 ab = + mulmod(cs, ks, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 aa = + mulmod(cd, kd, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 ac = + mulmod(ct, k2dt, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + kkx = ab - aa + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + kku = addmod(c2z, ac, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + kky = ab + aa; + kkv = addmod( + c2z, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ac, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 t; + (cprod, t) = pow22501(cprod); + cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + cprod = mulmod(cprod, t, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + for (uint256 i = 7;; i--) { + uint256 cinv = mulmod( + cprod, + tables_[0][1][i], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + tables_[1][0][i] = mulmod( + tables_[1][0][i], + cinv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + tables_[1][1][i] = mulmod( + tables_[1][1][i], + cinv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + tables_[1][2][i] = mulmod( + tables_[1][2][i], + cinv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + if (i == 0) { + break; + } + cprod = mulmod( + cprod, + tables_[0][0][i], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + tables_[0] = [ + [ + 0x43e7ce9d_19ea5d32_9385a44c_321ea161_67c996e3_7dc6070c_97de49e3_7ac61db9, + 0x40cff344_25d8ec30_a3bb74ba_58cd5854_fa1e3818_6ad0d31e_bc8ae251_ceb2c97e, + 0x459bd270_46e8dd45_aea7008d_b87a5a8f_79067792_53d64523_58951859_9fdfbf4b, + 0x69fdd1e2_8c23cc38_94d0c8ff_90e76f6d_5b6e4c2e_620136d0_4dd83c4a_51581ab9, + 0x54dceb34_13ce5cfa_11196dfc_960b6eda_f4b380c6_d4d23784_19cc0279_ba49c5f3, + 0x4e24184d_d71a3d77_eef3729f_7f8cf7c1_7224cf40_aa7b9548_b9942f3c_5084ceed, + 0x5a0e5aab_20262674_ae117576_1cbf5e88_9b52a55f_d7ac5027_c228cebd_c8d2360a, + 0x26239334_073e9b38_c6285955_6d451c3d_cc8d30e8_4b361174_f488eadd_e2cf17d9 + ], + [ + 0x227e97c9_4c7c0933_d2e0c21a_3447c504_fe9ccf82_e8a05f59_ce881c82_eba0489f, + 0x226a3e0e_cc4afec6_fd0d2884_13014a9d_bddecf06_c1a2f0bb_702ba77c_613d8209, + 0x34d7efc8_51d45c5e_71efeb0f_235b7946_91de6228_877569b3_a8d52bf0_58b8a4a0, + 0x3c1f5fb3_ca7166fc_e1471c9b_752b6d28_c56301ad_7b65e845_1b2c8c55_26726e12, + 0x6102416c_f02f02ff_5be75275_f55f28db_89b2a9d2_456b860c_e22fc0e5_031f7cc5, + 0x40adf677_f1bfdae0_57f0fd17_9c126179_18ddaa28_91a6530f_b1a4294f_a8665490, + 0x61936f3c_41560904_6187b8ba_a978cbc9_b4789336_3ae5a3cc_7d909f36_35ae7f48, + 0x562a9662_b6ec47f9_e979d473_c02b51e4_42336823_8c58ddb5_2f0e5c6a_180e6410 + ], + [ + 0x3788bdb4_4f8632d4_2d0dbee5_eea1acc6_136cf411_e655624f_55e48902_c3bd5534, + 0x6190cf2c_2a7b5ad7_69d594a8_2844f23b_4167fa7c_8ac30e51_aa6cfbeb_dcd4b945, + 0x65f77870_96be9204_123a71f3_ac88a87b_e1513217_737d6a1e_2f3a13a4_3d7e3a9a, + 0x23af32d_bfa67975_536479a7_a7ce74a0_2142147f_ac048018_7f1f1334_9cda1f2d, + 0x64fc44b7_fc6841bd_db0ced8b_8b0fe675_9137ef87_ee966512_15fc1dbc_d25c64dc, + 0x1434aa37_48b701d5_b69df3d7_d340c1fe_3f6b9c1e_fc617484_caadb47e_382f4475, + 0x457a6da8_c962ef35_f2b21742_3e5844e9_d2353452_7e8ea429_0d24e3dd_f21720c6, + 0x63b9540c_eb60ccb5_1e4d989d_956e053c_f2511837_efb79089_d2ff4028_4202c53d + ] + ]; + } + // Step 4: compute s*G - h*A + { + uint256 ss = uint256(s) << 3; + uint256 hhh = hh + 0x80000000_00000000_00000000_00000000_a6f7cef5_17bce6b2_c09318d2_e7ae9f60; + uint256 vvx = 0; + uint256 vvu = 1; + uint256 vvy = 1; + uint256 vvv = 1; + for (uint256 i = 252;; i--) { + uint256 bit = 8 << i; + if ((ss & bit) != 0) { + uint256 ws; + uint256 wd; + uint256 wz; + uint256 wt; + { + uint256 wx = mulmod( + vvx, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 wy = mulmod( + vvy, vvu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + ws = wy + wx; + wd = wy - wx + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + wz = mulmod( + vvu, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + wt = mulmod( + vvx, vvy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 j = (ss >> i) & 7; + ss &= ~(7 << i); + uint256[8][3][2] memory tables_ = tables; + uint256 aa = mulmod( + wd, + tables_[0][1][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ab = mulmod( + ws, + tables_[0][0][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ac = mulmod( + wt, + tables_[0][2][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vvx = ab - aa + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvu = wz + ac; + vvy = ab + aa; + vvv = wz - ac + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + } + if ((hhh & bit) != 0) { + uint256 ws; + uint256 wd; + uint256 wz; + uint256 wt; + { + uint256 wx = mulmod( + vvx, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 wy = mulmod( + vvy, vvu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + ws = wy + wx; + wd = wy - wx + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + wz = mulmod( + vvu, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + wt = mulmod( + vvx, vvy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 j = (hhh >> i) & 7; + hhh &= ~(7 << i); + uint256[8][3][2] memory tables_ = tables; + uint256 aa = mulmod( + wd, + tables_[1][0][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ab = mulmod( + ws, + tables_[1][1][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ac = mulmod( + wt, + tables_[1][2][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vvx = ab - aa + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvu = wz - ac + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvy = ab + aa; + vvv = wz + ac; + } + if (i == 0) { + uint256 ws; + uint256 wd; + uint256 wz; + uint256 wt; + { + uint256 wx = mulmod( + vvx, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 wy = mulmod( + vvy, vvu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + ws = wy + wx; + wd = wy - wx + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + wz = mulmod( + vvu, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + wt = mulmod( + vvx, vvy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 j = hhh & 7; + uint256[8][3][2] memory tables_ = tables; + uint256 aa = mulmod( + wd, + tables_[1][0][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ab = mulmod( + ws, + tables_[1][1][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ac = mulmod( + wt, + tables_[1][2][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vvx = ab - aa + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvu = wz - ac + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvy = ab + aa; + vvv = wz + ac; + break; + } + { + uint256 xx = + mulmod(vvx, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 yy = + mulmod(vvy, vvu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 zz = + mulmod(vvu, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 xx2 = + mulmod(xx, xx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 yy2 = + mulmod(yy, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 xxyy = + mulmod(xx, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + uint256 zz2 = + mulmod(zz, zz, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + vvx = xxyy + xxyy; + vvu = yy2 - xx2 + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvy = xx2 + yy2; + vvv = addmod( + zz2 + zz2, + 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - vvu, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + } + vx = vvx; + vu = vvu; + vy = vvy; + vv = vvv; + } + // Step 5: compare the points + (uint256 vi, uint256 vj) = + pow22501(mulmod(vu, vv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed)); + vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + vi = mulmod(vi, vj, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + vx = mulmod( + vx, + mulmod(vi, vv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vy = mulmod( + vy, + mulmod(vi, vu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + bytes32 vr = bytes32(vy | (vx << 255)); + vr = bytes32( + ((uint256(vr) & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) + | ((uint256(vr) & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8) + ); + vr = bytes32( + ((uint256(vr) & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) + | ((uint256(vr) & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16) + ); + vr = bytes32( + ((uint256(vr) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) + | ((uint256(vr) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32) + ); + vr = bytes32( + ((uint256(vr) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) + | ((uint256(vr) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> 64) + ); + vr = bytes32((uint256(vr) << 128) | (uint256(vr) >> 128)); + return vr == r; + } + } +} From 135af7f7033277b12328f397358f8dabb0e6fed3 Mon Sep 17 00:00:00 2001 From: gm256 Date: Tue, 12 Nov 2024 12:38:17 +0400 Subject: [PATCH 047/115] added tests --- .gitmodules | 6 +- src/libraries/PauseableEnumerableSet.sol | 4 +- src/managers/OperatorManager.sol | 9 + src/middleware/extensions/Operators.sol | 10 +- test/DefaultSDK.t.sol | 612 ----------------------- test/OperatorsRegistration.t.sol | 405 +++++++++++++++ 6 files changed, 428 insertions(+), 618 deletions(-) delete mode 100644 test/DefaultSDK.t.sol create mode 100644 test/OperatorsRegistration.t.sol diff --git a/.gitmodules b/.gitmodules index 07c3c2d..2c84a91 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,9 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts -[submodule "lib/core"] - path = lib/core - url = https://github.com/symbioticfi/core [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/core"] + path = lib/core + url = https://github.com/symbioticfi/core diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 286ca2f..9f5adc9 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -711,6 +711,7 @@ library PauseableEnumerableSet { function set(Inner256 storage self, uint48 timestamp, uint256 value) internal { self.value = value; self.enabledTimestamp = timestamp; + self.disabledTimestamp = 0; } /* @@ -722,6 +723,7 @@ library PauseableEnumerableSet { function set(Inner256 storage self, uint48 timestamp, bytes32 key) internal { self.value = uint256(key); self.enabledTimestamp = timestamp; + self.disabledTimestamp = 0; } /* @@ -747,7 +749,7 @@ library PauseableEnumerableSet { if (self.disabledTimestamp != 0) { revert NotEnabled(); } - + self.enabledTimestamp = 0; self.disabledTimestamp = timestamp; } diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index 66c8344..04b51f7 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -65,6 +65,15 @@ abstract contract OperatorManager is BaseManager { return _operators.wasActiveAt(timestamp, operator); } + /* + * @notice Checks if an operator is registered. + * @param operator The address of the operator to check. + * @return A boolean indicating whether the operator is registered. + */ + function isOperatorRegistered(address operator) public view returns (bool) { + return _operators.contains(operator); + } + /* * @notice Registers a new operator. * @param operator The address of the operator to register. diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/Operators.sol index 50e28ad..f92fdcb 100644 --- a/src/middleware/extensions/Operators.sol +++ b/src/middleware/extensions/Operators.sol @@ -3,15 +3,20 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../BaseMiddleware.sol"; -import {console} from "forge-std/console.sol"; abstract contract Operators is BaseMiddleware { + function registerOperatorVault(address operator, address vault) public checkAccess { + require(isOperatorRegistered(operator), "Operator not registered"); + _beforeRegisterOperatorVault(operator, vault); + _registerOperatorVault(operator, vault); + } + function registerOperator(address operator, bytes memory key, address vault) public checkAccess { _beforeRegisterOperator(operator, key, vault); _registerOperator(operator); _updateKey(operator, key); if (vault != address(0)) { - _registerOperatorVault(operator, vault); + registerOperatorVault(operator, vault); } } @@ -31,6 +36,7 @@ abstract contract Operators is BaseMiddleware { } function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} + function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} function _beforeUnregisterOperator(address operator) internal virtual {} function _beforePauseOperator(address operator) internal virtual {} function _beforeUnpauseOperator(address operator) internal virtual {} diff --git a/test/DefaultSDK.t.sol b/test/DefaultSDK.t.sol deleted file mode 100644 index 9522d0e..0000000 --- a/test/DefaultSDK.t.sol +++ /dev/null @@ -1,612 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; - -import {SimplePosMiddleware} from "../src/examples/simple-pos-network/SimplePosMiddleware.sol"; -import {ExtendedSimplePosMiddleware} from "./mocks/ExtendedSimplePosMiddleware.sol"; -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; -import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; - -import {console} from "forge-std/console.sol"; - -contract DefaultSDKTest is POCBaseTest { - using Subnetwork for bytes32; - using Subnetwork for address; - using Math for uint256; - - address network = address(0x123); - - ExtendedSimplePosMiddleware internal middleware; - - - uint48 internal epochDuration = 600; // 10 minutes - uint48 internal slashingWindow = 1200; // 20 minutes - - function setUp() public override { - vm.warp(1729690309); - - super.setUp(); - - _deposit(vault1, alice, 1000 ether); - _deposit(vault2, alice, 1000 ether); - _deposit(vault3, alice, 1000 ether); - - // Initialize middleware contract - middleware = new ExtendedSimplePosMiddleware( - address(network), - address(operatorRegistry), - address(vaultFactory), - address(operatorNetworkOptInService), - owner, - epochDuration, - slashingWindow - ); - - - _registerNetwork(network, address(middleware)); - } - - function testOperators() public { - address operator = address(0x1337); - uint256 operatorsLength = middleware.operatorsLength(); - assertEq(operatorsLength, 0, "Operators length should be 0"); - - console.log("A"); - - // can't register without registration - vm.expectRevert(); - middleware.registerOperator(operator, "0x5", address(0)); - console.log("B"); - - _registerOperator(operator); -console.log("C"); - // can't register without opt-in - vm.expectRevert(); - middleware.registerOperator(operator, "0x5", address(0)); -console.log("D"); - // Need to set operator as msg.sender since _optInOperatorNetwork uses vm.startPrank(user) - // and operator needs to call optIn themselves - vm.startPrank(operator); - operatorNetworkOptInService.optIn(network); - console.log("DD"); - vm.stopPrank(); - bytes memory key = hex"0000000000000000000000000000000000000000000000000000000000000005"; - middleware.registerOperator(operator, key, address(0)); - - (address op, uint48 s, uint48 f) = middleware.operatorWithTimesAt(0); -console.log("E"); - operatorsLength = middleware.operatorsLength(); - assertEq(operatorsLength, 1, "Operators length should be 1"); -console.log("F"); - // can't register twice - vm.expectRevert(); - middleware.registerOperator(operator, "", address(0)); -console.log("G"); - // activates on next epoch - address[] memory operators = middleware.activeOperators(); - assertEq(operators.length, 0, "1 Active operators length should be 0"); - skipEpoch(); - operators = middleware.activeOperators(); - assertEq(operators.length, 1, "2 Active operators length should be 1"); -console.log("H"); - // pause - middleware.pauseOperator(operator); -console.log("I"); - // can't pause twice - vm.expectRevert(); - middleware.pauseOperator(operator); - console.log("J"); - - // pause applies on next epoch - operators = middleware.activeOperators(); - assertEq(operators.length, 1, "3 Active operators length should be 1"); -console.log("K"); - // can't unpause right now, minumum one epoch before immutable period passed - vm.expectRevert(); - middleware.unpauseOperator(operator); -console.log("L"); - skipImmutablePeriod(); - skipImmutablePeriod(); - operators = middleware.activeOperators(); - assertEq(operators.length, 0, "4 Active operators length should be 0"); - - (op, s, f) = middleware.operatorWithTimesAt(0); - console.log(s, f, middleware.getCaptureTimestamp(), Time.timestamp()); - - -console.log("M"); - // unpause - middleware.unpauseOperator(operator); - (op, s, f) = middleware.operatorWithTimesAt(0); - console.log(s, f, middleware.getCaptureTimestamp(), Time.timestamp()); - - console.log(middleware.operatorWasActiveAt(middleware.getCaptureTimestamp(), operator)); - -console.log("N"); - // unpause applies on next epoch - operators = middleware.activeOperators(); - assertEq(operators.length, 0, "5 Active operators length should be 0"); - skipEpoch(); - operators = middleware.activeOperators(); - assertEq(operators.length, 1, "6 Active operators length should be 1"); -console.log("O"); - // pause and unregister - middleware.pauseOperator(operator); -console.log("P"); - // can't unregister before immutable period passed - vm.expectRevert(); - middleware.unregisterOperator(operator); - skipEpoch(); - vm.expectRevert(); - middleware.unregisterOperator(operator); - skipEpoch(); - middleware.unregisterOperator(operator); -console.log("Q"); - operatorsLength = middleware.operatorsLength(); - assertEq(operatorsLength, 0, "7 Operators length should be 0"); - } - - /*function testKeys() public { - bytes32 key = keccak256("key"); - address operator = address(0x1337); - - bytes32 operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, bytes32(0), "Operator's key should be empty"); - address keyOperator = middleware.operatorByKey(key); - assertEq(keyOperator, address(0), "Key's operator should be empty"); - - middleware.updateKey(operator, key); - keyOperator = middleware.operatorByKey(key); - assertEq(keyOperator, operator, "Key's operator was not updated correctly"); - - // applies on next epoch - operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, bytes32(0), "Operator's key should be empty"); - - skipEpoch(); - operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, key, "Operator's key was not updated correctly"); - - // update key - bytes32 newKey = keccak256("newKey"); - middleware.updateKey(operator, newKey); - - // can't update already active key twice - vm.expectRevert(); - middleware.updateKey(operator, newKey); - - keyOperator = middleware.operatorByKey(key); - assertEq(keyOperator, operator, "Key's operator was not updated correctly"); - - // applies on next epoch - operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, key, "Operator's key should be previous key"); - - skipEpoch(); - operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, newKey, "Operator's key was not updated correctly"); - - bytes32 zeroKey = bytes32(0); - middleware.updateKey(operator, zeroKey); - operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, newKey, "Operator's key should be previous key"); - - skipEpoch(); - operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, zeroKey, "Operator's key was not updated correctly"); - - // can't set used key to another operator - vm.expectRevert(); - middleware.updateKey(address(0x123123), key); - - // should apply update to latest updated key - bytes32 newKey2 = keccak256("newKey2"); - bytes32 newKey3 = keccak256("newKey3"); - middleware.updateKey(operator, newKey2); - middleware.updateKey(operator, newKey3); - operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, zeroKey, "Operator's key should be previous key"); - - skipEpoch(); - operatorKey = middleware.operatorKey(operator); - assertEq(operatorKey, newKey3, "Operator's key was not updated correctly"); - }*/ - - // function testBLSKeys() public { - // bytes memory key = "key"; - // address operator = address(0x1337); - - // bytes memory operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, "", "Operator's BLS key should be empty"); - // address keyOperator = middleware.operatorByBLSKey(key); - // assertEq(keyOperator, address(0), "BLS key's operator should be empty"); - - // middleware.updateBLSKey(operator, key); - // keyOperator = middleware.operatorByBLSKey(key); - // assertEq(keyOperator, operator, "BLS key's operator was not updated correctly"); - - // // applies on next epoch - // operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, "", "Operator's BLS key should be empty"); - - // skipEpoch(); - // operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, key, "Operator's BLS key was not updated correctly"); - - // // update key - // bytes memory newKey = "newKey"; - // middleware.updateBLSKey(operator, newKey); - - // // can't update already active bls key twice - // vm.expectRevert(); - // middleware.updateBLSKey(operator, newKey); - - // keyOperator = middleware.operatorByBLSKey(key); - // assertEq(keyOperator, operator, "BLS key's operator was not updated correctly"); - - // // applies on next epoch - // operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, key, "Operator's BLS key should be previous key"); - - // skipEpoch(); - // operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, newKey, "Operator's BLS key was not updated correctly"); - - // bytes memory zeroKey = ""; - // middleware.updateBLSKey(operator, zeroKey); - // operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, newKey, "Operator's BLS key should be previous key"); - - // skipEpoch(); - // operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, zeroKey, "Operator's BLS key was not updated correctly"); - - // // can't set used bls key to another operator - // vm.expectRevert(); - // middleware.updateBLSKey(address(0x123123), key); - - // // should apply update to latest updated bls key - // bytes memory newKey2 = "newKey2"; - // bytes memory newKey3 = "newKey3"; - // middleware.updateBLSKey(operator, newKey2); - // middleware.updateBLSKey(operator, newKey3); - // operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, zeroKey, "Operator's BLS key should be previous key"); - - // skipEpoch(); - // operatorKey = middleware.operatorBLSKey(operator); - // assertEq(operatorKey, newKey3, "Operator's BLS key was not updated correctly"); - // } - - /*function testSubnetworks() public { - skipEpoch(); // let first 0 subnetwork activate - - uint96 subnetwork = 1; - uint256 subnetworksLength = middleware.subnetworksLength(); - assertEq(subnetworksLength, 1, "Subnetworks length should be 1"); - - // register - middleware.registerSubnetwork(subnetwork); - - subnetworksLength = middleware.subnetworksLength(); - assertEq(subnetworksLength, 2, "Subnetworks length should be 2"); - - // can't register twice - vm.expectRevert(); - middleware.registerSubnetwork(subnetwork); - - // activates on next epoch - uint160[] memory subnetworks = middleware.activeSubnetworks(); - assertEq(subnetworks.length, 1, "Active subnetworks length should be 1"); - skipEpoch(); - subnetworks = middleware.activeSubnetworks(); - assertEq(subnetworks.length, 2, "Active subnetworks length should be 2"); - - // pause - middleware.pauseSubnetwork(subnetwork); - - // can't pause twice - vm.expectRevert(); - middleware.pauseSubnetwork(subnetwork); - - // pause applies on next epoch - subnetworks = middleware.activeSubnetworks(); - assertEq(subnetworks.length, 2, "Active subnetworks length should be 2"); - - // can't unpause right now, minumum one epoch before immutable period passed - vm.expectRevert(); - middleware.unpauseSubnetwork(subnetwork); - - skipEpoch(); - subnetworks = middleware.activeSubnetworks(); - assertEq(subnetworks.length, 1, "Active subnetworks length should be 1"); - - // unpause - middleware.unpauseSubnetwork(subnetwork); - - // unpause applies on next epoch - subnetworks = middleware.activeSubnetworks(); - assertEq(subnetworks.length, 1, "Active subnetworks length should be 1"); - skipEpoch(); - subnetworks = middleware.activeSubnetworks(); - assertEq(subnetworks.length, 2, "Active subnetworks length should be 2"); - - // pause and unregister - middleware.pauseSubnetwork(subnetwork); - - // can't unregister before immutable period passed - vm.expectRevert(); - middleware.unregisterSubnetwork(subnetwork); - skipEpoch(); - vm.expectRevert(); - middleware.unregisterSubnetwork(subnetwork); - skipEpoch(); - middleware.unregisterSubnetwork(subnetwork); - - subnetworksLength = middleware.subnetworksLength(); - assertEq(subnetworksLength, 1, "Subnetworks length should be 1"); - } - - function testVaults() public { - address operator = address(0x1337); - address operator2 = address(0x1338); - address vault = address(vault1); - - // should register only vault - vm.expectRevert(); - middleware.registerSharedVault(operator); - - // register shared vault - middleware.registerSharedVault(vault); - uint256 sharedVaultLength = middleware.sharedVaultsLength(); - assertEq(sharedVaultLength, 1, "Shared vaults length should be 1"); - - // active vaults should be zero - address[] memory activeVaults = middleware.activeVaults(operator); - assertEq(activeVaults.length, 0, "Active vaults length should be 0"); - - // shared vaults are active for each operator - skipEpoch(); - activeVaults = middleware.activeVaults(operator); - assertEq(activeVaults.length, 1, "Active vaults length should be 1"); - - // can't register twice - vm.expectRevert(); - middleware.registerSharedVault(vault); - - // can't register as operator vault if registered as shared - vm.expectRevert(); - middleware.registerOperatorVault(operator, vault); - - middleware.registerOperatorVault(operator, address(vault2)); - - // activates only for registered operator - skipEpoch(); - activeVaults = middleware.activeVaults(operator); - assertEq(activeVaults.length, 2, "Active vaults length should be 2"); - activeVaults = middleware.activeVaults(operator2); - assertEq(activeVaults.length, 1, "Active vaults length should be 1"); - - // can't register as shared if resgistred as operator's - vm.expectRevert(); - middleware.registerSharedVault(address(vault2)); - - // can register to another operator if registered as operator's - middleware.registerOperatorVault(address(0x1339), address(vault2)); - - // pause, unpause and unregister same as operators, subnetworks so don't test - middleware.pauseSharedVault(vault); - skipImmutablePeriod(); - middleware.unregisterSharedVault(vault); - - // can register to operator after unregister - middleware.registerOperatorVault(operator, vault); - } - - function testValidatorSet() public { - address operator1 = address(0x1337); - address operator2 = address(0x1338); - - _registerOperator(operator1); - _registerOperator(operator2); - - _optInOperatorNetwork(operator1, network); - _optInOperatorNetwork(operator2, network); - - _optInOperatorVault(vault1, operator1); - _optInOperatorVault(vault1, operator2); - _optInOperatorVault(vault2, operator1); - _optInOperatorVault(vault2, operator2); - - _setMaxNetworkLimit(address(delegator1), network, 0, 1000 ether); - _setMaxNetworkLimit(address(delegator2), network, 0, 1000 ether); - - _setNetworkLimitNetwork(delegator1, alice, network, 1000 ether); - _setNetworkLimitFull(delegator2, alice, network, 1000 ether); - - _setOperatorNetworkShares(delegator1, alice, network, operator1, 500 ether); - _setOperatorNetworkShares(delegator1, alice, network, operator2, 500 ether); - - _setOperatorNetworkLimit(delegator2, alice, network, operator1, 500 ether); - _setOperatorNetworkLimit(delegator2, alice, network, operator2, 500 ether); - - middleware.registerOperator(operator1); - middleware.registerOperator(operator2); - middleware.registerSharedVault(address(vault1)); - middleware.registerSharedVault(address(vault2)); - - bytes32 key1 = keccak256("key1"); - bytes32 key2 = keccak256("key2"); - - middleware.updateKey(operator1, key1); - middleware.updateKey(operator2, key2); - - SimplePosMiddleware.ValidatorData[] memory validatorSet = middleware.getValidatorSet(); - assertEq(validatorSet.length, 0, "valset length should be 0"); - - skipEpoch(); - - // updates applies on next epoch - validatorSet = middleware.getValidatorSet(); - assertEq(validatorSet.length, 2, "valset length should be 2"); - for (uint256 i = 0; i < validatorSet.length; i++) { - SimplePosMiddleware.ValidatorData memory validator = validatorSet[i]; - if (validator.key == key1) { - assertEq(validator.power, 1000 ether, "validator1 power should be 1000"); - } else if (validator.key == key2) { - assertEq(validator.power, 1000 ether, "validator2 power should be 1000"); - } else { - assertEq(true, false, "unexpected validator key"); - } - } - - middleware.pauseOperator(operator1); - - // not excluded immediately - validatorSet = middleware.getValidatorSet(); - assertEq(validatorSet.length, 2, "valset length should be 1"); - skipEpoch(); - validatorSet = middleware.getValidatorSet(); - assertEq(validatorSet.length, 1, "valset length should be 1"); - assertEq(validatorSet[0].key, key2, "validator key should be key2"); - assertEq(validatorSet[0].power, 1000 ether, "validator2 power should be 1000"); - - middleware.unpauseOperator(operator1); - skipEpoch(); - validatorSet = middleware.getValidatorSet(); - assertEq(validatorSet.length, 2, "valset length should be 1"); - - // stake decrease if vault paused - middleware.pauseSharedVault(address(vault1)); - skipEpoch(); - validatorSet = middleware.getValidatorSet(); - assertEq(validatorSet.length, 2, "valset length should be 2"); - for (uint256 i = 0; i < validatorSet.length; i++) { - SimplePosMiddleware.ValidatorData memory validator = validatorSet[i]; - if (validator.key == key1) { - assertEq(validator.power, 500 ether, "validator1 power should be 1000"); - } else if (validator.key == key2) { - assertEq(validator.power, 500 ether, "validator2 power should be 1000"); - } else { - assertEq(true, false, "unexpected validator key"); - } - } - - // change on next epoch - _setOperatorNetworkLimit(delegator2, alice, network, operator2, 1000 ether); - validatorSet = middleware.getValidatorSet(); - assertEq(validatorSet.length, 2, "valset length should be 2"); - for (uint256 i = 0; i < validatorSet.length; i++) { - SimplePosMiddleware.ValidatorData memory validator = validatorSet[i]; - if (validator.key == key1) { - assertEq(validator.power, 500 ether, "validator1 power should be 1000"); - } else if (validator.key == key2) { - assertEq(validator.power, 500 ether, "validator2 power should be 1000"); - } else { - assertEq(true, false, "unexpected validator key"); - } - } - skipEpoch(); - validatorSet = middleware.getValidatorSet(); - assertEq(validatorSet.length, 2, "valset length should be 2"); - for (uint256 i = 0; i < validatorSet.length; i++) { - SimplePosMiddleware.ValidatorData memory validator = validatorSet[i]; - if (validator.key == key1) { - assertEq(validator.power, 500 ether, "validator1 power should be 1000"); - } else if (validator.key == key2) { - assertEq(validator.power, 1000 ether, "validator2 power should be 1000"); - } else { - assertEq(true, false, "unexpected validator key"); - } - } - } - - function testSlash() public { - address operator1 = address(0x1337); - address operator2 = address(0x1338); - - _registerOperator(operator1); - _registerOperator(operator2); - - _optInOperatorNetwork(operator1, network); - _optInOperatorNetwork(operator2, network); - - _optInOperatorVault(vault3, operator1); - _optInOperatorVault(vault3, operator2); - _optInOperatorVault(vault2, operator1); - _optInOperatorVault(vault2, operator2); - - _setMaxNetworkLimit(address(delegator3), network, 0, 1000 ether); - _setMaxNetworkLimit(address(delegator2), network, 0, 1000 ether); - - _setNetworkLimitNetwork(delegator3, alice, network, 1000 ether); - _setNetworkLimitFull(delegator2, alice, network, 1000 ether); - - _setOperatorNetworkShares(delegator3, alice, network, operator1, 500 ether); - _setOperatorNetworkShares(delegator3, alice, network, operator2, 500 ether); - - _setOperatorNetworkLimit(delegator2, alice, network, operator1, 500 ether); - _setOperatorNetworkLimit(delegator2, alice, network, operator2, 500 ether); - - middleware.registerOperator(operator1); - middleware.registerOperator(operator2); - middleware.registerSharedVault(address(vault3)); - middleware.registerSharedVault(address(vault2)); - - bytes32 key1 = keccak256("key1"); - bytes32 key2 = keccak256("key2"); - - middleware.updateKey(operator1, key1); - middleware.updateKey(operator2, key2); - - skipEpoch(); - - // Prepare hints - uint256 vaultsLen = middleware.activeVaults(operator1).length; - bytes[][] memory stakeHints = new bytes[][](vaultsLen); - for (uint256 i; i < vaultsLen; i++) { - stakeHints[i] = new bytes[](middleware.activeSubnetworks().length); - for (uint256 j; j < stakeHints[i].length; j++) { - stakeHints[i][j] = ""; - } - } - - bytes[] memory slashHints = new bytes[](stakeHints.length); - slashHints[0] = ""; - - uint48 epoch = middleware.getCurrentEpoch(); - uint256 amount = 100 ether; - - // Perform a slash on operator1 - vm.prank(owner); - middleware.slash(epoch, key1, amount, stakeHints, slashHints); - - vm.warp(block.timestamp + 1 days); - middleware.executeSlash(address(vault3), 0, ""); - - skipEpoch(); - uint256 totalStake = middleware.getTotalStake(); - - assertEq(totalStake, 1950 ether, "Total stake not updated correctly"); - - // can't slash after immutable period - skipImmutablePeriod(); - vm.expectRevert(); - middleware.slash(epoch, key1, amount, stakeHints, slashHints); - }*/ - - function skipEpoch() private { - vm.warp(block.timestamp + epochDuration); - } - - function skipImmutablePeriod() private { - vm.warp(block.timestamp + slashingWindow); - } -} diff --git a/test/OperatorsRegistration.t.sol b/test/OperatorsRegistration.t.sol new file mode 100644 index 0000000..8845848 --- /dev/null +++ b/test/OperatorsRegistration.t.sol @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; + +import {SimplePosMiddleware} from "../src/examples/simple-pos-network/SimplePosMiddleware.sol"; +import {ExtendedSimplePosMiddleware} from "./mocks/ExtendedSimplePosMiddleware.sol"; +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; +import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; + +contract OperatorsRegistrationTest is POCBaseTest { + using Subnetwork for bytes32; + using Subnetwork for address; + using Math for uint256; + + address network = address(0x123); + + ExtendedSimplePosMiddleware internal middleware; + + + uint48 internal epochDuration = 600; // 10 minutes + uint48 internal slashingWindow = 1200; // 20 minutes + + function setUp() public override { + vm.warp(1729690309); + + super.setUp(); + + _deposit(vault1, alice, 1000 ether); + _deposit(vault2, alice, 1000 ether); + _deposit(vault3, alice, 1000 ether); + + // Initialize middleware contract + middleware = new ExtendedSimplePosMiddleware( + address(network), + address(operatorRegistry), + address(vaultFactory), + address(operatorNetworkOptInService), + owner, + epochDuration, + slashingWindow + ); + + + _registerNetwork(network, address(middleware)); + } + + function testOperators() public { + address operator = address(0x1337); + bytes memory key = hex"0000000000000000000000000000000000000000000000000000000000000005"; + uint256 operatorsLength = middleware.operatorsLength(); + assertEq(operatorsLength, 0, "Operators length should be 0"); + + // can't register without registration + vm.expectRevert(); + middleware.registerOperator(operator, key, address(0)); + + _registerOperator(operator); + + // can't register without opt-in + vm.expectRevert(); + middleware.registerOperator(operator, key, address(0)); + + // Need to set operator as msg.sender since _optInOperatorNetwork uses vm.startPrank(user) + // and operator needs to call optIn themselves + vm.startPrank(operator); + operatorNetworkOptInService.optIn(network); + vm.stopPrank(); + + middleware.registerOperator(operator, key, address(0)); + + (address op, uint48 s, uint48 f) = middleware.operatorWithTimesAt(0); + + operatorsLength = middleware.operatorsLength(); + assertEq(operatorsLength, 1, "Operators length should be 1"); + + // can't register twice + vm.expectRevert(); + middleware.registerOperator(operator, key, address(0)); + + // activates on next epoch + address[] memory operators = middleware.activeOperators(); + assertEq(operators.length, 0, "1 Active operators length should be 0"); + skipEpoch(); + operators = middleware.activeOperators(); + assertEq(operators.length, 1, "2 Active operators length should be 1"); + + // pause + middleware.pauseOperator(operator); + + // can't pause twice + vm.expectRevert(); + middleware.pauseOperator(operator); + + // pause applies on next epoch + operators = middleware.activeOperators(); + assertEq(operators.length, 1, "3 Active operators length should be 1"); + + // can't unpause right now, minumum one epoch before immutable period passed + vm.expectRevert(); + middleware.unpauseOperator(operator); + + skipImmutablePeriod(); + skipImmutablePeriod(); + operators = middleware.activeOperators(); + assertEq(operators.length, 0, "4 Active operators length should be 0"); + + (op, s, f) = middleware.operatorWithTimesAt(0); + + // unpause + middleware.unpauseOperator(operator); + (op, s, f) = middleware.operatorWithTimesAt(0); + + // unpause applies on next epoch + operators = middleware.activeOperators(); + assertEq(operators.length, 0, "5 Active operators length should be 0"); + skipEpoch(); + operators = middleware.activeOperators(); + assertEq(operators.length, 1, "6 Active operators length should be 1"); + + // pause and unregister + middleware.pauseOperator(operator); + + // can't unregister before immutable period passed + vm.expectRevert(); + middleware.unregisterOperator(operator); + skipEpoch(); + vm.expectRevert(); + middleware.unregisterOperator(operator); + skipEpoch(); + middleware.unregisterOperator(operator); + + operatorsLength = middleware.operatorsLength(); + assertEq(operatorsLength, 0, "7 Operators length should be 0"); + } + + function testMultipleOperatorsWithKeys() public { + // Set up multiple operators with different keys + address[] memory operators = new address[](3); + operators[0] = address(0x1337); + operators[1] = address(0x1338); + operators[2] = address(0x1339); + + bytes[] memory keys = new bytes[](3); + keys[0] = hex"0000000000000000000000000000000000000000000000000000000000000001"; + keys[1] = hex"0000000000000000000000000000000000000000000000000000000000000002"; + keys[2] = hex"0000000000000000000000000000000000000000000000000000000000000003"; + + // Register and opt-in all operators + for(uint i = 0; i < operators.length; i++) { + _registerOperator(operators[i]); + vm.startPrank(operators[i]); + operatorNetworkOptInService.optIn(network); + vm.stopPrank(); + middleware.registerOperator(operators[i], keys[i], address(0)); + } + + // Skip epoch to activate all operators + skipEpoch(); + + // Verify all operators are active + address[] memory activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 3, "Should have 3 active operators"); + + // Test complex pause/unpause sequence + // Pause operator 0 + middleware.pauseOperator(operators[0]); + skipEpoch(); + activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 2, "Should have 2 active operators after pause"); + + // Pause operator 1 + middleware.pauseOperator(operators[1]); + skipEpoch(); + activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 1, "Should have 1 active operator after second pause"); + + // Wait for immutable period and try to unpause operator 0 + skipImmutablePeriod(); + middleware.unpauseOperator(operators[0]); + skipEpoch(); + activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 2, "Should have 2 active operators after unpause"); + + // Test operator was active at specific timestamps + uint48 currentTimestamp = middleware.getCaptureTimestamp(); + assertTrue(middleware.operatorWasActiveAt(currentTimestamp, operators[0]), "Operator 0 should be active"); + assertFalse(middleware.operatorWasActiveAt(currentTimestamp, operators[1]), "Operator 1 should be inactive"); + assertTrue(middleware.operatorWasActiveAt(currentTimestamp, operators[2]), "Operator 2 should be active"); + + // Test unregistration with active and inactive operators + vm.expectRevert(); + middleware.unregisterOperator(operators[2]); // Should fail - operator is active + + skipImmutablePeriod(); + middleware.unregisterOperator(operators[1]); // Should succeed - operator is paused + + // Verify final state + assertEq(middleware.operatorsLength(), 2, "Should have 2 operators remaining"); + activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 2, "Should still have 2 active operators"); + } + + function testReregisterOperator() public { + // Set up initial operator + address operator = address(0x1111); + bytes memory key = hex"0000000000000000000000000000000000000000000000000000000000001111"; + + // Register operator first time + _registerOperator(operator); + vm.startPrank(operator); + operatorNetworkOptInService.optIn(network); + vm.stopPrank(); + middleware.registerOperator(operator, key, address(0)); + + // Skip epoch to activate operator + skipEpoch(); + assertEq(middleware.activeOperators().length, 1, "Should have 1 active operator"); + + // Pause operator + middleware.pauseOperator(operator); + skipEpoch(); + assertEq(middleware.activeOperators().length, 0, "Should have 0 active operators after pause"); + + // Wait for immutable period and unregister + skipImmutablePeriod(); + middleware.unregisterOperator(operator); + assertEq(middleware.operatorsLength(), 0, "Should have 0 operators after unregister"); + + // Register same operator again + bytes memory keyNew = hex"0000000000000000000000000000000000000000000000000000000000001112"; + middleware.registerOperator(operator, keyNew, address(0)); + + // Skip epoch to activate operator + skipEpoch(); + assertEq(middleware.activeOperators().length, 1, "Should have 1 active operator after reregistration"); + assertTrue(middleware.operatorWasActiveAt(middleware.getCaptureTimestamp(), operator), "Operator should be active after reregistration"); + } + + + function testCornerCaseTimings() public { + // Set up initial operator + address operator = address(0x1111); + bytes memory key = hex"0000000000000000000000000000000000000000000000000000000000001111"; + + _registerOperator(operator); + vm.startPrank(operator); + operatorNetworkOptInService.optIn(network); + vm.stopPrank(); + + // Register operator just before epoch boundary + middleware.registerOperator(operator, key, address(0)); + vm.warp(middleware.getEpochStart(1) - 1); + assertEq(middleware.activeOperators().length, 0, "Should have no active operators before epoch"); + + // Check right at epoch boundary + vm.warp(middleware.getEpochStart(1)); + assertEq(middleware.activeOperators().length, 1, "Should have 1 active operator at epoch start"); + + // Test pause timing edge cases + middleware.pauseOperator(operator); + uint48 pauseTime = uint48(block.timestamp); + + // Try unpause just before slashing window ends + vm.warp(pauseTime + slashingWindow - 1); + vm.expectRevert(); + middleware.unpauseOperator(operator); + + // Should work exactly at slashing window + vm.warp(pauseTime + slashingWindow); + middleware.unpauseOperator(operator); + + // Test capture timestamp interactions + uint48 currentEpochStart = middleware.getEpochStart(middleware.getCurrentEpoch() + 1); + + // Pause right before capture timestamp + vm.warp(currentEpochStart - 1); + middleware.pauseOperator(operator); + assertTrue( + middleware.operatorWasActiveAt(currentEpochStart - 1, operator), + "Operator should be active before pause" + ); + + // Check status at capture timestamp + vm.warp(currentEpochStart); + assertFalse( + middleware.operatorWasActiveAt(currentEpochStart, operator), + "Operator should be inactive at capture" + ); + + // Test unregister timing + vm.warp(currentEpochStart + slashingWindow - 2); + + // Attempt unregister before slashing window + vm.expectRevert(); + middleware.unregisterOperator(operator); + + // Should succeed exactly at slashing window + vm.warp(currentEpochStart + slashingWindow); + middleware.unregisterOperator(operator); + + // Try to register again immediately - should fail + vm.expectRevert(); + middleware.registerOperator(operator, key, address(0)); + + // Test registration in next epoch + vm.warp(currentEpochStart + 2 * slashingWindow + 1); + + // Should work in next epoch + bytes memory keyNew = hex"0000000000000000000000000000000000000000000000000000000000001112"; + middleware.registerOperator(operator, keyNew, address(0)); + } + + function testComplexOperatorTimings() public { + // Set up two operators + address operator1 = address(0x1111); + address operator2 = address(0x2222); + bytes memory key1 = hex"0000000000000000000000000000000000000000000000000000000000001111"; + bytes memory key2 = hex"0000000000000000000000000000000000000000000000000000000000002222"; + + // Register and setup both operators + address[] memory operators = new address[](2); + operators[0] = operator1; + operators[1] = operator2; + + for (uint256 i = 0; i < operators.length; i++) { + _registerOperator(operators[i]); + vm.startPrank(operators[i]); + operatorNetworkOptInService.optIn(network); + vm.stopPrank(); + } + + // Register operators at different times + middleware.registerOperator(operator1, key1, address(0)); + skipEpoch(); // Skip one epoch + middleware.registerOperator(operator2, key2, address(0)); + + // At this point, operator1 should be active, operator2 pending + address[] memory activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 1, "Should have 1 active operator"); + assertEq(activeOps[0], operator1, "Active operator should be operator1"); + + // Skip epoch to activate operator2 + skipEpoch(); + activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 2, "Should have 2 active operators"); + + // Pause operator1, wait partial immutable period, pause operator2 + middleware.pauseOperator(operator1); + skipEpoch(); + vm.warp(block.timestamp + slashingWindow / 2); // Advance time by half the slashing window + middleware.pauseOperator(operator2); + + // Skip to when operator1 can be unpaused but operator2 cannot + vm.warp(block.timestamp + slashingWindow / 2); // Complete immutable period for operator1 + middleware.unpauseOperator(operator1); // Should work + vm.expectRevert(); + middleware.unpauseOperator(operator2); // Should fail + + // Skip to when operator2 can be unpaused + skipImmutablePeriod(); + middleware.unpauseOperator(operator2); + + // Skip epoch to activate both operators + skipEpoch(); + activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 2, "Should have 2 active operators again"); + + // Test historical activity + uint48 timestamp = middleware.getCaptureTimestamp(); + assertTrue(middleware.operatorWasActiveAt(timestamp, operator1)); + assertTrue(middleware.operatorWasActiveAt(timestamp, operator2)); + + // Pause both operators again but unregister at different times + middleware.pauseOperator(operator1); + middleware.pauseOperator(operator2); + skipImmutablePeriod(); + + middleware.unregisterOperator(operator1); + vm.expectRevert(); // Can't register same operator again + middleware.registerOperator(operator1, key1, address(0)); + + skipEpoch(); + middleware.unregisterOperator(operator2); + + // Verify final state + assertEq(middleware.operatorsLength(), 0, "Should have no operators"); + activeOps = middleware.activeOperators(); + assertEq(activeOps.length, 0, "Should have no active operators"); + } + + function skipEpoch() private { + vm.warp(block.timestamp + epochDuration); + } + + function skipImmutablePeriod() private { + vm.warp(block.timestamp + slashingWindow); + } +} From cf130392c4d65b25a9aa5686b096d846e3aa38e1 Mon Sep 17 00:00:00 2001 From: gm256 Date: Tue, 12 Nov 2024 15:00:20 +0400 Subject: [PATCH 048/115] fmt --- foundry.toml | 16 ++++++ lib/core | 2 +- .../SimplePosMiddleware.sol | 17 +++--- src/key-storage/KeyStorage256.sol | 1 - src/libraries/PauseableEnumerableSet.sol | 16 +++--- src/managers/VaultManager.sol | 31 ++++++----- src/middleware/extensions/Operators.sol | 1 - test/OperatorsRegistration.t.sol | 53 +++++++++---------- 8 files changed, 75 insertions(+), 62 deletions(-) diff --git a/foundry.toml b/foundry.toml index 529f204..f5c94d6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,4 +4,20 @@ src = "src" out = "out" libs = ["lib"] via_ir = true +fs_permissions = [{ access = "read", path = "./"}] +gas_reports = ["*"] + +[fmt] +bracket_spacing = false +int_types = "long" +line_length = 120 +multiline_func_header = "params_first" +number_underscore = "thousands" +quote_style = "double" +tab_width = 4 + +[fuzz] +runs = 4096 +max_test_rejects = 262144 + # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/core b/lib/core index ae741fa..2a5f6f0 160000 --- a/lib/core +++ b/lib/core @@ -1 +1 @@ -Subproject commit ae741fa21daa77c5f78876ff426a8e6450a19fe0 +Subproject commit 2a5f6f0fcee9a8d0ace03c38c77a352c5e5f95ae diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index e5451bb..39133ad 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -15,7 +15,6 @@ import {Operators} from "../../middleware/extensions/Operators.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; - contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { using Subnetwork for address; @@ -135,10 +134,13 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { * @param stakeHints Hints for determining stakes. * @param slashHints Hints for the slashing process. */ - function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) - public - onlyOwner - { + function slash( + uint48 epoch, + bytes32 key, + uint256 amount, + bytes[][] memory stakeHints, + bytes[] memory slashHints + ) public onlyOwner { uint48 epochStart = getEpochStart(epoch); address operator = operatorByKey(abi.encode(key)); @@ -162,10 +164,7 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { for (uint256 j; j < subnetworks.length; ++j) { bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( - subnetwork, - operator, - epochStart, - stakeHints[i][j] + subnetwork, operator, epochStart, stakeHints[i][j] ); uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); diff --git a/src/key-storage/KeyStorage256.sol b/src/key-storage/KeyStorage256.sol index 5302fbc..3ab62eb 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/key-storage/KeyStorage256.sol @@ -24,7 +24,6 @@ abstract contract KeyStorage256 is BaseMiddleware { * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ - function operatorByKey(bytes memory key) public view override returns (address) { return keyToOperator[abi.decode(key, (bytes32))]; } diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 9f5adc9..3946510 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -358,8 +358,7 @@ library PauseableEnumerableSet { * @return True if the value was active at the timestamp, false otherwise. */ function wasActiveAt(Inner storage self, uint48 timestamp) internal view returns (bool) { - return - self.enabledTimestamp < timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); + return self.enabledTimestamp < timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); } /* @@ -760,8 +759,7 @@ library PauseableEnumerableSet { * @return True if the value was active at the timestamp, false otherwise. */ function wasActiveAt(Inner256 storage self, uint48 timestamp) internal view returns (bool) { - return - self.enabledTimestamp < timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); + return self.enabledTimestamp < timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); } /* @@ -799,11 +797,11 @@ library PauseableEnumerableSet { * @param immutablePeriod The immutable period that must pass before unregistering. * @return True if the value can be unregistered, false otherwise. */ - function checkUnregister(Inner256 storage self, uint48 timestamp, uint48 immutablePeriod) - internal - view - returns (bool) - { + function checkUnregister( + Inner256 storage self, + uint48 timestamp, + uint48 immutablePeriod + ) internal view returns (bool) { if (self.enabledTimestamp != 0 || self.disabledTimestamp == 0) { return false; } diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 4f782d9..d988a44 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -304,11 +304,12 @@ abstract contract VaultManager is BaseManager { * @param timestamp The timestamp to check stake at * @return The stake amount */ - function getOperatorStakeAt(address operator, address vault, uint96 subnetwork, uint48 timestamp) - public - view - returns (uint256) - { + function getOperatorStakeAt( + address operator, + address vault, + uint96 subnetwork, + uint48 timestamp + ) public view returns (uint256) { bytes32 subnetworkId = NETWORK.subnetwork(subnetwork); return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); } @@ -333,11 +334,12 @@ abstract contract VaultManager is BaseManager { * @param timestamp The timestamp to check power at * @return The power amount */ - function getOperatorPowerAt(address operator, address vault, uint96 subnetwork, uint48 timestamp) - public - view - returns (uint256) - { + function getOperatorPowerAt( + address operator, + address vault, + uint96 subnetwork, + uint48 timestamp + ) public view returns (uint256) { uint256 stake = getOperatorStakeAt(operator, vault, subnetwork, timestamp); return stakeToPower(vault, stake); } @@ -606,10 +608,11 @@ abstract contract VaultManager is BaseManager { * @param hints Additional data for the veto slasher * @return slashedAmount The amount that was slashed */ - function _executeSlash(address vault, uint256 slashIndex, bytes calldata hints) - internal - returns (uint256 slashedAmount) - { + function _executeSlash( + address vault, + uint256 slashIndex, + bytes calldata hints + ) internal returns (uint256 slashedAmount) { address slasher = IVault(vault).slasher(); uint64 slasherType = IEntity(slasher).TYPE(); if (slasherType != VETO_SLASHER_TYPE) { diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/Operators.sol index f92fdcb..7ba0d76 100644 --- a/src/middleware/extensions/Operators.sol +++ b/src/middleware/extensions/Operators.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../BaseMiddleware.sol"; - abstract contract Operators is BaseMiddleware { function registerOperatorVault(address operator, address vault) public checkAccess { require(isOperatorRegistered(operator), "Operator not registered"); diff --git a/test/OperatorsRegistration.t.sol b/test/OperatorsRegistration.t.sol index 8845848..a3de8b0 100644 --- a/test/OperatorsRegistration.t.sol +++ b/test/OperatorsRegistration.t.sol @@ -5,30 +5,30 @@ import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; import {SimplePosMiddleware} from "../src/examples/simple-pos-network/SimplePosMiddleware.sol"; import {ExtendedSimplePosMiddleware} from "./mocks/ExtendedSimplePosMiddleware.sol"; -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +//import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +//import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +// import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; -import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; +//import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; +//import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; contract OperatorsRegistrationTest is POCBaseTest { - using Subnetwork for bytes32; - using Subnetwork for address; + // using Subnetwork for bytes32; + // using Subnetwork for address; using Math for uint256; address network = address(0x123); ExtendedSimplePosMiddleware internal middleware; - uint48 internal epochDuration = 600; // 10 minutes uint48 internal slashingWindow = 1200; // 20 minutes function setUp() public override { - vm.warp(1729690309); + SYMBIOTIC_CORE_PROJECT_ROOT = "lib/core/"; + vm.warp(1_729_690_309); super.setUp(); @@ -47,7 +47,6 @@ contract OperatorsRegistrationTest is POCBaseTest { slashingWindow ); - _registerNetwork(network, address(middleware)); } @@ -72,7 +71,7 @@ contract OperatorsRegistrationTest is POCBaseTest { vm.startPrank(operator); operatorNetworkOptInService.optIn(network); vm.stopPrank(); - + middleware.registerOperator(operator, key, address(0)); (address op, uint48 s, uint48 f) = middleware.operatorWithTimesAt(0); @@ -153,7 +152,7 @@ contract OperatorsRegistrationTest is POCBaseTest { keys[2] = hex"0000000000000000000000000000000000000000000000000000000000000003"; // Register and opt-in all operators - for(uint i = 0; i < operators.length; i++) { + for (uint256 i = 0; i < operators.length; i++) { _registerOperator(operators[i]); vm.startPrank(operators[i]); operatorNetworkOptInService.optIn(network); @@ -163,7 +162,7 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate all operators skipEpoch(); - + // Verify all operators are active address[] memory activeOps = middleware.activeOperators(); assertEq(activeOps.length, 3, "Should have 3 active operators"); @@ -240,10 +239,12 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate operator skipEpoch(); assertEq(middleware.activeOperators().length, 1, "Should have 1 active operator after reregistration"); - assertTrue(middleware.operatorWasActiveAt(middleware.getCaptureTimestamp(), operator), "Operator should be active after reregistration"); + assertTrue( + middleware.operatorWasActiveAt(middleware.getCaptureTimestamp(), operator), + "Operator should be active after reregistration" + ); } - function testCornerCaseTimings() public { // Set up initial operator address operator = address(0x1111); @@ -258,7 +259,7 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.registerOperator(operator, key, address(0)); vm.warp(middleware.getEpochStart(1) - 1); assertEq(middleware.activeOperators().length, 0, "Should have no active operators before epoch"); - + // Check right at epoch boundary vm.warp(middleware.getEpochStart(1)); assertEq(middleware.activeOperators().length, 1, "Should have 1 active operator at epoch start"); @@ -266,7 +267,7 @@ contract OperatorsRegistrationTest is POCBaseTest { // Test pause timing edge cases middleware.pauseOperator(operator); uint48 pauseTime = uint48(block.timestamp); - + // Try unpause just before slashing window ends vm.warp(pauseTime + slashingWindow - 1); vm.expectRevert(); @@ -278,25 +279,23 @@ contract OperatorsRegistrationTest is POCBaseTest { // Test capture timestamp interactions uint48 currentEpochStart = middleware.getEpochStart(middleware.getCurrentEpoch() + 1); - + // Pause right before capture timestamp vm.warp(currentEpochStart - 1); middleware.pauseOperator(operator); assertTrue( - middleware.operatorWasActiveAt(currentEpochStart - 1, operator), - "Operator should be active before pause" + middleware.operatorWasActiveAt(currentEpochStart - 1, operator), "Operator should be active before pause" ); - + // Check status at capture timestamp vm.warp(currentEpochStart); assertFalse( - middleware.operatorWasActiveAt(currentEpochStart, operator), - "Operator should be inactive at capture" + middleware.operatorWasActiveAt(currentEpochStart, operator), "Operator should be inactive at capture" ); // Test unregister timing vm.warp(currentEpochStart + slashingWindow - 2); - + // Attempt unregister before slashing window vm.expectRevert(); middleware.unregisterOperator(operator); @@ -311,7 +310,7 @@ contract OperatorsRegistrationTest is POCBaseTest { // Test registration in next epoch vm.warp(currentEpochStart + 2 * slashingWindow + 1); - + // Should work in next epoch bytes memory keyNew = hex"0000000000000000000000000000000000000000000000000000000000001112"; middleware.registerOperator(operator, keyNew, address(0)); @@ -328,7 +327,7 @@ contract OperatorsRegistrationTest is POCBaseTest { address[] memory operators = new address[](2); operators[0] = operator1; operators[1] = operator2; - + for (uint256 i = 0; i < operators.length; i++) { _registerOperator(operators[i]); vm.startPrank(operators[i]); @@ -381,7 +380,7 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.pauseOperator(operator1); middleware.pauseOperator(operator2); skipImmutablePeriod(); - + middleware.unregisterOperator(operator1); vm.expectRevert(); // Can't register same operator again middleware.registerOperator(operator1, key1, address(0)); From 6f792d3b7f1b8293e1d8045cc6972d6ac71d94dd Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Tue, 12 Nov 2024 18:14:39 +0700 Subject: [PATCH 049/115] feat: key sigs --- .../SelfRegisterEd25519Middleware.sol | 37 ++++++++++++++ .../SelfRegisterMiddleware.sol | 36 ++++++++++++++ src/libraries/Ed25519.sol | 2 +- .../extensions/SelfRegisterOperators.sol | 48 +++++++++++++------ src/middleware/extensions/sigs/BaseSig.sol | 12 +++++ src/middleware/extensions/sigs/ECDSASig.sol | 16 +++++++ src/middleware/extensions/sigs/Ed25519Sig.sol | 19 ++++++++ 7 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 src/examples/self-register-network/SelfRegisterEd25519Middleware.sol create mode 100644 src/examples/self-register-network/SelfRegisterMiddleware.sol create mode 100644 src/middleware/extensions/sigs/BaseSig.sol create mode 100644 src/middleware/extensions/sigs/ECDSASig.sol create mode 100644 src/middleware/extensions/sigs/Ed25519Sig.sol diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol new file mode 100644 index 0000000..49cc504 --- /dev/null +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; +import {SelfRegisterOperators} from "../../middleware/extensions/SelfRegisterOperators.sol"; + +import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; +import {Ed25519Sig} from "../../middleware/extensions/sigs/Ed25519Sig.sol"; + +contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, KeyStorage256, Ed25519Sig { + /* + * @notice Constructor for initializing the SelfRegisterEd25519Middleware contract. + * @param network The address of the network. + * @param operatorRegistry The address of the operator registry. + * @param vaultRegistry The address of the vault registry. + * @param operatorNetOptin The address of the operator network opt-in service. + * @param owner The address of the contract owner. + * @param slashingWindow The duration of the slashing window + */ + constructor( + address network, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + address owner, + uint48 slashingWindow + ) BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) {} +} diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol new file mode 100644 index 0000000..641b088 --- /dev/null +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; +import {SelfRegisterOperators} from "../../middleware/extensions/SelfRegisterOperators.sol"; +import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; +import {ECDSASig} from "../../middleware/extensions/sigs/ECDSASig.sol"; + +contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyStorage256, ECDSASig { + /* + * @notice Constructor for initializing the SelfRegisterMiddleware contract. + * @param network The address of the network. + * @param operatorRegistry The address of the operator registry. + * @param vaultRegistry The address of the vault registry. + * @param operatorNetOptin The address of the operator network opt-in service. + * @param owner The address of the contract owner. + * @param slashingWindow The duration of the slashing window + */ + constructor( + address network, + address operatorRegistry, + address vaultRegistry, + address operatorNetOptin, + address owner, + uint48 slashingWindow + ) BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) {} +} diff --git a/src/libraries/Ed25519.sol b/src/libraries/Ed25519.sol index 399802d..580b419 100644 --- a/src/libraries/Ed25519.sol +++ b/src/libraries/Ed25519.sol @@ -3,7 +3,7 @@ // Original code by Near Foundation, 2021. pragma solidity ^0.8; -contract Ed25519 { +library Ed25519 { // Computes (v^(2^250-1), v^11) mod p function pow22501(uint256 v) private pure returns (uint256 p22501, uint256 p11) { p11 = mulmod(v, v, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); diff --git a/src/middleware/extensions/SelfRegisterOperators.sol b/src/middleware/extensions/SelfRegisterOperators.sol index af08933..9946526 100644 --- a/src/middleware/extensions/SelfRegisterOperators.sol +++ b/src/middleware/extensions/SelfRegisterOperators.sol @@ -2,30 +2,48 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseSig} from "./sigs/BaseSig.sol"; -abstract contract Operators is BaseMiddleware { - function registerOperator(address operator, bytes memory key, address vault) public { - _beforeRegisterOperator(operator, key, vault); - _registerOperator(operator); - _updateKey(operator, key); - _registerOperatorVault(operator, vault); +abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig { + error InvalidSignature(); + + function registerOperator(bytes memory key, address vault, bytes memory signature) public { + _beforeRegisterOperator(msg.sender, key, vault); + _verifyKey(key, signature); + _registerOperator(msg.sender); + _updateKey(msg.sender, key); + _registerOperatorVault(msg.sender, vault); + } + + function unregisterOperator() public { + _beforeUnregisterOperator(msg.sender); + _unregisterOperator(msg.sender); } - function unregisterOperator(address operator) public { - _beforeUnregisterOperator(operator); - _unregisterOperator(operator); + function pauseOperator() public { + _beforePauseOperator(msg.sender); + _pauseOperator(msg.sender); } - function pauseOperator(address operator) public { - _beforePauseOperator(operator); - _pauseOperator(operator); + function unpauseOperator() public { + _beforeUnpauseOperator(msg.sender); + _unpauseOperator(msg.sender); } - function unpauseOperator(address operator) public { - _beforeUnpauseOperator(operator); - _unpauseOperator(operator); + function updateOperatorKey(bytes memory key, bytes memory signature) public { + _beforeUpdateOperatorKey(msg.sender, key); + _verifyKey(key, signature); + _updateKey(msg.sender, key); } + function _verifyKey(bytes memory key, bytes memory signature) internal view { + if (!_verifyKeySignature(key, signature)) { + revert InvalidSignature(); + } + } + + function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} + function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} function _beforeUnregisterOperator(address operator) internal virtual {} function _beforePauseOperator(address operator) internal virtual {} diff --git a/src/middleware/extensions/sigs/BaseSig.sol b/src/middleware/extensions/sigs/BaseSig.sol new file mode 100644 index 0000000..71024d8 --- /dev/null +++ b/src/middleware/extensions/sigs/BaseSig.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +abstract contract BaseSig { + /** + * @notice Verifies the signature of a key + * @param key_ The key to verify + * @param signature The signature to verify + * @return True if the signature is valid, false otherwise + */ + function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view virtual returns (bool); +} diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol new file mode 100644 index 0000000..d8b6d82 --- /dev/null +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {BaseSig} from "./BaseSig.sol"; + +abstract contract ECDSASig is BaseSig { + using ECDSA for bytes32; + + function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view override returns (bool) { + bytes32 key = abi.decode(key_, (bytes32)); + bytes32 hash = keccak256(abi.encodePacked(msg.sender, key)); + address signer = hash.recover(signature); + return signer == msg.sender; + } +} diff --git a/src/middleware/extensions/sigs/Ed25519Sig.sol b/src/middleware/extensions/sigs/Ed25519Sig.sol new file mode 100644 index 0000000..d4a834e --- /dev/null +++ b/src/middleware/extensions/sigs/Ed25519Sig.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Ed25519} from "../../../libraries/Ed25519.sol"; +import {BaseSig} from "./BaseSig.sol"; + +abstract contract Ed25519Sig is BaseSig { + function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view override returns (bool) { + bytes32 key = abi.decode(key_, (bytes32)); + bytes32 r; + bytes32 s; + assembly { + r := mload(add(signature, 32)) + s := mload(add(signature, 64)) + } + bytes32 message = keccak256(abi.encodePacked(msg.sender, key)); + return Ed25519.check(key, r, s, message, bytes9(0)); + } +} From 41bc52ac2afee1450a53d4c8f606d20afe196cfa Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Thu, 14 Nov 2024 11:30:01 +0700 Subject: [PATCH 050/115] fix: can compile without via-ir --- foundry.toml | 4 +- .../SimplePosMiddleware.sol | 41 +++++++++++-------- src/middleware/extensions/sigs/ECDSASig.sol | 3 +- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/foundry.toml b/foundry.toml index 529f204..5d3a3fd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,4 @@ solc-version = "0.8.26" src = "src" out = "out" -libs = ["lib"] -via_ir = true -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +libs = ["lib"] \ No newline at end of file diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index e5451bb..d8deb60 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -15,7 +15,6 @@ import {Operators} from "../../middleware/extensions/Operators.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; - contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { using Subnetwork for address; @@ -32,6 +31,14 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { uint48 public immutable EPOCH_DURATION; // Duration of each epoch uint48 public immutable START_TIMESTAMP; // Start timestamp of the network + struct SlashParams { + uint48 epochStart; + address operator; + uint256 totalStake; + address[] vaults; + uint160[] subnetworks; + } + /* * @notice Constructor for initializing the SimpleMiddleware contract. * @param network The address of the network. @@ -139,41 +146,39 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { public onlyOwner { - uint48 epochStart = getEpochStart(epoch); - address operator = operatorByKey(abi.encode(key)); + SlashParams memory params; + params.epochStart = getEpochStart(epoch); + params.operator = operatorByKey(abi.encode(key)); _checkCanSlash(epoch, key); - uint256 totalStake = getOperatorStakeAt(operator, epochStart); - address[] memory vaults = activeVaultsAt(epochStart, operator); - uint160[] memory subnetworks = activeSubnetworksAt(epochStart); + params.totalStake = getOperatorStakeAt(params.operator, params.epochStart); + params.vaults = activeVaultsAt(params.epochStart, params.operator); + params.subnetworks = activeSubnetworksAt(params.epochStart); // Validate hints lengths upfront - if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { + if (stakeHints.length != slashHints.length || stakeHints.length != params.vaults.length) { revert InvalidHints(); } - for (uint256 i; i < vaults.length; ++i) { - if (stakeHints[i].length != subnetworks.length) { + for (uint256 i; i < params.vaults.length; ++i) { + if (stakeHints[i].length != params.subnetworks.length) { revert InvalidHints(); } - address vault = vaults[i]; - for (uint256 j; j < subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(subnetworks[j])); + address vault = params.vaults[i]; + for (uint256 j; j < params.subnetworks.length; ++j) { + bytes32 subnetwork = NETWORK.subnetwork(uint96(params.subnetworks[j])); uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( - subnetwork, - operator, - epochStart, - stakeHints[i][j] + subnetwork, params.operator, params.epochStart, stakeHints[i][j] ); - uint256 slashAmount = Math.mulDiv(amount, stake, totalStake); + uint256 slashAmount = Math.mulDiv(amount, stake, params.totalStake); if (slashAmount == 0) { continue; } - _slashVault(epochStart, vault, subnetwork, operator, slashAmount, slashHints[i]); + _slashVault(params.epochStart, vault, subnetwork, params.operator, slashAmount, slashHints[i]); } } } diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index d8b6d82..1e6d930 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -11,6 +11,7 @@ abstract contract ECDSASig is BaseSig { bytes32 key = abi.decode(key_, (bytes32)); bytes32 hash = keccak256(abi.encodePacked(msg.sender, key)); address signer = hash.recover(signature); - return signer == msg.sender; + address keyAddress = address(uint160(uint256(key))); + return signer == keyAddress; } } From 77e909bf64bdc2a9f794db4750933c8011d8cbec Mon Sep 17 00:00:00 2001 From: georgiypetrov Date: Sat, 16 Nov 2024 12:43:55 +0700 Subject: [PATCH 051/115] tests: ed25519 tests and refactor --- .gitignore | 4 +- package-lock.json | 241 ++++++++++++++++++ package.json | 17 ++ .../SimplePosMiddleware.sol | 15 +- src/key-storage/KeyStorage256.sol | 14 +- src/key-storage/KeyStorageBytes.sol | 8 +- src/libraries/Ed25519.sol | 4 +- src/libraries/PauseableEnumerableSet.sol | 32 ++- src/managers/OperatorManager.sol | 28 +- src/managers/VaultManager.sol | 80 ++++-- src/middleware/BaseMiddleware.sol | 8 +- src/middleware/extensions/Operators.sol | 24 +- src/middleware/extensions/OzAccessManaged.sol | 4 +- .../extensions/SelfRegisterOperators.sol | 12 +- src/middleware/extensions/SharedVaults.sol | 32 ++- src/middleware/extensions/Subnetworks.sol | 32 ++- src/middleware/extensions/sigs/ECDSASig.sol | 8 +- test/SigTests.t.sol | 183 +++++++++++++ test/helpers/ed25519TestData.json | 7 + test/helpers/ed25519TestGenerator.js | 107 ++++++++ 20 files changed, 780 insertions(+), 80 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 test/SigTests.t.sol create mode 100644 test/helpers/ed25519TestData.json create mode 100644 test/helpers/ed25519TestGenerator.js diff --git a/.gitignore b/.gitignore index deea2d0..36a800a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ out/ docs/ # Dotenv file -.env \ No newline at end of file +.env + +node_modules/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2fe0ea9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,241 @@ +{ + "name": "symbiotic-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "symbiotic-tests", + "version": "1.0.0", + "dependencies": { + "@noble/curves": "^1.3.0", + "ethereum-cryptography": "^2.1.3", + "ethers": "^6.11.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, + "node_modules/@noble/curves": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "dependencies": { + "@noble/hashes": "1.5.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, + "node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers": { + "version": "6.13.4", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.4.tgz", + "integrity": "sha512-21YtnZVg4/zKkCQPjrDj38B1r4nQvTZLopUGMLQ1ePU2zV/joCfDC3t3iKQjWRzjjjbzR+mdAIoikeBRNkdllA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ed497d0 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "symbiotic-tests", + "version": "1.0.0", + "description": "Test helpers for Symbiotic protocol", + "private": true, + "scripts": { + "testgen": "node test/helpers/ed25519TestGenerator.js" + }, + "dependencies": { + "@noble/curves": "^1.3.0", + "ethereum-cryptography": "^2.1.3", + "ethers": "^6.11.1" + }, + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index d8deb60..d123f79 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -67,7 +67,9 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { * @param epoch The epoch number. * @return The start timestamp. */ - function getEpochStart(uint48 epoch) public view returns (uint48) { + function getEpochStart( + uint48 epoch + ) public view returns (uint48) { return START_TIMESTAMP + epoch * EPOCH_DURATION; } @@ -142,10 +144,13 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { * @param stakeHints Hints for determining stakes. * @param slashHints Hints for the slashing process. */ - function slash(uint48 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) - public - onlyOwner - { + function slash( + uint48 epoch, + bytes32 key, + uint256 amount, + bytes[][] memory stakeHints, + bytes[] memory slashHints + ) public onlyOwner { SlashParams memory params; params.epochStart = getEpochStart(epoch); params.operator = operatorByKey(abi.encode(key)); diff --git a/src/key-storage/KeyStorage256.sol b/src/key-storage/KeyStorage256.sol index 3ab62eb..6c98483 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/key-storage/KeyStorage256.sol @@ -24,7 +24,9 @@ abstract contract KeyStorage256 is BaseMiddleware { * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ - function operatorByKey(bytes memory key) public view override returns (address) { + function operatorByKey( + bytes memory key + ) public view override returns (address) { return keyToOperator[abi.decode(key, (bytes32))]; } @@ -34,8 +36,14 @@ abstract contract KeyStorage256 is BaseMiddleware { * @param operator The address of the operator * @return The key associated with the specified operator */ - function operatorKey(address operator) public view override returns (bytes memory) { - return abi.encode(keys[operator].getActive(getCaptureTimestamp())[0]); + function operatorKey( + address operator + ) public view override returns (bytes memory) { + bytes32[] memory active = keys[operator].getActive(getCaptureTimestamp()); + if (active.length == 0) { + return abi.encode(ZERO_BYTES32); + } + return abi.encode(active[0]); } /** diff --git a/src/key-storage/KeyStorageBytes.sol b/src/key-storage/KeyStorageBytes.sol index 1d709ed..addac96 100644 --- a/src/key-storage/KeyStorageBytes.sol +++ b/src/key-storage/KeyStorageBytes.sol @@ -22,7 +22,9 @@ abstract contract KeyStorageBytes is BaseManager { * @param key The BLS key for which to find the associated operator * @return The address of the operator linked to the specified BLS key */ - function operatorByKey(bytes memory key) public view returns (address) { + function operatorByKey( + bytes memory key + ) public view returns (address) { return _keyData[key].getAddress(); } @@ -32,7 +34,9 @@ abstract contract KeyStorageBytes is BaseManager { * @param operator The address of the operator * @return The BLS key associated with the specified operator */ - function operatorKey(address operator) public view returns (bytes memory) { + function operatorKey( + address operator + ) public view returns (bytes memory) { if (keyUpdateTimestamp[operator] == getCaptureTimestamp()) { return prevKeys[operator]; } diff --git a/src/libraries/Ed25519.sol b/src/libraries/Ed25519.sol index 580b419..084b82d 100644 --- a/src/libraries/Ed25519.sol +++ b/src/libraries/Ed25519.sol @@ -5,7 +5,9 @@ pragma solidity ^0.8; library Ed25519 { // Computes (v^(2^250-1), v^11) mod p - function pow22501(uint256 v) private pure returns (uint256 p22501, uint256 p11) { + function pow22501( + uint256 v + ) private pure returns (uint256 p22501, uint256 p11) { p11 = mulmod(v, v, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); p22501 = mulmod(p11, p11, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); p22501 = mulmod( diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 3946510..c95790f 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -44,7 +44,9 @@ library PauseableEnumerableSet { * @param self The AddressSet storage. * @return The number of elements in the set. */ - function length(AddressSet storage self) internal view returns (uint256) { + function length( + AddressSet storage self + ) internal view returns (uint256) { return self.set.length(); } @@ -142,7 +144,9 @@ library PauseableEnumerableSet { * @param self The Uint160Set storage. * @return The number of elements. */ - function length(Uint160Set storage self) internal view returns (uint256) { + function length( + Uint160Set storage self + ) internal view returns (uint256) { return self.array.length; } @@ -283,7 +287,9 @@ library PauseableEnumerableSet { * @param self The Inner struct * @return The stored Uint160 as address */ - function getAddress(Inner storage self) internal view returns (address) { + function getAddress( + Inner storage self + ) internal view returns (address) { return address(self.value); } @@ -292,7 +298,9 @@ library PauseableEnumerableSet { * @param self The Inner struct. * @return The value, enabled timestamp and disabled timestamp. */ - function get(Inner storage self) internal view returns (uint160, uint48, uint48) { + function get( + Inner storage self + ) internal view returns (uint160, uint48, uint48) { return (self.value, self.enabledTimestamp, self.disabledTimestamp); } @@ -418,7 +426,9 @@ library PauseableEnumerableSet { * @param self The AddressSet storage. * @return The number of elements in the set. */ - function length(Bytes32Set storage self) internal view returns (uint256) { + function length( + Bytes32Set storage self + ) internal view returns (uint256) { return self.set.length(); } @@ -526,7 +536,9 @@ library PauseableEnumerableSet { * @param self The Uint160Set storage. * @return The number of elements. */ - function length(Uint256Set storage self) internal view returns (uint256) { + function length( + Uint256Set storage self + ) internal view returns (uint256) { return self.array.length; } @@ -688,7 +700,9 @@ library PauseableEnumerableSet { * @param self The Inner struct * @return The stored Uint160 as address */ - function getBytes32(Inner256 storage self) internal view returns (bytes32) { + function getBytes32( + Inner256 storage self + ) internal view returns (bytes32) { return bytes32(self.value); } @@ -697,7 +711,9 @@ library PauseableEnumerableSet { * @param self The Inner struct. * @return The value, enabled timestamp and disabled timestamp. */ - function get(Inner256 storage self) internal view returns (uint256, uint48, uint48) { + function get( + Inner256 storage self + ) internal view returns (uint256, uint48, uint48) { return (self.value, self.enabledTimestamp, self.disabledTimestamp); } diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index 04b51f7..aede709 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -34,7 +34,9 @@ abstract contract OperatorManager is BaseManager { * @param pos The index position in the operators array. * @return The address, enabled epoch, disabled epoch and enabled before disabled epoch of the operator. */ - function operatorWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { + function operatorWithTimesAt( + uint256 pos + ) public view returns (address, uint48, uint48) { return _operators.at(pos); } @@ -51,7 +53,9 @@ abstract contract OperatorManager is BaseManager { * @param timestamp The timestamp to check. * @return An array of addresses representing the active operators. */ - function activeOperatorsAt(uint48 timestamp) public view returns (address[] memory) { + function activeOperatorsAt( + uint48 timestamp + ) public view returns (address[] memory) { return _operators.getActive(timestamp); } @@ -70,7 +74,9 @@ abstract contract OperatorManager is BaseManager { * @param operator The address of the operator to check. * @return A boolean indicating whether the operator is registered. */ - function isOperatorRegistered(address operator) public view returns (bool) { + function isOperatorRegistered( + address operator + ) public view returns (bool) { return _operators.contains(operator); } @@ -78,7 +84,9 @@ abstract contract OperatorManager is BaseManager { * @notice Registers a new operator. * @param operator The address of the operator to register. */ - function _registerOperator(address operator) internal { + function _registerOperator( + address operator + ) internal { if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { revert NotOperator(); } @@ -94,7 +102,9 @@ abstract contract OperatorManager is BaseManager { * @notice Pauses a registered operator. * @param operator The address of the operator to pause. */ - function _pauseOperator(address operator) internal { + function _pauseOperator( + address operator + ) internal { _operators.pause(Time.timestamp(), operator); } @@ -102,7 +112,9 @@ abstract contract OperatorManager is BaseManager { * @notice Unpauses a paused operator. * @param operator The address of the operator to unpause. */ - function _unpauseOperator(address operator) internal { + function _unpauseOperator( + address operator + ) internal { _operators.unpause(Time.timestamp(), SLASHING_WINDOW, operator); } @@ -110,7 +122,9 @@ abstract contract OperatorManager is BaseManager { * @notice Unregisters an operator. * @param operator The address of the operator to unregister. */ - function _unregisterOperator(address operator) internal { + function _unregisterOperator( + address operator + ) internal { _operators.unregister(Time.timestamp(), SLASHING_WINDOW, operator); } } diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index d988a44..c9aef49 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -61,7 +61,9 @@ abstract contract VaultManager is BaseManager { * @return enableTime The time when the subnetwork was enabled * @return disableTime The time when the subnetwork was disabled */ - function subnetworkWithTimesAt(uint256 pos) public view returns (uint160, uint48, uint48) { + function subnetworkWithTimesAt( + uint256 pos + ) public view returns (uint160, uint48, uint48) { return _subnetworks.at(pos); } @@ -78,7 +80,9 @@ abstract contract VaultManager is BaseManager { * @param timestamp The timestamp to check activity at * @return An array of active subnetwork addresses */ - function activeSubnetworksAt(uint48 timestamp) public view returns (uint160[] memory) { + function activeSubnetworksAt( + uint48 timestamp + ) public view returns (uint160[] memory) { return _subnetworks.getActive(timestamp); } @@ -107,7 +111,9 @@ abstract contract VaultManager is BaseManager { * @return enableTime The time when the vault was enabled * @return disableTime The time when the vault was disabled */ - function sharedVaultWithTimesAt(uint256 pos) public view returns (address, uint48, uint48) { + function sharedVaultWithTimesAt( + uint256 pos + ) public view returns (address, uint48, uint48) { return _sharedVaults.at(pos); } @@ -124,7 +130,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return The count of vaults for the operator */ - function operatorVaultsLength(address operator) public view returns (uint256) { + function operatorVaultsLength( + address operator + ) public view returns (uint256) { return _operatorVaults[operator].length(); } @@ -145,7 +153,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return An array of active vault addresses */ - function activeOperatorVaults(address operator) public view returns (address[] memory) { + function activeOperatorVaults( + address operator + ) public view returns (address[] memory) { return _operatorVaults[operator].getActive(getCaptureTimestamp()); } @@ -183,7 +193,9 @@ abstract contract VaultManager is BaseManager { * @param timestamp The timestamp to check activity at * @return An array of active vault addresses */ - function activeVaultsAt(uint48 timestamp) public view virtual returns (address[] memory) { + function activeVaultsAt( + uint48 timestamp + ) public view virtual returns (address[] memory) { address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); uint256 len = activeSharedVaults_.length; address[] memory vaults = new address[](len + _vaultOperator.length()); @@ -212,7 +224,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return An array of active vault addresses */ - function activeVaults(address operator) public view virtual returns (address[] memory) { + function activeVaults( + address operator + ) public view virtual returns (address[] memory) { uint48 timestamp = getCaptureTimestamp(); address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); address[] memory activeOperatorVaults_ = _operatorVaults[operator].getActive(timestamp); @@ -349,7 +363,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return stake The total stake amount */ - function getOperatorStake(address operator) public view virtual returns (uint256 stake) { + function getOperatorStake( + address operator + ) public view virtual returns (uint256 stake) { address[] memory vaults = activeVaults(operator); uint160[] memory subnetworks = activeSubnetworks(); @@ -388,7 +404,9 @@ abstract contract VaultManager is BaseManager { * @param operator The address of the operator * @return power The total power amount */ - function getOperatorPower(address operator) public view virtual returns (uint256 power) { + function getOperatorPower( + address operator + ) public view virtual returns (uint256 power) { address[] memory vaults = activeVaults(operator); uint160[] memory subnetworks = activeSubnetworks(); @@ -427,7 +445,9 @@ abstract contract VaultManager is BaseManager { * @param operators Array of operator addresses * @return stake The total stake amount */ - function _totalStake(address[] memory operators) internal view returns (uint256 stake) { + function _totalStake( + address[] memory operators + ) internal view returns (uint256 stake) { for (uint256 i; i < operators.length; ++i) { uint256 operatorStake = getOperatorStake(operators[i]); stake += operatorStake; @@ -441,7 +461,9 @@ abstract contract VaultManager is BaseManager { * @param operators Array of operator addresses * @return power The total power amount */ - function _totalPower(address[] memory operators) internal view returns (uint256 power) { + function _totalPower( + address[] memory operators + ) internal view returns (uint256 power) { for (uint256 i; i < operators.length; ++i) { uint256 operatorStake = getOperatorPower(operators[i]); power += operatorStake; @@ -454,7 +476,9 @@ abstract contract VaultManager is BaseManager { * @notice Registers a new subnetwork * @param subnetwork The identifier of the subnetwork to register */ - function _registerSubnetwork(uint96 subnetwork) internal { + function _registerSubnetwork( + uint96 subnetwork + ) internal { _subnetworks.register(Time.timestamp(), uint160(subnetwork)); } @@ -462,7 +486,9 @@ abstract contract VaultManager is BaseManager { * @notice Pauses a subnetwork * @param subnetwork The identifier of the subnetwork to pause */ - function _pauseSubnetwork(uint96 subnetwork) internal { + function _pauseSubnetwork( + uint96 subnetwork + ) internal { _subnetworks.pause(Time.timestamp(), uint160(subnetwork)); } @@ -470,7 +496,9 @@ abstract contract VaultManager is BaseManager { * @notice Unpauses a subnetwork * @param subnetwork The identifier of the subnetwork to unpause */ - function _unpauseSubnetwork(uint96 subnetwork) internal { + function _unpauseSubnetwork( + uint96 subnetwork + ) internal { _subnetworks.unpause(Time.timestamp(), SLASHING_WINDOW, uint160(subnetwork)); } @@ -478,7 +506,9 @@ abstract contract VaultManager is BaseManager { * @notice Unregisters a subnetwork * @param subnetwork The identifier of the subnetwork to unregister */ - function _unregisterSubnetwork(uint96 subnetwork) internal { + function _unregisterSubnetwork( + uint96 subnetwork + ) internal { _subnetworks.unregister(Time.timestamp(), SLASHING_WINDOW, uint160(subnetwork)); } @@ -486,7 +516,9 @@ abstract contract VaultManager is BaseManager { * @notice Registers a new shared vault * @param vault The address of the vault to register */ - function _registerSharedVault(address vault) internal { + function _registerSharedVault( + address vault + ) internal { _validateVault(vault); _sharedVaults.register(Time.timestamp(), vault); } @@ -509,7 +541,9 @@ abstract contract VaultManager is BaseManager { * @notice Pauses a shared vault * @param vault The address of the vault to pause */ - function _pauseSharedVault(address vault) internal { + function _pauseSharedVault( + address vault + ) internal { _sharedVaults.pause(Time.timestamp(), vault); } @@ -517,7 +551,9 @@ abstract contract VaultManager is BaseManager { * @notice Unpauses a shared vault * @param vault The address of the vault to unpause */ - function _unpauseSharedVault(address vault) internal { + function _unpauseSharedVault( + address vault + ) internal { _sharedVaults.unpause(Time.timestamp(), SLASHING_WINDOW, vault); } @@ -543,7 +579,9 @@ abstract contract VaultManager is BaseManager { * @notice Unregisters a shared vault * @param vault The address of the vault to unregister */ - function _unregisterSharedVault(address vault) internal { + function _unregisterSharedVault( + address vault + ) internal { _sharedVaults.unregister(Time.timestamp(), SLASHING_WINDOW, vault); } @@ -626,7 +664,9 @@ abstract contract VaultManager is BaseManager { * @notice Validates if the vault is properly initialized and registered * @param vault The address of the vault to validate */ - function _validateVault(address vault) private view { + function _validateVault( + address vault + ) private view { if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { revert NotVault(); } diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 3a76478..a6ab904 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -48,7 +48,9 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager * @param key The key for which to find the associated operator * @return The address of the operator linked to the specified key */ - function operatorByKey(bytes memory key) public view virtual returns (address); + function operatorByKey( + bytes memory key + ) public view virtual returns (address); /** * @notice Returns the current or previous key for a given operator @@ -56,5 +58,7 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager * @param operator The address of the operator * @return The key associated with the specified operator */ - function operatorKey(address operator) public view virtual returns (bytes memory); + function operatorKey( + address operator + ) public view virtual returns (bytes memory); } diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/Operators.sol index 7ba0d76..7e0ed3a 100644 --- a/src/middleware/extensions/Operators.sol +++ b/src/middleware/extensions/Operators.sol @@ -19,24 +19,36 @@ abstract contract Operators is BaseMiddleware { } } - function unregisterOperator(address operator) public checkAccess { + function unregisterOperator( + address operator + ) public checkAccess { _beforeUnregisterOperator(operator); _unregisterOperator(operator); } - function pauseOperator(address operator) public checkAccess { + function pauseOperator( + address operator + ) public checkAccess { _beforePauseOperator(operator); _pauseOperator(operator); } - function unpauseOperator(address operator) public checkAccess { + function unpauseOperator( + address operator + ) public checkAccess { _beforeUnpauseOperator(operator); _unpauseOperator(operator); } function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} - function _beforeUnregisterOperator(address operator) internal virtual {} - function _beforePauseOperator(address operator) internal virtual {} - function _beforeUnpauseOperator(address operator) internal virtual {} + function _beforeUnregisterOperator( + address operator + ) internal virtual {} + function _beforePauseOperator( + address operator + ) internal virtual {} + function _beforeUnpauseOperator( + address operator + ) internal virtual {} } diff --git a/src/middleware/extensions/OzAccessManaged.sol b/src/middleware/extensions/OzAccessManaged.sol index 9d7fd9f..992f913 100644 --- a/src/middleware/extensions/OzAccessManaged.sol +++ b/src/middleware/extensions/OzAccessManaged.sol @@ -6,7 +6,9 @@ import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/acce import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { - constructor(address authority) { + constructor( + address authority + ) { __AccessManaged_init(authority); } diff --git a/src/middleware/extensions/SelfRegisterOperators.sol b/src/middleware/extensions/SelfRegisterOperators.sol index 9946526..17ef236 100644 --- a/src/middleware/extensions/SelfRegisterOperators.sol +++ b/src/middleware/extensions/SelfRegisterOperators.sol @@ -45,7 +45,13 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig { function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} - function _beforeUnregisterOperator(address operator) internal virtual {} - function _beforePauseOperator(address operator) internal virtual {} - function _beforeUnpauseOperator(address operator) internal virtual {} + function _beforeUnregisterOperator( + address operator + ) internal virtual {} + function _beforePauseOperator( + address operator + ) internal virtual {} + function _beforeUnpauseOperator( + address operator + ) internal virtual {} } diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 77347ff..601e072 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -4,28 +4,44 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract SharedVaults is BaseMiddleware { - function registerSharedVault(address sharedVault) public checkAccess { + function registerSharedVault( + address sharedVault + ) public checkAccess { _beforeRegisterSharedVault(sharedVault); _registerSharedVault(sharedVault); } - function pauseSharedVault(address sharedVault) public checkAccess { + function pauseSharedVault( + address sharedVault + ) public checkAccess { _beforePauseSharedVault(sharedVault); _pauseSharedVault(sharedVault); } - function unpauseSharedVault(address sharedVault) public checkAccess { + function unpauseSharedVault( + address sharedVault + ) public checkAccess { _beforeUnpauseSharedVault(sharedVault); _unpauseSharedVault(sharedVault); } - function unregisterSharedVault(address sharedVault) public checkAccess { + function unregisterSharedVault( + address sharedVault + ) public checkAccess { _beforeUnregisterSharedVault(sharedVault); _unregisterSharedVault(sharedVault); } - function _beforeRegisterSharedVault(address sharedVault) internal virtual {} - function _beforePauseSharedVault(address sharedVault) internal virtual {} - function _beforeUnpauseSharedVault(address sharedVault) internal virtual {} - function _beforeUnregisterSharedVault(address sharedVault) internal virtual {} + function _beforeRegisterSharedVault( + address sharedVault + ) internal virtual {} + function _beforePauseSharedVault( + address sharedVault + ) internal virtual {} + function _beforeUnpauseSharedVault( + address sharedVault + ) internal virtual {} + function _beforeUnregisterSharedVault( + address sharedVault + ) internal virtual {} } diff --git a/src/middleware/extensions/Subnetworks.sol b/src/middleware/extensions/Subnetworks.sol index 4250bf1..362b942 100644 --- a/src/middleware/extensions/Subnetworks.sol +++ b/src/middleware/extensions/Subnetworks.sol @@ -6,28 +6,44 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {BaseMiddleware} from "../BaseMiddleware.sol"; abstract contract Subnetworks is BaseMiddleware { - function registerSubnetwork(uint96 subnetwork) public checkAccess { + function registerSubnetwork( + uint96 subnetwork + ) public checkAccess { _beforeRegisterSubnetwork(subnetwork); _registerSubnetwork(subnetwork); } - function pauseSubnetwork(uint96 subnetwork) public checkAccess { + function pauseSubnetwork( + uint96 subnetwork + ) public checkAccess { _beforePauseSubnetwork(subnetwork); _pauseSubnetwork(subnetwork); } - function unpauseSubnetwork(uint96 subnetwork) public checkAccess { + function unpauseSubnetwork( + uint96 subnetwork + ) public checkAccess { _beforeUnpauseSubnetwork(subnetwork); _unpauseSubnetwork(subnetwork); } - function unregisterSubnetwork(uint96 subnetwork) public checkAccess { + function unregisterSubnetwork( + uint96 subnetwork + ) public checkAccess { _beforeUnregisterSubnetwork(subnetwork); _unregisterSubnetwork(subnetwork); } - function _beforeRegisterSubnetwork(uint96 subnetwork) internal virtual {} - function _beforePauseSubnetwork(uint96 subnetwork) internal virtual {} - function _beforeUnpauseSubnetwork(uint96 subnetwork) internal virtual {} - function _beforeUnregisterSubnetwork(uint96 subnetwork) internal virtual {} + function _beforeRegisterSubnetwork( + uint96 subnetwork + ) internal virtual {} + function _beforePauseSubnetwork( + uint96 subnetwork + ) internal virtual {} + function _beforeUnpauseSubnetwork( + uint96 subnetwork + ) internal virtual {} + function _beforeUnregisterSubnetwork( + uint96 subnetwork + ) internal virtual {} } diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index 1e6d930..b86b0be 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {BaseSig} from "./BaseSig.sol"; abstract contract ECDSASig is BaseSig { - using ECDSA for bytes32; - function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); bytes32 hash = keccak256(abi.encodePacked(msg.sender, key)); - address signer = hash.recover(signature); + (uint8 v, bytes32 r, bytes32 s) = abi.decode(signature, (uint8, bytes32, bytes32)); + address signer = ecrecover(hash, v, r, s); address keyAddress = address(uint160(uint256(key))); - return signer == keyAddress; + return signer == keyAddress && signer != address(0); } } diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol new file mode 100644 index 0000000..dce7992 --- /dev/null +++ b/test/SigTests.t.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Test, Vm, console} from "forge-std/Test.sol"; +import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; +import {SelfRegisterMiddleware} from "../src/examples/self-register-network/SelfRegisterMiddleware.sol"; +import {SelfRegisterEd25519Middleware} from "../src/examples/self-register-network/SelfRegisterEd25519Middleware.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +contract SigTests is POCBaseTest { + using ECDSA for bytes32; + using MessageHashUtils for bytes32; + + SelfRegisterMiddleware internal middleware; + SelfRegisterEd25519Middleware internal ed25519Middleware; + uint256 internal operatorPrivateKey; + address internal operator; + bytes32 internal operatorPublicKey; + string internal constant ED25519_TEST_DATA = "test/helpers/ed25519TestData.json"; + address internal ed25519Operator; + + function setUp() public override { + SYMBIOTIC_CORE_PROJECT_ROOT = "lib/core/"; + vm.warp(1_729_690_309); + super.setUp(); + + (operator, operatorPrivateKey) = makeAddrAndKey("operator"); + operatorPublicKey = bytes32(uint256(uint160(operator))); + + string memory json = vm.readFile(ED25519_TEST_DATA); + ed25519Operator = abi.decode(vm.parseJson(json, ".operator"), (address)); + + // Initialize both middlewares + middleware = new SelfRegisterMiddleware( + address(0x123), + address(operatorRegistry), + address(vaultFactory), + address(operatorNetworkOptInService), + owner, + 1200 // slashing window + ); + + ed25519Middleware = new SelfRegisterEd25519Middleware( + address(0x456), + address(operatorRegistry), + address(vaultFactory), + address(operatorNetworkOptInService), + owner, + 1200 // slashing window + ); + + _registerNetwork(address(0x123), address(middleware)); + _registerNetwork(address(0x456), address(ed25519Middleware)); + _registerOperator(operator); + _registerOperator(ed25519Operator); + _optInOperatorVault(vault1, operator); + _optInOperatorVault(vault1, ed25519Operator); + _optInOperatorNetwork(operator, address(0x123)); + _optInOperatorNetwork(ed25519Operator, address(0x456)); + } + + function testEd25519RegisterOperator() public { + string memory json = vm.readFile(ED25519_TEST_DATA); + bytes32 key = abi.decode(vm.parseJson(json, ".key"), (bytes32)); + bytes memory signature = abi.decode(vm.parseJson(json, ".signature"), (bytes)); + + // Register operator using Ed25519 signature + vm.prank(ed25519Operator); + ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + + // Verify operator is registered correctly + assertTrue(ed25519Middleware.isOperatorRegistered(ed25519Operator)); + + assertEq(abi.decode(ed25519Middleware.operatorKey(ed25519Operator), (bytes32)), bytes32(0)); + vm.warp(block.timestamp + 2); + assertEq(abi.decode(ed25519Middleware.operatorKey(ed25519Operator), (bytes32)), key); + } + + function testEd25519RegisterOperatorInvalidSignature() public { + string memory json = vm.readFile(ED25519_TEST_DATA); + bytes32 key = abi.decode(vm.parseJson(json, ".key"), (bytes32)); + + // Create invalid signature + bytes32 r = bytes32(uint256(1)); + bytes32 s = bytes32(uint256(2)); + bytes memory signature = abi.encodePacked(r, s); + + // Attempt to register with invalid signature should fail + vm.prank(ed25519Operator); + vm.expectRevert(); + ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + } + + function testEd25519RegisterOperatorWrongSender() public { + string memory json = vm.readFile(ED25519_TEST_DATA); + bytes32 key = abi.decode(vm.parseJson(json, ".key"), (bytes32)); + bytes memory signature = abi.decode(vm.parseJson(json, ".signature"), (bytes)); + bytes32 r; + bytes32 s; + assembly { + r := mload(add(signature, 32)) + s := mload(add(signature, 64)) + } + + // Attempt to register from different address should fail + vm.prank(alice); + vm.expectRevert(); + ed25519Middleware.registerOperator(abi.encode(key), address(vault1), abi.encodePacked(r, s)); + } + + function testSelfRegisterOperator() public { + // Create registration message + bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); + // Sign message with operator's private key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); + bytes memory signature = abi.encode(v, r, s); + // Register operator using their own signature + vm.prank(operator); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + + // Verify operator is registered correctly + assertTrue(middleware.isOperatorRegistered(operator)); + + assertEq(abi.decode(middleware.operatorKey(operator), (bytes32)), bytes32(0)); + vm.warp(block.timestamp + 2); + assertEq(abi.decode(middleware.operatorKey(operator), (bytes32)), operatorPublicKey); + } + + function testSelfRegisterOperatorInvalidSignature() public { + // Create registration message with wrong key + bytes32 wrongKey = bytes32(uint256(1)); + bytes32 messageHash = keccak256(abi.encodePacked(operator, wrongKey)); + // Sign message + (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); + bytes memory signature = abi.encode(v, r, s); + + // Attempt to register with mismatched key should fail + vm.prank(operator); + vm.expectRevert(); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + } + + function testSelfRegisterOperatorWrongSender() public { + // Create valid registration message + bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); + // Sign message with operator's key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); + bytes memory signature = abi.encode(v, r, s); + + // Attempt to register from different address should fail + vm.prank(alice); + vm.expectRevert(); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + } + + function testSelfRegisterOperatorAlreadyRegistered() public { + // Create registration message + bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); + // Sign message + (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); + bytes memory signature = abi.encode(v, r, s); + // Register operator first time + vm.prank(operator); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + + // Attempt to register again should fail + vm.prank(operator); + vm.expectRevert(); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + } + + function testEd25519RegisterOperatorMismatchedKeyAndSignature() public { + string memory json = vm.readFile(ED25519_TEST_DATA); + bytes32 key = abi.decode(vm.parseJson(json, ".invalidKey"), (bytes32)); + bytes memory signature = abi.decode(vm.parseJson(json, ".invalidSignature"), (bytes)); + + // Attempt to register with mismatched key and signature should fail + vm.prank(ed25519Operator); + vm.expectRevert(); + ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + } +} diff --git a/test/helpers/ed25519TestData.json b/test/helpers/ed25519TestData.json new file mode 100644 index 0000000..f09a97a --- /dev/null +++ b/test/helpers/ed25519TestData.json @@ -0,0 +1,7 @@ +{ + "operator": "0xc2c1697Fe88772f844D1b622F4fc0E6E0b16Cb77", + "key": "0x121516a0d84c94a28d9f9cd2263d3fe8eceee3cef43aa043e388cce914f85205", + "signature": "0x5d590ecf3f7019846aba3c363ddb8f640109f9fa35a5d8ce027407e8d4507045b651075cfbecbb86ede90a4f20ac99fa969419c81ee4d7066868bfcd5e47d50d", + "invalidKey": "0x98c6edf296f06a1b23e56d56020c0ea8289d07a8b0a23e953b24239697cf5a96", + "invalidSignature": "0x8f3d84ae06d3d912328d6c5c97622d880c2de9255d50c697314fc5f61ce0d211b8603ac2c3287ed607364d8cd1e89675a1f3e0892c11ddc7116cf92221b63600" +} \ No newline at end of file diff --git a/test/helpers/ed25519TestGenerator.js b/test/helpers/ed25519TestGenerator.js new file mode 100644 index 0000000..7096f75 --- /dev/null +++ b/test/helpers/ed25519TestGenerator.js @@ -0,0 +1,107 @@ +const fs = require('fs'); +const { ed25519 } = require('@noble/curves/ed25519'); +const { bytesToHex, hexToBytes } = require('@noble/curves/abstract/utils'); +const { keccak256 } = require('ethereum-cryptography/keccak'); +const { ethers } = require('ethers'); + +// Generate random operator address +function generateOperatorAddress() { + const wallet = ethers.Wallet.createRandom(); + return wallet.address; +} + +// Generate Ed25519 keypair and signature +function generateTestData(operatorAddress) { + // Generate keypair + const privateKey = ed25519.utils.randomPrivateKey(); + const publicKey = ed25519.getPublicKey(privateKey); + + // Create message hash as done in the contract + let message = keccak256( + Buffer.concat([ + Buffer.from(operatorAddress.replace('0x', ''), 'hex'), + publicKey, + ]) + ); + + // Add 9 bytes of zeros to the message + message = Buffer.concat([ + message, + Buffer.alloc(9) + ]); + + // Sign the message + const signature = "0x" + bytesToHex(ed25519.sign(message, privateKey)); + + // Verify the signature + const isValid = ed25519.verify( + hexToBytes(signature.slice(2)), // Remove 0x prefix + message, + publicKey + ); + + if (!isValid) { + throw new Error('Generated signature failed verification'); + } + + // Return ABI encoded data + const abiCoder = new ethers.AbiCoder(); + const key = abiCoder.encode(['bytes32'], ['0x' + bytesToHex(publicKey)]); + + return { + operatorAddress, + key, + signature: signature + }; +} + +// Generate invalid test data with mismatched key and signature +function generateInvalidTestData(operatorAddress) { + // Generate two different keypairs + const privateKey1 = ed25519.utils.randomPrivateKey(); + const publicKey1 = ed25519.getPublicKey(privateKey1); + const privateKey2 = ed25519.utils.randomPrivateKey(); + const publicKey2 = ed25519.getPublicKey(privateKey2); + + // Create message hash with publicKey1 + let message = keccak256( + Buffer.concat([ + Buffer.from(operatorAddress.replace('0x', ''), 'hex'), + publicKey1, + ]) + ); + + message = Buffer.concat([ + message, + Buffer.alloc(9) + ]); + + // Sign with privateKey2 (mismatch) + const signature = "0x" + bytesToHex(ed25519.sign(message, privateKey2)); + + // ABI encode publicKey1 + const abiCoder = new ethers.AbiCoder(); + const key = abiCoder.encode(['bytes32'], ['0x' + bytesToHex(publicKey1)]); + + return { + operatorAddress, + key, + signature: signature + }; +} + +// Generate both valid and invalid test data +const operatorAddress = generateOperatorAddress(); +const validTestData = generateTestData(operatorAddress); +const invalidTestData = generateInvalidTestData(operatorAddress); + +// Write data to file +fs.writeFileSync('test/helpers/ed25519TestData.json', + JSON.stringify({ + operator: validTestData.operatorAddress, + key: validTestData.key, + signature: validTestData.signature, + invalidKey: invalidTestData.key, + invalidSignature: invalidTestData.signature + }, null, 2) +); \ No newline at end of file From 1b764437dc975627874c92e576b4ddacf337e0bd Mon Sep 17 00:00:00 2001 From: Georgy Date: Mon, 18 Nov 2024 18:35:25 +0700 Subject: [PATCH 052/115] refactor: add recover function and ecrecover via OZ ECDSA --- src/middleware/extensions/sigs/ECDSASig.sol | 10 ++++++++-- test/SigTests.t.sol | 8 ++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index b86b0be..48a0dd6 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -2,14 +2,20 @@ pragma solidity ^0.8.25; import {BaseSig} from "./BaseSig.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; abstract contract ECDSASig is BaseSig { + using ECDSA for bytes32; + function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); bytes32 hash = keccak256(abi.encodePacked(msg.sender, key)); - (uint8 v, bytes32 r, bytes32 s) = abi.decode(signature, (uint8, bytes32, bytes32)); - address signer = ecrecover(hash, v, r, s); + address signer = recover(hash, signature); address keyAddress = address(uint160(uint256(key))); return signer == keyAddress && signer != address(0); } + + function recover(bytes32 hash, bytes memory signature) public pure returns (address) { + return hash.recover(signature); + } } diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol index dce7992..ebdb3cf 100644 --- a/test/SigTests.t.sol +++ b/test/SigTests.t.sol @@ -114,7 +114,7 @@ contract SigTests is POCBaseTest { bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); // Sign message with operator's private key (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); - bytes memory signature = abi.encode(v, r, s); + bytes memory signature = abi.encodePacked(r, s, v); // Register operator using their own signature vm.prank(operator); middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); @@ -133,7 +133,7 @@ contract SigTests is POCBaseTest { bytes32 messageHash = keccak256(abi.encodePacked(operator, wrongKey)); // Sign message (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); - bytes memory signature = abi.encode(v, r, s); + bytes memory signature = abi.encodePacked(r, s, v); // Attempt to register with mismatched key should fail vm.prank(operator); @@ -146,7 +146,7 @@ contract SigTests is POCBaseTest { bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); // Sign message with operator's key (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); - bytes memory signature = abi.encode(v, r, s); + bytes memory signature = abi.encodePacked(r, s, v); // Attempt to register from different address should fail vm.prank(alice); @@ -159,7 +159,7 @@ contract SigTests is POCBaseTest { bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); // Sign message (uint8 v, bytes32 r, bytes32 s) = vm.sign(operatorPrivateKey, messageHash); - bytes memory signature = abi.encode(v, r, s); + bytes memory signature = abi.encodePacked(r, s, v); // Register operator first time vm.prank(operator); middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); From 5bac83f41c7d6e93b133735722f521eceb7226f9 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 18 Nov 2024 18:38:57 +0700 Subject: [PATCH 053/115] refactor: recover is internal --- src/middleware/extensions/sigs/ECDSASig.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index 48a0dd6..162840b 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -15,7 +15,7 @@ abstract contract ECDSASig is BaseSig { return signer == keyAddress && signer != address(0); } - function recover(bytes32 hash, bytes memory signature) public pure returns (address) { + function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { return hash.recover(signature); } } From c41c8b5dad3c2adad711a41ac37ef5f6086d8ab6 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 20 Nov 2024 21:03:30 +0700 Subject: [PATCH 054/115] refactor keys and self register operators --- .../SelfRegisterEd25519Middleware.sol | 21 +- .../SelfRegisterMiddleware.sol | 24 +- .../SimplePosMiddleware.sol | 22 +- .../sqrt-task-network/SqrtTaskMiddleware.sol | 13 +- src/key-storage/KeyStorage256.sol | 40 +- src/key-storage/KeyStorageBytes.sol | 83 +- src/libraries/PauseableEnumerableSet.sol | 856 +++++------------- src/managers/AccessManager.sol | 2 +- src/managers/BaseManager.sol | 8 +- src/middleware/BaseMiddleware.sol | 22 +- .../extensions/SelfRegisterOperators.sol | 57 -- .../access-managers/NoAccessManager.sol | 11 + .../access-managers/OwnableAccessManager.sol | 29 + .../{ => access-managers}/OzAccessManaged.sol | 6 +- .../ForcePauseSelfRegisterOperators.sol | 38 + .../extensions/{ => operators}/Operators.sol | 42 +- .../operators/SelfRegisterOperators.sol | 229 +++++ src/middleware/extensions/sigs/BaseSig.sol | 7 +- src/middleware/extensions/sigs/ECDSASig.sol | 8 +- src/middleware/extensions/sigs/Ed25519Sig.sol | 8 +- test/OperatorsRegistration.t.sol | 5 +- test/SigTests.t.sol | 10 +- test/mocks/ExtendedSimplePosMiddleware.sol | 26 - 23 files changed, 703 insertions(+), 864 deletions(-) delete mode 100644 src/middleware/extensions/SelfRegisterOperators.sol create mode 100644 src/middleware/extensions/access-managers/NoAccessManager.sol create mode 100644 src/middleware/extensions/access-managers/OwnableAccessManager.sol rename src/middleware/extensions/{ => access-managers}/OzAccessManaged.sol (77%) create mode 100644 src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol rename src/middleware/extensions/{ => operators}/Operators.sol (58%) create mode 100644 src/middleware/extensions/operators/SelfRegisterOperators.sol delete mode 100644 test/mocks/ExtendedSimplePosMiddleware.sol diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index 49cc504..a6896bf 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -11,12 +11,13 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; -import {SelfRegisterOperators} from "../../middleware/extensions/SelfRegisterOperators.sol"; +import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; +import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; import {Ed25519Sig} from "../../middleware/extensions/sigs/Ed25519Sig.sol"; -contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, KeyStorage256, Ed25519Sig { +contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, KeyStorage256, Ed25519Sig, NoAccessManager { /* * @notice Constructor for initializing the SelfRegisterEd25519Middleware contract. * @param network The address of the network. @@ -31,7 +32,19 @@ contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, K address operatorRegistry, address vaultRegistry, address operatorNetOptin, - address owner, uint48 slashingWindow - ) BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) {} + ) { + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + } + + function initialize( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptIn + ) public override initializer { + super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + __SelfRegisterOperators_init("SelfRegisterEd25519Middleware"); + } } diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index 641b088..2e48141 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -11,11 +11,13 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; -import {SelfRegisterOperators} from "../../middleware/extensions/SelfRegisterOperators.sol"; -import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; +import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; import {ECDSASig} from "../../middleware/extensions/sigs/ECDSASig.sol"; +import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; + +import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; -contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyStorage256, ECDSASig { +contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyStorage256, ECDSASig, NoAccessManager { /* * @notice Constructor for initializing the SelfRegisterMiddleware contract. * @param network The address of the network. @@ -30,7 +32,19 @@ contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyStora address operatorRegistry, address vaultRegistry, address operatorNetOptin, - address owner, uint48 slashingWindow - ) BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) {} + ) { + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + } + + function initialize( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptIn + ) public override initializer { + super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + __SelfRegisterOperators_init("SelfRegisterMiddleware"); + } } diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index d123f79..3df02c1 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -11,11 +11,12 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; -import {Operators} from "../../middleware/extensions/Operators.sol"; +import {Operators} from "../../middleware/extensions/operators/Operators.sol"; +import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; -contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { +contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableAccessManager { using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key @@ -57,11 +58,24 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { address owner, uint48 epochDuration, uint48 slashingWindow - ) BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) { + ) { + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, owner); EPOCH_DURATION = epochDuration; START_TIMESTAMP = Time.timestamp(); } + function initialize( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptin, + address owner + ) public initializer { + super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + __OwnableAccessManaged_init(owner); + } + /* * @notice Returns the start timestamp for a given epoch. * @param epoch The epoch number. @@ -150,7 +164,7 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256 { uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints - ) public onlyOwner { + ) public checkAccess { SlashParams memory params; params.epochStart = getEpochStart(epoch); params.operator = operatorByKey(abi.encode(key)); diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 1c751bc..db3abd4 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -12,11 +12,11 @@ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/Signa import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; -import {Operators} from "../../middleware/extensions/Operators.sol"; - +import {Operators} from "../../middleware/extensions/operators/Operators.sol"; +import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; -contract SqrtTaskMiddleware is SharedVaults, Operators, KeyStorage256, EIP712 { +contract SqrtTaskMiddleware is SharedVaults, Operators, KeyStorage256, EIP712, OwnableAccessManager { using Subnetwork for address; using Math for uint256; @@ -45,10 +45,9 @@ contract SqrtTaskMiddleware is SharedVaults, Operators, KeyStorage256, EIP712 { address operatorNetOptin, address owner, uint48 slashingWindow - ) - EIP712("SqrtTaskMiddleware", "1") - BaseMiddleware(network, operatorRegistry, vaultRegistry, operatorNetOptin, slashingWindow, owner) - {} + ) EIP712("SqrtTaskMiddleware", "1") { + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + } function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { taskIndex = tasks.length; diff --git a/src/key-storage/KeyStorage256.sol b/src/key-storage/KeyStorage256.sol index 6c98483..a106a01 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/key-storage/KeyStorage256.sol @@ -8,6 +8,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; abstract contract KeyStorage256 is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; + using PauseableEnumerableSet for PauseableEnumerableSet.Status; error DuplicateKey(); error KeyAlreadyEnabled(); @@ -16,8 +17,8 @@ abstract contract KeyStorage256 is BaseMiddleware { bytes32 private constant ZERO_BYTES32 = bytes32(0); uint256 private constant MAX_DISABLED_KEYS = 1; - mapping(address => PauseableEnumerableSet.Bytes32Set) internal keys; // Mapping from operator addresses to their current keys - mapping(bytes32 => address) internal keyToOperator; // Mapping from keys to operator addresses + mapping(address => PauseableEnumerableSet.Bytes32Set) internal _keys; // Mapping from operator addresses to their current keys + mapping(bytes32 => address) internal _keyToOperator; // Mapping from keys to operator addresses /** * @notice Returns the operator address associated with a given key @@ -27,7 +28,7 @@ abstract contract KeyStorage256 is BaseMiddleware { function operatorByKey( bytes memory key ) public view override returns (address) { - return keyToOperator[abi.decode(key, (bytes32))]; + return _keyToOperator[abi.decode(key, (bytes32))]; } /** @@ -39,7 +40,7 @@ abstract contract KeyStorage256 is BaseMiddleware { function operatorKey( address operator ) public view override returns (bytes memory) { - bytes32[] memory active = keys[operator].getActive(getCaptureTimestamp()); + bytes32[] memory active = _keys[operator].getActive(getCaptureTimestamp()); if (active.length == 0) { return abi.encode(ZERO_BYTES32); } @@ -53,7 +54,7 @@ abstract contract KeyStorage256 is BaseMiddleware { * @return A boolean indicating whether the key was active at the specified timestamp */ function keyWasActiveAt(uint48 timestamp, bytes32 key) public view returns (bool) { - return keys[keyToOperator[key]].wasActiveAt(timestamp, key); + return _keys[_keyToOperator[key]].wasActiveAt(timestamp, key); } /** @@ -65,28 +66,31 @@ abstract contract KeyStorage256 is BaseMiddleware { function _updateKey(address operator, bytes memory key_) internal override { bytes32 key = abi.decode(key_, (bytes32)); - if (keyToOperator[key] != address(0)) { + if (_keyToOperator[key] != address(0)) { revert DuplicateKey(); } - // try to remove disabled keys - keys[operator].prune(Time.timestamp(), SLASHING_WINDOW); - // check if we have reached the max number of disabled keys // this allow us to limit the number times we can change the key - if (keys[operator].length() > MAX_DISABLED_KEYS + 1) { + if (key != ZERO_BYTES32 && _keys[operator].length() > MAX_DISABLED_KEYS + 1) { revert MaxDisabledKeysReached(); } - // get the current active keys - bytes32[] memory activeKeys = keys[operator].getActive(Time.timestamp()); - - // pause the current active key if any - if (activeKeys.length > 0) { - keys[operator].pause(Time.timestamp(), activeKeys[0]); + if (_keys[operator].length() > 0) { + // try to remove disabled keys + bytes32 prevKey = _keys[operator].array[0].value; + if (_keys[operator].array[0].status.checkUnregister(Time.timestamp(), SLASHING_WINDOW)) { + _keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW, prevKey); + delete _keyToOperator[prevKey]; + } else if (_keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { + _keys[operator].pause(Time.timestamp(), prevKey); + } } - // register the new key - keys[operator].register(Time.timestamp(), key); + if (key != ZERO_BYTES32) { + // register the new key + _keys[operator].register(Time.timestamp(), key); + _keyToOperator[key] = operator; + } } } diff --git a/src/key-storage/KeyStorageBytes.sol b/src/key-storage/KeyStorageBytes.sol index addac96..0d61bd2 100644 --- a/src/key-storage/KeyStorageBytes.sol +++ b/src/key-storage/KeyStorageBytes.sol @@ -4,82 +4,93 @@ pragma solidity ^0.8.25; import {BaseManager} from "../managers/BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + abstract contract KeyStorageBytes is BaseManager { - using PauseableEnumerableSet for PauseableEnumerableSet.Inner; + using PauseableEnumerableSet for PauseableEnumerableSet.BytesSet; + using PauseableEnumerableSet for PauseableEnumerableSet.Status; error DuplicateKey(); - error KeyAlreadyEnabled(); + error MaxDisabledKeysReached(); - bytes32 private constant ZERO_BYTES_HASH = keccak256(""); // Constant representing an empty hash + uint256 private constant MAX_DISABLED_KEYS = 1; + bytes private constant ZERO_BYTES = ""; + bytes32 private constant ZERO_BYTES_HASH = keccak256(""); - mapping(address => bytes) public keys; // Mapping from operator addresses to their BLS keys - mapping(address => bytes) public prevKeys; // Mapping from operator addresses to their previous keys - mapping(address => uint48) public keyUpdateTimestamp; // Mapping from operator addresses to the timestamp of the last key update - mapping(bytes => PauseableEnumerableSet.Inner) internal _keyData; // Mapping from keys to their associated data + mapping(address => PauseableEnumerableSet.BytesSet) internal _keys; + mapping(bytes => address) internal _keyToOperator; /** - * @notice Returns the operator address associated with a given BLS key - * @param key The BLS key for which to find the associated operator - * @return The address of the operator linked to the specified BLS key + * @notice Returns the operator address associated with a given key + * @param key The key for which to find the associated operator + * @return The address of the operator linked to the specified key */ function operatorByKey( bytes memory key ) public view returns (address) { - return _keyData[key].getAddress(); + return _keyToOperator[key]; } /** - * @notice Returns the current or previous BLS key for a given operator + * @notice Returns the current or previous key for a given operator * @dev Returns the previous key if the key was updated in the current epoch * @param operator The address of the operator - * @return The BLS key associated with the specified operator + * @return The key associated with the specified operator */ function operatorKey( address operator ) public view returns (bytes memory) { - if (keyUpdateTimestamp[operator] == getCaptureTimestamp()) { - return prevKeys[operator]; + bytes[] memory active = _keys[operator].getActive(getCaptureTimestamp()); + if (active.length == 0) { + return ZERO_BYTES; } - - return keys[operator]; + return active[0]; } /** - * @notice Checks if a given BLS key was active at a specified timestamp + * @notice Checks if a given key was active at a specified timestamp * @param timestamp The timestamp to check - * @param key The BLS key to check - * @return A boolean indicating whether the BLS key was active at the specified timestamp + * @param key The key to check + * @return A boolean indicating whether the key was active at the specified timestamp */ function keyWasActiveAt(uint48 timestamp, bytes memory key) public view returns (bool) { - return _keyData[key].wasActiveAt(timestamp); + return _keys[_keyToOperator[key]].wasActiveAt(timestamp, key); } /** - * @notice Updates the BLS key associated with an operator + * @notice Updates the key associated with an operator * @dev Reverts if the key is already enabled or if another operator is using it - * @param operator The address of the operator whose BLS key is to be updated - * @param key The new BLS key to associate with the operator + * @param operator The address of the operator whose key is to be updated + * @param key The new key to associate with the operator */ function _updateKey(address operator, bytes memory key) internal { - uint48 timestamp = getCaptureTimestamp(); + bytes32 keyHash = keccak256(key); - if (keccak256(keys[operator]) == keccak256(key)) { - revert KeyAlreadyEnabled(); - } - - if (_keyData[key].getAddress() != address(0) && _keyData[key].getAddress() != operator) { + if (_keyToOperator[key] != address(0)) { revert DuplicateKey(); } - if (keccak256(key) != ZERO_BYTES_HASH && _keyData[key].getAddress() == address(0)) { - _keyData[key].set(timestamp, operator); + // check if we have reached the max number of disabled keys + // this allow us to limit the number times we can change the key + if (keyHash != ZERO_BYTES_HASH && _keys[operator].length() > MAX_DISABLED_KEYS + 1) { + revert MaxDisabledKeysReached(); } - if (keyUpdateTimestamp[operator] != timestamp) { - prevKeys[operator] = keys[operator]; - keyUpdateTimestamp[operator] = timestamp; + if (_keys[operator].length() > 0) { + // try to remove disabled keys + bytes memory prevKey = _keys[operator].array[0].value; + if (_keys[operator].array[0].status.checkUnregister(Time.timestamp(), SLASHING_WINDOW)) { + _keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW, prevKey); + delete _keyToOperator[prevKey]; + } else if (_keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { + _keys[operator].pause(Time.timestamp(), prevKey); + } } - keys[operator] = key; + if (keyHash != ZERO_BYTES_HASH) { + // register the new key + _keys[operator].register(Time.timestamp(), key); + _keyToOperator[key] = operator; + } } } diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index c95790f..e398112 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -2,264 +2,215 @@ pragma solidity ^0.8.25; library PauseableEnumerableSet { - using PauseableEnumerableSet for Inner; + using PauseableEnumerableSet for Inner160; using PauseableEnumerableSet for Uint160Set; - using PauseableEnumerableSet for Inner256; - using PauseableEnumerableSet for Uint256Set; - - // Custom error messages - error AlreadyRegistered(); // Thrown when trying to register an already registered value. - error NotRegistered(); // Thrown when trying to modify a value that's not registered. - error AlreadyEnabled(); // Thrown when enabling an already enabled value. - error NotEnabled(); // Thrown when disabling a value that's not enabled. - error Enabled(); // Thrown when trying to disable a value that's enabled. - error ImmutablePeriodNotPassed(); // Thrown when an action is attempted before immutable period passes. - - /* - * Struct for managing a set of Uint160 values. - */ + using PauseableEnumerableSet for InnerBytes32; + using PauseableEnumerableSet for InnerBytes; + using PauseableEnumerableSet for Status; + + error AlreadyRegistered(); + error NotRegistered(); + error AlreadyEnabled(); + error NotEnabled(); + error Enabled(); + error ImmutablePeriodNotPassed(); + + struct Status { + uint48 enabled; + uint48 disabled; + } + + struct Inner160 { + uint160 value; + Status status; + } + + struct InnerBytes32 { + bytes32 value; + Status status; + } + + struct InnerBytes { + bytes value; + Status status; + } + struct Uint160Set { - Inner[] array; - mapping(uint160 => uint256) positions; // Maps value to its index + 1. + Inner160[] array; + mapping(uint160 => uint256) positions; } - /* - * Struct for managing a set of addresses. - */ struct AddressSet { Uint160Set set; } - /* - * Struct for managing value and its active status. - */ - struct Inner { - uint160 value; // The actual value. - uint48 enabledTimestamp; // Timestamp when the value was enabled. - uint48 disabledTimestamp; // Timestamp when the value was disabled. + struct Bytes32Set { + InnerBytes32[] array; + mapping(bytes32 => uint256) positions; + } + + struct BytesSet { + InnerBytes[] array; + mapping(bytes => uint256) positions; + } + + function set(Status storage self, uint48 timestamp) internal { + self.enabled = timestamp; + self.disabled = 0; + } + // Status functions + + function enable(Status storage self, uint48 timestamp, uint48 immutablePeriod) internal { + if (self.enabled != 0) revert AlreadyEnabled(); + if (self.disabled + immutablePeriod > timestamp) revert ImmutablePeriodNotPassed(); + + self.enabled = timestamp; + self.disabled = 0; + } + + function disable(Status storage self, uint48 timestamp) internal { + if (self.disabled != 0) revert NotEnabled(); + self.enabled = 0; + self.disabled = timestamp; + } + + function validateUnregister(Status storage self, uint48 timestamp, uint48 immutablePeriod) internal view { + if (self.enabled != 0 || self.disabled == 0) revert Enabled(); + if (self.disabled + immutablePeriod > timestamp) revert ImmutablePeriodNotPassed(); + } + + function checkUnregister( + Status storage self, + uint48 timestamp, + uint48 immutablePeriod + ) internal view returns (bool) { + return self.enabled != 0 && self.disabled == 0 && self.disabled + immutablePeriod > timestamp; + } + + function wasActiveAt(Status storage self, uint48 timestamp) internal view returns (bool) { + return self.enabled < timestamp && (self.disabled == 0 || self.disabled >= timestamp); + } + + function get( + Inner160 storage self + ) internal view returns (uint160, uint48, uint48) { + return (self.value, self.status.enabled, self.status.disabled); + } + + function get( + InnerBytes32 storage self + ) internal view returns (bytes32, uint48, uint48) { + return (self.value, self.status.enabled, self.status.disabled); + } + + function get( + InnerBytes storage self + ) internal view returns (bytes memory, uint48, uint48) { + return (self.value, self.status.enabled, self.status.disabled); } - /* - * @notice Returns the length of the AddressSet. - * @param self The AddressSet storage. - * @return The number of elements in the set. - */ + // AddressSet functions function length( AddressSet storage self ) internal view returns (uint256) { return self.set.length(); } - /* - * @notice Returns the address and its active period at a given position in the AddressSet. - * @param self The AddressSet storage. - * @param pos The position in the set. - * @return The address, enabled timestamp and disabled timestamp at the position. - */ function at(AddressSet storage self, uint256 pos) internal view returns (address, uint48, uint48) { - (uint160 value, uint48 enabledTimestamp, uint48 disabledTimestamp) = self.set.at(pos); - return (address(value), enabledTimestamp, disabledTimestamp); + (uint160 value, uint48 enabled, uint48 disabled) = self.set.at(pos); + return (address(value), enabled, disabled); } - /* - * @notice Retrieves all active addresses at a given timestamp. - * @param self The AddressSet storage. - * @param timestamp The timestamp to check. - * @return An array of active addresses. - */ function getActive(AddressSet storage self, uint48 timestamp) internal view returns (address[] memory array) { uint160[] memory uint160Array = self.set.getActive(timestamp); - assembly { array := uint160Array } - return array; } - /* - * @notice Checks if a given addr was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param addr The address to check. - * @return A boolean indicating whether the addr was active at the specified timestamp. - */ function wasActiveAt(AddressSet storage self, uint48 timestamp, address addr) internal view returns (bool) { return self.set.wasActiveAt(timestamp, uint160(addr)); } - /* - * @notice Registers a new address at a given timestamp. - * @param self The AddressSet storage. - * @param timestamp The timestamp when the address is added. - * @param addr The address to register. - */ function register(AddressSet storage self, uint48 timestamp, address addr) internal { self.set.register(timestamp, uint160(addr)); } - /* - * @notice Pauses an address at a given timestamp. - * @param self The AddressSet storage. - * @param timestamp The timestamp when the address is paused. - * @param addr The address to pause. - */ function pause(AddressSet storage self, uint48 timestamp, address addr) internal { self.set.pause(timestamp, uint160(addr)); } - /* - * @notice Unpauses an address, re-enabling it after the immutable period. - * @param self The AddressSet storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The required immutable period before unpausing. - * @param addr The address to unpause. - */ function unpause(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address addr) internal { self.set.unpause(timestamp, immutablePeriod, uint160(addr)); } - /* - * @notice Unregisters an address, removing it from the set. - * @param self The AddressSet storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The required immutable period before unregistering. - * @param addr The address to unregister. - */ function unregister(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address addr) internal { self.set.unregister(timestamp, immutablePeriod, uint160(addr)); } - /* - * @notice Checks if an address is contained in the AddressSet. - * @param self The AddressSet storage. - * @param addr The address to check. - * @return True if the address is in the set, false otherwise. - */ function contains(AddressSet storage self, address addr) internal view returns (bool) { return self.set.contains(uint160(addr)); } - /* - * @notice Returns the number of elements in the Uint160Set. - * @param self The Uint160Set storage. - * @return The number of elements. - */ + // Uint160Set functions function length( Uint160Set storage self ) internal view returns (uint256) { return self.array.length; } - /* - * @notice Returns the value and its active period at a given position in the Uint160Set. - * @param self The Uint160Set storage. - * @param pos The position in the set. - * @return The value, enabled timestamp and disabled timestamp at the position. - */ function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint48, uint48) { return self.array[pos].get(); } - /* - * @notice Retrieves all active values at a given timestamp. - * @param self The Uint160Set storage. - * @param timestamp The timestamp to check. - * @return An array of active values. - */ - function getActive(Uint160Set storage self, uint48 timestamp) internal view returns (uint160[] memory) { - uint160[] memory array = new uint160[](self.array.length); - uint256 len = 0; + function getActive(Uint160Set storage self, uint48 timestamp) internal view returns (uint160[] memory array) { + array = new uint160[](self.array.length); + uint256 len; for (uint256 i; i < self.array.length; ++i) { - if (!self.array[i].wasActiveAt(timestamp)) { - continue; + if (self.array[i].status.wasActiveAt(timestamp)) { + array[len++] = self.array[i].value; } - array[len++] = self.array[i].value; } assembly { mstore(array, len) } - return array; } - /* - * @notice Checks if a given value was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param value The value to check. - * @return A boolean indicating whether the value was active at the specified timestamp. - */ function wasActiveAt(Uint160Set storage self, uint48 timestamp, uint160 value) internal view returns (bool) { - if (self.positions[value] == 0) { - return false; - } - - return self.array[self.positions[value] - 1].wasActiveAt(timestamp); + uint256 pos = self.positions[value]; + return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); } - /* - * @notice Registers a new Uint160 value at a given timestamp. - * @param self The Uint160Set storage. - * @param timestamp The timestamp when the value is added. - * @param value The Uint160 value to register. - */ function register(Uint160Set storage self, uint48 timestamp, uint160 value) internal { - if (self.positions[value] != 0) { - revert AlreadyRegistered(); - } + if (self.positions[value] != 0) revert AlreadyRegistered(); - uint256 pos = self.array.length; - Inner storage element = self.array.push(); - element.set(timestamp, value); - self.positions[value] = pos + 1; + Inner160 storage element = self.array.push(); + element.value = value; + element.status.set(timestamp); + self.positions[value] = self.array.length; } - /* - * @notice Pauses a Uint160 value at a given timestamp. - * @param self The Uint160Set storage. - * @param timestamp The timestamp when the value is paused. - * @param value The Uint160 value to pause. - */ function pause(Uint160Set storage self, uint48 timestamp, uint160 value) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } - - self.array[self.positions[value] - 1].disable(timestamp); + if (self.positions[value] == 0) revert NotRegistered(); + self.array[self.positions[value] - 1].status.disable(timestamp); } - /* - * @notice Unpauses a Uint160 value after the immutable period. - * @param self The Uint160Set storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The required immutable period before unpausing. - * @param value The Uint160 value to unpause. - */ function unpause(Uint160Set storage self, uint48 timestamp, uint48 immutablePeriod, uint160 value) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } - - self.array[self.positions[value] - 1].validateUnpause(timestamp, immutablePeriod); - self.array[self.positions[value] - 1].enable(timestamp); + if (self.positions[value] == 0) revert NotRegistered(); + self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } - /* - * @notice Unregisters a Uint160 value from the set. - * @param self The Uint160Set storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The required immutable period before unregistering. - * @param value The Uint160 value to unregister. - */ function unregister(Uint160Set storage self, uint48 timestamp, uint48 immutablePeriod, uint160 value) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } + uint256 pos = self.positions[value]; + if (pos == 0) revert NotRegistered(); + pos--; - uint256 pos = self.positions[value] - 1; - self.array[pos].validateUnregister(timestamp, immutablePeriod); + self.array[pos].status.validateUnregister(timestamp, immutablePeriod); - if (self.array.length == 1 || self.array.length == pos + 1) { + if (self.array.length <= pos + 1) { delete self.positions[value]; self.array.pop(); return; @@ -272,386 +223,144 @@ library PauseableEnumerableSet { self.positions[self.array[pos].value] = pos + 1; } - /* - * @notice Checks if a Uint160 value is contained in the Uint160Set. - * @param self The Uint160Set storage. - * @param value The Uint160 value to check. - * @return True if the value is in the set, false otherwise. - */ function contains(Uint160Set storage self, uint160 value) internal view returns (bool) { return self.positions[value] != 0; } - /* - * @notice Returns the address stored in the Inner struct. - * @param self The Inner struct - * @return The stored Uint160 as address - */ - function getAddress( - Inner storage self - ) internal view returns (address) { - return address(self.value); + // Bytes32Set functions + function length( + Bytes32Set storage self + ) internal view returns (uint256) { + return self.array.length; } - /* - * @notice Returns the value and its active period from the Inner struct. - * @param self The Inner struct. - * @return The value, enabled timestamp and disabled timestamp. - */ - function get( - Inner storage self - ) internal view returns (uint160, uint48, uint48) { - return (self.value, self.enabledTimestamp, self.disabledTimestamp); - } - - /* - * @notice Sets the value and marks it as enabled at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp when the value is set. - * @param value The Uint160 value to store. - */ - function set(Inner storage self, uint48 timestamp, uint160 value) internal { - self.value = value; - self.enabledTimestamp = timestamp; - self.disabledTimestamp = 0; - } - - /* - * @notice Sets the address and marks it as enabled at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp when the address is set. - * @param addr The address to store. - */ - function set(Inner storage self, uint48 timestamp, address addr) internal { - self.value = uint160(addr); - self.enabledTimestamp = timestamp; - self.disabledTimestamp = 0; - } - - /* - * @notice Enables the value at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp when the value is enabled. - */ - function enable(Inner storage self, uint48 timestamp) internal { - if (self.enabledTimestamp != 0) { - revert AlreadyEnabled(); - } - - self.enabledTimestamp = timestamp; - self.disabledTimestamp = 0; + function at(Bytes32Set storage self, uint256 pos) internal view returns (bytes32, uint48, uint48) { + return self.array[pos].get(); } - /* - * @notice Disables the value at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp when the value is disabled. - */ - function disable(Inner storage self, uint48 timestamp) internal { - if (self.disabledTimestamp != 0) { - revert NotEnabled(); - } - if (self.enabledTimestamp == 0) { - revert NotEnabled(); + function getActive(Bytes32Set storage self, uint48 timestamp) internal view returns (bytes32[] memory array) { + array = new bytes32[](self.array.length); + uint256 len; + for (uint256 i; i < self.array.length; ++i) { + if (self.array[i].status.wasActiveAt(timestamp)) { + array[len++] = self.array[i].value; + } } - self.disabledTimestamp = timestamp; - self.enabledTimestamp = 0; - } - - /* - * @notice Checks if the value was active at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp to check. - * @return True if the value was active at the timestamp, false otherwise. - */ - function wasActiveAt(Inner storage self, uint48 timestamp) internal view returns (bool) { - return self.enabledTimestamp < timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); - } - - /* - * @notice Validates whether the value can be unpaused at a given timestamp. - * @param self The Inner struct. - * @param timestamp The current timestamp. - * @param immutablePeriod The immutable period that must pass before unpausing. - */ - function validateUnpause(Inner storage self, uint48 timestamp, uint48 immutablePeriod) internal view { - if (self.disabledTimestamp + immutablePeriod > timestamp) { - revert ImmutablePeriodNotPassed(); + assembly { + mstore(array, len) } + return array; } - /* - * @notice Validates whether the value can be unregistered at a given timestamp. - * @param self The Inner struct. - * @param timestamp The current timestamp. - * @param immutablePeriod The immutable period that must pass before unregistering. - */ - function validateUnregister(Inner storage self, uint48 timestamp, uint48 immutablePeriod) internal view { - if (self.enabledTimestamp != 0 || self.disabledTimestamp == 0) { - revert Enabled(); - } - - if (self.disabledTimestamp + immutablePeriod > timestamp) { - revert ImmutablePeriodNotPassed(); - } + function wasActiveAt(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal view returns (bool) { + uint256 pos = self.positions[value]; + return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); } - /* - * Struct for managing a set of Uint160 values. - */ - struct Uint256Set { - Inner256[] array; - mapping(uint256 => uint256) positions; // Maps value to its index + 1. - } + function register(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal { + if (self.positions[value] != 0) revert AlreadyRegistered(); - /* - * Struct for managing a set of addresses. - */ - struct Bytes32Set { - Uint256Set set; + uint256 pos = self.array.length; + InnerBytes32 storage element = self.array.push(); + element.value = value; + element.status.set(timestamp); + self.positions[value] = pos + 1; } - /* - * Struct for managing value and its active status. - */ - struct Inner256 { - uint256 value; // The actual value. - uint48 enabledTimestamp; // Timestamp when the value was enabled. - uint48 disabledTimestamp; // Timestamp when the value was disabled. + function pause(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal { + if (self.positions[value] == 0) revert NotRegistered(); + self.array[self.positions[value] - 1].status.disable(timestamp); } - /* - * @notice Returns the length of the AddressSet. - * @param self The AddressSet storage. - * @return The number of elements in the set. - */ - function length( - Bytes32Set storage self - ) internal view returns (uint256) { - return self.set.length(); + function unpause(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal { + if (self.positions[value] == 0) revert NotRegistered(); + self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } - /* - * @notice Returns the address and its active period at a given position in the AddressSet. - * @param self The AddressSet storage. - * @param pos The position in the set. - * @return The address, enabled timestamp and disabled timestamp at the position. - */ - function at(Bytes32Set storage self, uint256 pos) internal view returns (bytes32, uint48, uint48) { - (uint256 value, uint48 enabledTimestamp, uint48 disabledTimestamp) = self.set.at(pos); - return (bytes32(value), enabledTimestamp, disabledTimestamp); - } + function unregister(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal { + uint256 pos = self.positions[value]; + if (pos == 0) revert NotRegistered(); + pos--; - /* - * @notice Retrieves all active addresses at a given timestamp. - * @param self The AddressSet storage. - * @param timestamp The timestamp to check. - * @return An array of active addresses. - */ - function getActive(Bytes32Set storage self, uint48 timestamp) internal view returns (bytes32[] memory array) { - uint256[] memory uint256Array = self.set.getActive(timestamp); + self.array[pos].status.validateUnregister(timestamp, immutablePeriod); - assembly { - array := uint256Array + if (self.array.length <= pos + 1) { + delete self.positions[value]; + self.array.pop(); + return; } - return array; + self.array[pos] = self.array[self.array.length - 1]; + self.array.pop(); + + delete self.positions[value]; + self.positions[self.array[pos].value] = pos + 1; + } + + function contains(Bytes32Set storage self, bytes32 value) internal view returns (bool) { + return self.positions[value] != 0; } - /* - * @notice Checks if a given addr was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param addr The address to check. - * @return A boolean indicating whether the addr was active at the specified timestamp. - */ - function wasActiveAt(Bytes32Set storage self, uint48 timestamp, bytes32 key) internal view returns (bool) { - return self.set.wasActiveAt(timestamp, uint256(key)); - } - - /* - * @notice Registers a new address at a given timestamp. - * @param self The AddressSet storage. - * @param timestamp The timestamp when the address is added. - * @param addr The address to register. - */ - function register(Bytes32Set storage self, uint48 timestamp, bytes32 key) internal { - self.set.register(timestamp, uint256(key)); - } - - /* - * @notice Pauses an address at a given timestamp. - * @param self The AddressSet storage. - * @param timestamp The timestamp when the address is paused. - * @param addr The address to pause. - */ - function pause(Bytes32Set storage self, uint48 timestamp, bytes32 key) internal { - self.set.pause(timestamp, uint256(key)); - } - - /* - * @notice Unpauses an address, re-enabling it after the immutable period. - * @param self The AddressSet storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The required immutable period before unpausing. - * @param addr The address to unpause. - */ - function unpause(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 key) internal { - self.set.unpause(timestamp, immutablePeriod, uint256(key)); - } - - /* - * @notice Unregisters an address, removing it from the set. - * @param self The AddressSet storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The required immutable period before unregistering. - * @param addr The address to unregister. - */ - function unregister(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 key) internal { - self.set.unregister(timestamp, immutablePeriod, uint256(key)); - } - - /* - * @notice Prunes the set by removing disabled values that have passed the immutable period. - * @param self The AddressSet storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The immutable period that must pass before unregistering. - */ - function prune(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod) internal { - self.set.prune(timestamp, immutablePeriod); - } - - /* - * @notice Checks if an address is contained in the AddressSet. - * @param self The AddressSet storage. - * @param addr The address to check. - * @return True if the address is in the set, false otherwise. - */ - function contains(Bytes32Set storage self, bytes32 key) internal view returns (bool) { - return self.set.contains(uint256(key)); - } - - /* - * @notice Returns the number of elements in the Uint160Set. - * @param self The Uint160Set storage. - * @return The number of elements. - */ + // BytesSet functions function length( - Uint256Set storage self + BytesSet storage self ) internal view returns (uint256) { return self.array.length; } - /* - * @notice Returns the value and its active period at a given position in the Uint160Set. - * @param self The Uint160Set storage. - * @param pos The position in the set. - * @return The value, enabled timestamp and disabled timestamp at the position. - */ - function at(Uint256Set storage self, uint256 pos) internal view returns (uint256, uint48, uint48) { + function at(BytesSet storage self, uint256 pos) internal view returns (bytes memory, uint48, uint48) { return self.array[pos].get(); } - /* - * @notice Retrieves all active values at a given timestamp. - * @param self The Uint160Set storage. - * @param timestamp The timestamp to check. - * @return An array of active values. - */ - function getActive(Uint256Set storage self, uint48 timestamp) internal view returns (uint256[] memory) { - uint256[] memory array = new uint256[](self.array.length); - uint256 len = 0; + function getActive(BytesSet storage self, uint48 timestamp) internal view returns (bytes[] memory array) { + array = new bytes[](self.array.length); + uint256 len; for (uint256 i; i < self.array.length; ++i) { - if (!self.array[i].wasActiveAt(timestamp)) { - continue; + if (self.array[i].status.wasActiveAt(timestamp)) { + array[len++] = self.array[i].value; } - array[len++] = self.array[i].value; } assembly { mstore(array, len) } - return array; } - /* - * @notice Checks if a given value was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param value The value to check. - * @return A boolean indicating whether the value was active at the specified timestamp. - */ - function wasActiveAt(Uint256Set storage self, uint48 timestamp, uint256 value) internal view returns (bool) { - if (self.positions[value] == 0) { - return false; - } - - return self.array[self.positions[value] - 1].wasActiveAt(timestamp); + function wasActiveAt(BytesSet storage self, uint48 timestamp, bytes memory value) internal view returns (bool) { + uint256 pos = self.positions[value]; + return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); } - /* - * @notice Registers a new Uint160 value at a given timestamp. - * @param self The Uint160Set storage. - * @param timestamp The timestamp when the value is added. - * @param value The Uint160 value to register. - */ - function register(Uint256Set storage self, uint48 timestamp, uint256 value) internal { - if (self.positions[value] != 0) { - revert AlreadyRegistered(); - } + function register(BytesSet storage self, uint48 timestamp, bytes memory value) internal { + if (self.positions[value] != 0) revert AlreadyRegistered(); uint256 pos = self.array.length; - Inner256 storage element = self.array.push(); - element.set(timestamp, value); + InnerBytes storage element = self.array.push(); + element.value = value; + element.status.set(timestamp); self.positions[value] = pos + 1; } - /* - * @notice Pauses a Uint160 value at a given timestamp. - * @param self The Uint160Set storage. - * @param timestamp The timestamp when the value is paused. - * @param value The Uint160 value to pause. - */ - function pause(Uint256Set storage self, uint48 timestamp, uint256 value) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } - - self.array[self.positions[value] - 1].disable(timestamp); + function pause(BytesSet storage self, uint48 timestamp, bytes memory value) internal { + if (self.positions[value] == 0) revert NotRegistered(); + self.array[self.positions[value] - 1].status.disable(timestamp); } - /* - * @notice Unpauses a Uint160 value after the immutable period. - * @param self The Uint160Set storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The required immutable period before unpausing. - * @param value The Uint160 value to unpause. - */ - function unpause(Uint256Set storage self, uint48 timestamp, uint48 immutablePeriod, uint256 value) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } - - self.array[self.positions[value] - 1].validateUnpause(timestamp, immutablePeriod); - self.array[self.positions[value] - 1].enable(timestamp); + function unpause(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal { + if (self.positions[value] == 0) revert NotRegistered(); + self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } - /* - * @notice Unregisters a Uint160 value from the set. - * @param self The Uint160Set storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The required immutable period before unregistering. - * @param value The Uint160 value to unregister. - */ - function unregister(Uint256Set storage self, uint48 timestamp, uint48 immutablePeriod, uint256 value) internal { - if (self.positions[value] == 0) { - revert NotRegistered(); - } + function unregister(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal { + uint256 pos = self.positions[value]; + if (pos == 0) revert NotRegistered(); + pos--; - uint256 pos = self.positions[value] - 1; - self.array[pos].validateUnregister(timestamp, immutablePeriod); + self.array[pos].status.validateUnregister(timestamp, immutablePeriod); - if (self.array.length == 1 || self.array.length == pos + 1) { + if (self.array.length <= pos + 1) { delete self.positions[value]; self.array.pop(); return; @@ -664,168 +373,7 @@ library PauseableEnumerableSet { self.positions[self.array[pos].value] = pos + 1; } - /* - * @notice Prunes the set by removing disabled values that have passed the immutable period. - * @param self The Uint160Set storage. - * @param timestamp The current timestamp. - * @param immutablePeriod The immutable period that must pass before unregistering. - */ - function prune(Uint256Set storage self, uint48 timestamp, uint48 immutablePeriod) internal { - // Start from end to avoid shifting elements during unregister - for (uint256 i = self.array.length; i > 0;) { - unchecked { - --i; - } - - if (!self.array[i].checkUnregister(timestamp, immutablePeriod)) { - continue; - } - - self.unregister(timestamp, immutablePeriod, self.array[i].value); - } - } - - /* - * @notice Checks if a Uint160 value is contained in the Uint160Set. - * @param self The Uint160Set storage. - * @param value The Uint160 value to check. - * @return True if the value is in the set, false otherwise. - */ - function contains(Uint256Set storage self, uint256 value) internal view returns (bool) { + function contains(BytesSet storage self, bytes memory value) internal view returns (bool) { return self.positions[value] != 0; } - - /* - * @notice Returns the address stored in the Inner struct. - * @param self The Inner struct - * @return The stored Uint160 as address - */ - function getBytes32( - Inner256 storage self - ) internal view returns (bytes32) { - return bytes32(self.value); - } - - /* - * @notice Returns the value and its active period from the Inner struct. - * @param self The Inner struct. - * @return The value, enabled timestamp and disabled timestamp. - */ - function get( - Inner256 storage self - ) internal view returns (uint256, uint48, uint48) { - return (self.value, self.enabledTimestamp, self.disabledTimestamp); - } - - /* - * @notice Sets the value and marks it as enabled at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp when the value is set. - * @param value The Uint160 value to store. - */ - function set(Inner256 storage self, uint48 timestamp, uint256 value) internal { - self.value = value; - self.enabledTimestamp = timestamp; - self.disabledTimestamp = 0; - } - - /* - * @notice Sets the address and marks it as enabled at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp when the address is set. - * @param addr The address to store. - */ - function set(Inner256 storage self, uint48 timestamp, bytes32 key) internal { - self.value = uint256(key); - self.enabledTimestamp = timestamp; - self.disabledTimestamp = 0; - } - - /* - * @notice Enables the value at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp when the value is enabled. - */ - function enable(Inner256 storage self, uint48 timestamp) internal { - if (self.enabledTimestamp != 0) { - revert AlreadyEnabled(); - } - - self.enabledTimestamp = timestamp; - self.disabledTimestamp = 0; - } - - /* - * @notice Disables the value at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp when the value is disabled. - */ - function disable(Inner256 storage self, uint48 timestamp) internal { - if (self.disabledTimestamp != 0) { - revert NotEnabled(); - } - self.enabledTimestamp = 0; - self.disabledTimestamp = timestamp; - } - - /* - * @notice Checks if the value was active at a given timestamp. - * @param self The Inner struct. - * @param timestamp The timestamp to check. - * @return True if the value was active at the timestamp, false otherwise. - */ - function wasActiveAt(Inner256 storage self, uint48 timestamp) internal view returns (bool) { - return self.enabledTimestamp < timestamp && (self.disabledTimestamp == 0 || self.disabledTimestamp >= timestamp); - } - - /* - * @notice Validates whether the value can be unpaused at a given timestamp. - * @param self The Inner struct. - * @param timestamp The current timestamp. - * @param immutablePeriod The immutable period that must pass before unpausing. - */ - function validateUnpause(Inner256 storage self, uint48 timestamp, uint48 immutablePeriod) internal view { - if (self.disabledTimestamp + immutablePeriod > timestamp) { - revert ImmutablePeriodNotPassed(); - } - } - - /* - * @notice Validates whether the value can be unregistered at a given timestamp. - * @param self The Inner struct. - * @param timestamp The current timestamp. - * @param immutablePeriod The immutable period that must pass before unregistering. - */ - function validateUnregister(Inner256 storage self, uint48 timestamp, uint48 immutablePeriod) internal view { - if (self.enabledTimestamp != 0 || self.disabledTimestamp == 0) { - revert Enabled(); - } - - if (self.disabledTimestamp + immutablePeriod > timestamp) { - revert ImmutablePeriodNotPassed(); - } - } - - /* - * @notice Checks if the value can be unregistered at a given timestamp. - * @param self The Inner struct. - * @param timestamp The current timestamp. - * @param immutablePeriod The immutable period that must pass before unregistering. - * @return True if the value can be unregistered, false otherwise. - */ - function checkUnregister( - Inner256 storage self, - uint48 timestamp, - uint48 immutablePeriod - ) internal view returns (bool) { - if (self.enabledTimestamp != 0 || self.disabledTimestamp == 0) { - return false; - } - - if (self.disabledTimestamp + immutablePeriod > timestamp) { - return false; - } - - return true; - } } diff --git a/src/managers/AccessManager.sol b/src/managers/AccessManager.sol index dc92009..dc3c89c 100644 --- a/src/managers/AccessManager.sol +++ b/src/managers/AccessManager.sol @@ -10,5 +10,5 @@ abstract contract AccessManager { /** * @notice Checks if the user has access to the given selector. */ - function _checkAccess() internal virtual {} + function _checkAccess() internal virtual; } diff --git a/src/managers/BaseManager.sol b/src/managers/BaseManager.sol index cd4368c..4728e4f 100644 --- a/src/managers/BaseManager.sol +++ b/src/managers/BaseManager.sol @@ -2,10 +2,9 @@ pragma solidity ^0.8.25; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -abstract contract BaseManager is Initializable, OwnableUpgradeable { +abstract contract BaseManager is Initializable { address public NETWORK; // Address of the network uint48 public SLASHING_WINDOW; // Duration of the slashing window address public VAULT_REGISTRY; // Address of the vault registry @@ -29,11 +28,8 @@ abstract contract BaseManager is Initializable, OwnableUpgradeable { uint48 slashingWindow, address vaultRegistry, address operatorRegistry, - address operatorNetOptIn, - address owner + address operatorNetOptIn ) public virtual initializer { - __Ownable_init(owner); - NETWORK = network; SLASHING_WINDOW = slashingWindow; VAULT_REGISTRY = vaultRegistry; diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index a6ab904..bcb5956 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -15,32 +15,12 @@ import {AccessManager} from "../managers/AccessManager.sol"; abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager { using Subnetwork for address; - - /* - * @notice Constructor for initializing the BaseMiddleware contract. - * @param network The address of the network. - * @param operatorRegistry The address of the operator registry. - * @param vaultRegistry The address of the vault registry. - * @param operatorNetOptin The address of the operator network opt-in service. - * @param epochDuration The duration of each epoch. - * @param slashingWindow The duration of the slashing window - */ - constructor( - address network, - address operatorRegistry, - address vaultRegistry, - address operatorNetOptin, - uint48 slashingWindow, - address owner - ) { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, owner); - } - /** * @notice Updates the key associated with an operator * @param operator The address of the operator * @param key The key to update */ + function _updateKey(address operator, bytes memory key) internal virtual; /** diff --git a/src/middleware/extensions/SelfRegisterOperators.sol b/src/middleware/extensions/SelfRegisterOperators.sol deleted file mode 100644 index 17ef236..0000000 --- a/src/middleware/extensions/SelfRegisterOperators.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {BaseMiddleware} from "../BaseMiddleware.sol"; -import {BaseSig} from "./sigs/BaseSig.sol"; - -abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig { - error InvalidSignature(); - - function registerOperator(bytes memory key, address vault, bytes memory signature) public { - _beforeRegisterOperator(msg.sender, key, vault); - _verifyKey(key, signature); - _registerOperator(msg.sender); - _updateKey(msg.sender, key); - _registerOperatorVault(msg.sender, vault); - } - - function unregisterOperator() public { - _beforeUnregisterOperator(msg.sender); - _unregisterOperator(msg.sender); - } - - function pauseOperator() public { - _beforePauseOperator(msg.sender); - _pauseOperator(msg.sender); - } - - function unpauseOperator() public { - _beforeUnpauseOperator(msg.sender); - _unpauseOperator(msg.sender); - } - - function updateOperatorKey(bytes memory key, bytes memory signature) public { - _beforeUpdateOperatorKey(msg.sender, key); - _verifyKey(key, signature); - _updateKey(msg.sender, key); - } - - function _verifyKey(bytes memory key, bytes memory signature) internal view { - if (!_verifyKeySignature(key, signature)) { - revert InvalidSignature(); - } - } - - function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} - - function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} - function _beforeUnregisterOperator( - address operator - ) internal virtual {} - function _beforePauseOperator( - address operator - ) internal virtual {} - function _beforeUnpauseOperator( - address operator - ) internal virtual {} -} diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/middleware/extensions/access-managers/NoAccessManager.sol new file mode 100644 index 0000000..534453b --- /dev/null +++ b/src/middleware/extensions/access-managers/NoAccessManager.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseMiddleware} from "../../BaseMiddleware.sol"; + +abstract contract NoAccessManager is BaseMiddleware { + error NoAccess(); + function _checkAccess() internal pure override { + revert NoAccess(); + } +} diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/middleware/extensions/access-managers/OwnableAccessManager.sol new file mode 100644 index 0000000..44ef7e4 --- /dev/null +++ b/src/middleware/extensions/access-managers/OwnableAccessManager.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseMiddleware} from "../../BaseMiddleware.sol"; + +abstract contract OwnableAccessManager is BaseMiddleware { + address public owner; + + error OnlyOwnerCanCall(address sender); + error InvalidOwner(address owner); + function __OwnableAccessManaged_init( + address _owner + ) internal onlyInitializing { + owner = _owner; + } + + function _checkAccess() internal override { + if (msg.sender != owner) { + revert OnlyOwnerCanCall(msg.sender); + } + } + + function setOwner(address _owner) public checkAccess { + if (_owner == address(0)) { + revert InvalidOwner(address(0)); + } + owner = _owner; + } +} diff --git a/src/middleware/extensions/OzAccessManaged.sol b/src/middleware/extensions/access-managers/OzAccessManaged.sol similarity index 77% rename from src/middleware/extensions/OzAccessManaged.sol rename to src/middleware/extensions/access-managers/OzAccessManaged.sol index 992f913..9c21ad1 100644 --- a/src/middleware/extensions/OzAccessManaged.sol +++ b/src/middleware/extensions/access-managers/OzAccessManaged.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.25; import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; -import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseMiddleware} from "../../BaseMiddleware.sol"; abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { - constructor( + function __OzAccessManaged_init( address authority - ) { + ) internal onlyInitializing { __AccessManaged_init(authority); } diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol new file mode 100644 index 0000000..d9dda91 --- /dev/null +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -0,0 +1,38 @@ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; + +abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { + function forcePauseOperator( + address operator + ) public checkAccess { + _beforePauseOperator(operator); + _pauseOperator(operator); + } + + function forceUnpauseOperator( + address operator + ) public checkAccess { + _beforeUnpauseOperator(operator); + _unpauseOperator(operator); + } + + function forcePauseOperatorVault( + address operator, + address vault + ) public checkAccess { + _beforePauseOperatorVault(operator, vault); + _pauseOperatorVault(operator, vault); + } + + function forceUnpauseOperatorVault( + address operator, + address vault + ) public checkAccess { + _beforeUnpauseOperatorVault(operator, vault); + _unpauseOperatorVault(operator, vault); + } + +} diff --git a/src/middleware/extensions/Operators.sol b/src/middleware/extensions/operators/Operators.sol similarity index 58% rename from src/middleware/extensions/Operators.sol rename to src/middleware/extensions/operators/Operators.sol index 7e0ed3a..3d8261f 100644 --- a/src/middleware/extensions/Operators.sol +++ b/src/middleware/extensions/operators/Operators.sol @@ -1,15 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseMiddleware} from "../../BaseMiddleware.sol"; abstract contract Operators is BaseMiddleware { - function registerOperatorVault(address operator, address vault) public checkAccess { - require(isOperatorRegistered(operator), "Operator not registered"); - _beforeRegisterOperatorVault(operator, vault); - _registerOperatorVault(operator, vault); - } - function registerOperator(address operator, bytes memory key, address vault) public checkAccess { _beforeRegisterOperator(operator, key, vault); _registerOperator(operator); @@ -40,8 +34,35 @@ abstract contract Operators is BaseMiddleware { _unpauseOperator(operator); } + function updateOperatorKey(address operator, bytes memory key) public checkAccess { + _beforeUpdateOperatorKey(operator, key); + _updateKey(operator, key); + } + + function registerOperatorVault(address operator, address vault) public checkAccess { + require(isOperatorRegistered(operator), "Operator not registered"); + _beforeRegisterOperatorVault(operator, vault); + _registerOperatorVault(operator, vault); + } + + function unregisterOperatorVault(address operator, address vault) public checkAccess { + _beforeUnregisterOperatorVault(operator, vault); + _unregisterOperatorVault(operator, vault); + } + + function pauseOperatorVault(address operator, address vault) public checkAccess { + _beforePauseOperatorVault(operator, vault); + _pauseOperatorVault(operator, vault); + } + + function unpauseOperatorVault(address operator, address vault) public checkAccess { + _beforeUnpauseOperatorVault(operator, vault); + _unpauseOperatorVault(operator, vault); + } + + function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} + function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} - function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} function _beforeUnregisterOperator( address operator ) internal virtual {} @@ -51,4 +72,9 @@ abstract contract Operators is BaseMiddleware { function _beforeUnpauseOperator( address operator ) internal virtual {} + + function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} + function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual {} + function _beforePauseOperatorVault(address operator, address vault) internal virtual {} + function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual {} } diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol new file mode 100644 index 0000000..048fe96 --- /dev/null +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {BaseSig} from "../sigs/BaseSig.sol"; +import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; + +abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgradeable { + error InvalidSignature(); + + // EIP-712 TypeHash constants + bytes32 private constant REGISTER_OPERATOR_TYPEHASH = + keccak256("RegisterOperator(address operator,bytes key,address vault,uint256 nonce)"); + bytes32 private constant UNREGISTER_OPERATOR_TYPEHASH = + keccak256("UnregisterOperator(address operator,uint256 nonce)"); + bytes32 private constant PAUSE_OPERATOR_TYPEHASH = keccak256("PauseOperator(address operator,uint256 nonce)"); + bytes32 private constant UNPAUSE_OPERATOR_TYPEHASH = keccak256("UnpauseOperator(address operator,uint256 nonce)"); + bytes32 private constant UPDATE_OPERATOR_KEY_TYPEHASH = + keccak256("UpdateOperatorKey(address operator,bytes key,uint256 nonce)"); + bytes32 private constant REGISTER_OPERATOR_VAULT_TYPEHASH = + keccak256("RegisterOperatorVault(address operator,address vault,uint256 nonce)"); + bytes32 private constant UNREGISTER_OPERATOR_VAULT_TYPEHASH = + keccak256("UnregisterOperatorVault(address operator,address vault,uint256 nonce)"); + bytes32 private constant PAUSE_OPERATOR_VAULT_TYPEHASH = + keccak256("PauseOperatorVault(address operator,address vault,uint256 nonce)"); + bytes32 private constant UNPAUSE_OPERATOR_VAULT_TYPEHASH = + keccak256("UnpauseOperatorVault(address operator,address vault,uint256 nonce)"); + + mapping(address => uint256) public nonces; + + function __SelfRegisterOperators_init( + string memory name + ) internal onlyInitializing { + __EIP712_init(name, "1.0"); + } + + function registerOperator(bytes memory key, address vault, bytes memory signature) public { + _verifyKey(msg.sender, key, signature); + _beforeRegisterOperator(msg.sender, key, vault); + _registerOperator(msg.sender); + _beforeUpdateOperatorKey(msg.sender, key); + _updateKey(msg.sender, key); + if (vault != address(0)) { + _beforeRegisterOperatorVault(msg.sender, vault); + _registerOperatorVault(msg.sender, vault); + } + } + + function registerOperator( + address operator, + bytes memory key, + address vault, + bytes memory signature, + bytes memory keySignature + ) public { + _verifyEIP712( + operator, + keccak256(abi.encode(REGISTER_OPERATOR_TYPEHASH, operator, keccak256(key), vault, nonces[operator]++)), + signature + ); + _verifyKey(operator, key, keySignature); + _beforeRegisterOperator(operator, key, vault); + _registerOperator(operator); + _beforeUpdateOperatorKey(operator, key); + _updateKey(operator, key); + if (vault != address(0)) { + _beforeRegisterOperatorVault(operator, vault); + _registerOperatorVault(operator, vault); + } + } + + function unregisterOperator() public { + _beforeUnregisterOperator(msg.sender); + _unregisterOperator(msg.sender); + } + + function unregisterOperator(address operator, bytes memory signature) public { + _beforeUnregisterOperator(operator); + _verifyEIP712( + operator, keccak256(abi.encode(UNREGISTER_OPERATOR_TYPEHASH, operator, nonces[operator]++)), signature + ); + _unregisterOperator(operator); + } + + function pauseOperator() public { + _beforePauseOperator(msg.sender); + _pauseOperator(msg.sender); + } + + function pauseOperator(address operator, bytes memory signature) public { + _beforePauseOperator(operator); + _verifyEIP712(operator, keccak256(abi.encode(PAUSE_OPERATOR_TYPEHASH, operator, nonces[operator]++)), signature); + _pauseOperator(operator); + } + + function unpauseOperator() public { + _beforeUnpauseOperator(msg.sender); + _unpauseOperator(msg.sender); + } + + function unpauseOperator(address operator, bytes memory signature) public { + _beforeUnpauseOperator(operator); + _verifyEIP712( + operator, keccak256(abi.encode(UNPAUSE_OPERATOR_TYPEHASH, operator, nonces[operator]++)), signature + ); + _unpauseOperator(operator); + } + + function updateOperatorKey(bytes memory key, bytes memory signature) public { + _verifyKey(msg.sender, key, signature); + _beforeUpdateOperatorKey(msg.sender, key); + _updateKey(msg.sender, key); + } + + function updateOperatorKey( + address operator, + bytes memory key, + bytes memory signature, + bytes memory keySignature + ) public { + _verifyEIP712( + operator, + keccak256(abi.encode(UPDATE_OPERATOR_KEY_TYPEHASH, operator, keccak256(key), nonces[operator]++)), + signature + ); + _verifyKey(operator, key, keySignature); + _beforeUpdateOperatorKey(operator, key); + _updateKey(operator, key); + } + + function registerOperatorVault( + address vault + ) public { + require(isOperatorRegistered(msg.sender), "Operator not registered"); + _beforeRegisterOperatorVault(msg.sender, vault); + _registerOperatorVault(msg.sender, vault); + } + + function registerOperatorVault(address operator, address vault, bytes memory signature) public { + require(isOperatorRegistered(operator), "Operator not registered"); + _beforeRegisterOperatorVault(operator, vault); + _verifyEIP712( + operator, + keccak256(abi.encode(REGISTER_OPERATOR_VAULT_TYPEHASH, operator, vault, nonces[operator]++)), + signature + ); + _registerOperatorVault(operator, vault); + } + + function unregisterOperatorVault( + address vault + ) public { + _beforeUnregisterOperatorVault(msg.sender, vault); + _unregisterOperatorVault(msg.sender, vault); + } + + function unregisterOperatorVault(address operator, address vault, bytes memory signature) public { + _beforeUnregisterOperatorVault(operator, vault); + _verifyEIP712( + operator, + keccak256(abi.encode(UNREGISTER_OPERATOR_VAULT_TYPEHASH, operator, vault, nonces[operator]++)), + signature + ); + _unregisterOperatorVault(operator, vault); + } + + function pauseOperatorVault( + address vault + ) public { + _beforePauseOperatorVault(msg.sender, vault); + _pauseOperatorVault(msg.sender, vault); + } + + function pauseOperatorVault(address operator, address vault, bytes memory signature) public { + _beforePauseOperatorVault(operator, vault); + _verifyEIP712( + operator, + keccak256(abi.encode(PAUSE_OPERATOR_VAULT_TYPEHASH, operator, vault, nonces[operator]++)), + signature + ); + _pauseOperatorVault(operator, vault); + } + + function unpauseOperatorVault( + address vault + ) public { + _beforeUnpauseOperatorVault(msg.sender, vault); + _unpauseOperatorVault(msg.sender, vault); + } + + function unpauseOperatorVault(address operator, address vault, bytes memory signature) public { + _beforeUnpauseOperatorVault(operator, vault); + _verifyEIP712( + operator, + keccak256(abi.encode(UNPAUSE_OPERATOR_VAULT_TYPEHASH, operator, vault, nonces[operator]++)), + signature + ); + _unpauseOperatorVault(operator, vault); + } + + function _verifyKey(address operator, bytes memory key, bytes memory signature) internal view { + if (key.length != 0 && !_verifyKeySignature(operator, key, signature)) { + revert InvalidSignature(); + } + } + + function _verifyEIP712(address operator, bytes32 structHash, bytes memory signature) internal view { + if (!SignatureChecker.isValidSignatureNow(operator, _hashTypedDataV4(structHash), signature)) { + revert InvalidSignature(); + } + } + + function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} + function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} + function _beforeUnregisterOperator( + address operator + ) internal virtual {} + function _beforePauseOperator( + address operator + ) internal virtual {} + function _beforeUnpauseOperator( + address operator + ) internal virtual {} + function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} + function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual {} + function _beforePauseOperatorVault(address operator, address vault) internal virtual {} + function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual {} +} diff --git a/src/middleware/extensions/sigs/BaseSig.sol b/src/middleware/extensions/sigs/BaseSig.sol index 71024d8..70f2e26 100644 --- a/src/middleware/extensions/sigs/BaseSig.sol +++ b/src/middleware/extensions/sigs/BaseSig.sol @@ -4,9 +4,14 @@ pragma solidity ^0.8.25; abstract contract BaseSig { /** * @notice Verifies the signature of a key + * @param operator The operator to verify * @param key_ The key to verify * @param signature The signature to verify * @return True if the signature is valid, false otherwise */ - function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view virtual returns (bool); + function _verifyKeySignature( + address operator, + bytes memory key_, + bytes memory signature + ) internal view virtual returns (bool); } diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index 162840b..07967c0 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -7,9 +7,13 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; abstract contract ECDSASig is BaseSig { using ECDSA for bytes32; - function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view override returns (bool) { + function _verifyKeySignature( + address operator, + bytes memory key_, + bytes memory signature + ) internal pure override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); - bytes32 hash = keccak256(abi.encodePacked(msg.sender, key)); + bytes32 hash = keccak256(abi.encodePacked(operator, key)); address signer = recover(hash, signature); address keyAddress = address(uint160(uint256(key))); return signer == keyAddress && signer != address(0); diff --git a/src/middleware/extensions/sigs/Ed25519Sig.sol b/src/middleware/extensions/sigs/Ed25519Sig.sol index d4a834e..1b138f0 100644 --- a/src/middleware/extensions/sigs/Ed25519Sig.sol +++ b/src/middleware/extensions/sigs/Ed25519Sig.sol @@ -5,7 +5,11 @@ import {Ed25519} from "../../../libraries/Ed25519.sol"; import {BaseSig} from "./BaseSig.sol"; abstract contract Ed25519Sig is BaseSig { - function _verifyKeySignature(bytes memory key_, bytes memory signature) internal view override returns (bool) { + function _verifyKeySignature( + address operator, + bytes memory key_, + bytes memory signature + ) internal pure override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); bytes32 r; bytes32 s; @@ -13,7 +17,7 @@ abstract contract Ed25519Sig is BaseSig { r := mload(add(signature, 32)) s := mload(add(signature, 64)) } - bytes32 message = keccak256(abi.encodePacked(msg.sender, key)); + bytes32 message = keccak256(abi.encodePacked(operator, key)); return Ed25519.check(key, r, s, message, bytes9(0)); } } diff --git a/test/OperatorsRegistration.t.sol b/test/OperatorsRegistration.t.sol index a3de8b0..275e24f 100644 --- a/test/OperatorsRegistration.t.sol +++ b/test/OperatorsRegistration.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.25; import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; import {SimplePosMiddleware} from "../src/examples/simple-pos-network/SimplePosMiddleware.sol"; -import {ExtendedSimplePosMiddleware} from "./mocks/ExtendedSimplePosMiddleware.sol"; //import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; //import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; // import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; @@ -21,7 +20,7 @@ contract OperatorsRegistrationTest is POCBaseTest { address network = address(0x123); - ExtendedSimplePosMiddleware internal middleware; + SimplePosMiddleware internal middleware; uint48 internal epochDuration = 600; // 10 minutes uint48 internal slashingWindow = 1200; // 20 minutes @@ -37,7 +36,7 @@ contract OperatorsRegistrationTest is POCBaseTest { _deposit(vault3, alice, 1000 ether); // Initialize middleware contract - middleware = new ExtendedSimplePosMiddleware( + middleware = new SimplePosMiddleware( address(network), address(operatorRegistry), address(vaultFactory), diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol index ebdb3cf..afa7373 100644 --- a/test/SigTests.t.sol +++ b/test/SigTests.t.sol @@ -37,7 +37,6 @@ contract SigTests is POCBaseTest { address(operatorRegistry), address(vaultFactory), address(operatorNetworkOptInService), - owner, 1200 // slashing window ); @@ -46,7 +45,6 @@ contract SigTests is POCBaseTest { address(operatorRegistry), address(vaultFactory), address(operatorNetworkOptInService), - owner, 1200 // slashing window ); @@ -123,11 +121,11 @@ contract SigTests is POCBaseTest { assertTrue(middleware.isOperatorRegistered(operator)); assertEq(abi.decode(middleware.operatorKey(operator), (bytes32)), bytes32(0)); - vm.warp(block.timestamp + 2); + vm.warp(vm.getBlockTimestamp() + 100); assertEq(abi.decode(middleware.operatorKey(operator), (bytes32)), operatorPublicKey); } - function testSelfRegisterOperatorInvalidSignature() public { + function testSelxfRegisterOperatorInvalidSignature() public { // Create registration message with wrong key bytes32 wrongKey = bytes32(uint256(1)); bytes32 messageHash = keccak256(abi.encodePacked(operator, wrongKey)); @@ -141,7 +139,7 @@ contract SigTests is POCBaseTest { middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); } - function testSelfRegisterOperatorWrongSender() public { + function testSelfxRegisterOperatorWrongSender() public { // Create valid registration message bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); // Sign message with operator's key @@ -154,7 +152,7 @@ contract SigTests is POCBaseTest { middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); } - function testSelfRegisterOperatorAlreadyRegistered() public { + function testSelxfRegisterOperatorAlreadyRegistered() public { // Create registration message bytes32 messageHash = keccak256(abi.encodePacked(operator, operatorPublicKey)); // Sign message diff --git a/test/mocks/ExtendedSimplePosMiddleware.sol b/test/mocks/ExtendedSimplePosMiddleware.sol deleted file mode 100644 index 2856854..0000000 --- a/test/mocks/ExtendedSimplePosMiddleware.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {SimplePosMiddleware} from "../../src/examples/simple-pos-network/SimplePosMiddleware.sol"; - -contract ExtendedSimplePosMiddleware is SimplePosMiddleware { - constructor( - address network, - address operatorRegistry, - address vaultRegistry, - address operatorNetOptin, - address owner, - uint48 epochDuration, - uint48 slashingWindow - ) - SimplePosMiddleware( - network, - operatorRegistry, - vaultRegistry, - operatorNetOptin, - owner, - epochDuration, - slashingWindow - ) - {} -} From c404675c1b608d190c10065acbae09c5113a46e1 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 20 Nov 2024 21:06:07 +0700 Subject: [PATCH 055/115] refactor: add checkunregister function for bytes32/bytes sets --- src/key-storage/KeyStorage256.sol | 2 +- src/key-storage/KeyStorageBytes.sol | 2 +- src/libraries/PauseableEnumerableSet.sol | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/key-storage/KeyStorage256.sol b/src/key-storage/KeyStorage256.sol index a106a01..e9fabec 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/key-storage/KeyStorage256.sol @@ -79,7 +79,7 @@ abstract contract KeyStorage256 is BaseMiddleware { if (_keys[operator].length() > 0) { // try to remove disabled keys bytes32 prevKey = _keys[operator].array[0].value; - if (_keys[operator].array[0].status.checkUnregister(Time.timestamp(), SLASHING_WINDOW)) { + if (_keys[operator].checkUnregister(Time.timestamp(), SLASHING_WINDOW, prevKey)) { _keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW, prevKey); delete _keyToOperator[prevKey]; } else if (_keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { diff --git a/src/key-storage/KeyStorageBytes.sol b/src/key-storage/KeyStorageBytes.sol index 0d61bd2..e05447a 100644 --- a/src/key-storage/KeyStorageBytes.sol +++ b/src/key-storage/KeyStorageBytes.sol @@ -79,7 +79,7 @@ abstract contract KeyStorageBytes is BaseManager { if (_keys[operator].length() > 0) { // try to remove disabled keys bytes memory prevKey = _keys[operator].array[0].value; - if (_keys[operator].array[0].status.checkUnregister(Time.timestamp(), SLASHING_WINDOW)) { + if (_keys[operator].checkUnregister(Time.timestamp(), SLASHING_WINDOW, prevKey)) { _keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW, prevKey); delete _keyToOperator[prevKey]; } else if (_keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index e398112..79fb54c 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -278,6 +278,12 @@ library PauseableEnumerableSet { self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } + function checkUnregister(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal view returns (bool) { + uint256 pos = self.positions[value]; + if (pos == 0) return false; + return self.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); + } + function unregister(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal { uint256 pos = self.positions[value]; if (pos == 0) revert NotRegistered(); @@ -353,6 +359,12 @@ library PauseableEnumerableSet { self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } + function checkUnregister(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal view returns (bool) { + uint256 pos = self.positions[value]; + if (pos == 0) return false; + return self.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); + } + function unregister(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal { uint256 pos = self.positions[value]; if (pos == 0) revert NotRegistered(); From 47e8e30a276bc5ff2f81fd7c55d3017110abe008 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 20 Nov 2024 21:07:44 +0700 Subject: [PATCH 056/115] refactor: forge fmt --- .../SelfRegisterEd25519Middleware.sol | 8 +++++++- src/libraries/PauseableEnumerableSet.sol | 14 ++++++++++++-- .../extensions/access-managers/NoAccessManager.sol | 1 + .../access-managers/OwnableAccessManager.sol | 5 ++++- .../operators/ForcePauseSelfRegisterOperators.sol | 12 ++---------- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index a6896bf..68dc50c 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -17,7 +17,13 @@ import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAcc import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; import {Ed25519Sig} from "../../middleware/extensions/sigs/Ed25519Sig.sol"; -contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, KeyStorage256, Ed25519Sig, NoAccessManager { +contract SelfRegisterEd25519Middleware is + SharedVaults, + SelfRegisterOperators, + KeyStorage256, + Ed25519Sig, + NoAccessManager +{ /* * @notice Constructor for initializing the SelfRegisterEd25519Middleware contract. * @param network The address of the network. diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 79fb54c..45d626b 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -278,7 +278,12 @@ library PauseableEnumerableSet { self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } - function checkUnregister(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal view returns (bool) { + function checkUnregister( + Bytes32Set storage self, + uint48 timestamp, + uint48 immutablePeriod, + bytes32 value + ) internal view returns (bool) { uint256 pos = self.positions[value]; if (pos == 0) return false; return self.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); @@ -359,7 +364,12 @@ library PauseableEnumerableSet { self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } - function checkUnregister(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal view returns (bool) { + function checkUnregister( + BytesSet storage self, + uint48 timestamp, + uint48 immutablePeriod, + bytes memory value + ) internal view returns (bool) { uint256 pos = self.positions[value]; if (pos == 0) return false; return self.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/middleware/extensions/access-managers/NoAccessManager.sol index 534453b..a3656e0 100644 --- a/src/middleware/extensions/access-managers/NoAccessManager.sol +++ b/src/middleware/extensions/access-managers/NoAccessManager.sol @@ -5,6 +5,7 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; abstract contract NoAccessManager is BaseMiddleware { error NoAccess(); + function _checkAccess() internal pure override { revert NoAccess(); } diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/middleware/extensions/access-managers/OwnableAccessManager.sol index 44ef7e4..dd6dbf0 100644 --- a/src/middleware/extensions/access-managers/OwnableAccessManager.sol +++ b/src/middleware/extensions/access-managers/OwnableAccessManager.sol @@ -8,6 +8,7 @@ abstract contract OwnableAccessManager is BaseMiddleware { error OnlyOwnerCanCall(address sender); error InvalidOwner(address owner); + function __OwnableAccessManaged_init( address _owner ) internal onlyInitializing { @@ -20,7 +21,9 @@ abstract contract OwnableAccessManager is BaseMiddleware { } } - function setOwner(address _owner) public checkAccess { + function setOwner( + address _owner + ) public checkAccess { if (_owner == address(0)) { revert InvalidOwner(address(0)); } diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index d9dda91..4d4f152 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -1,4 +1,3 @@ - // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; @@ -19,20 +18,13 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { _unpauseOperator(operator); } - function forcePauseOperatorVault( - address operator, - address vault - ) public checkAccess { + function forcePauseOperatorVault(address operator, address vault) public checkAccess { _beforePauseOperatorVault(operator, vault); _pauseOperatorVault(operator, vault); } - function forceUnpauseOperatorVault( - address operator, - address vault - ) public checkAccess { + function forceUnpauseOperatorVault(address operator, address vault) public checkAccess { _beforeUnpauseOperatorVault(operator, vault); _unpauseOperatorVault(operator, vault); } - } From 1d178257c78aabcec882e152d318a4a74bdf6094 Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 21 Nov 2024 14:15:37 +0700 Subject: [PATCH 057/115] refactor: fix docstrings and capture timestamps add --- .../SelfRegisterEd25519Middleware.sol | 4 +- .../SelfRegisterMiddleware.sol | 10 +- .../SimplePosMiddleware.sol | 42 +-- .../sqrt-task-network/SqrtTaskMiddleware.sol | 13 +- src/key-storage/KeyStorage256.sol | 38 +- src/key-storage/KeyStorageBytes.sol | 32 +- src/libraries/PauseableEnumerableSet.sol | 325 +++++++++++++++++- src/managers/AccessManager.sol | 12 +- src/managers/BaseManager.sol | 31 +- src/managers/OperatorManager.sol | 74 ++-- src/managers/VaultManager.sol | 210 +++++------ src/middleware/extensions/SharedVaults.sol | 40 +++ src/middleware/extensions/Subnetworks.sol | 40 +++ .../access-managers/NoAccessManager.sol | 12 + .../access-managers/OwnableAccessManager.sol | 29 +- .../access-managers/OzAccessManaged.sol | 14 + .../capture-timestamps/EpochCapture.sol | 48 +++ .../capture-timestamps/TimestampCapture.sol | 15 + .../ForcePauseSelfRegisterOperators.sol | 27 ++ .../extensions/operators/Operators.sol | 100 +++++- .../operators/SelfRegisterOperators.sol | 163 +++++++++ src/middleware/extensions/sigs/BaseSig.sol | 8 +- src/middleware/extensions/sigs/ECDSASig.sol | 20 ++ src/middleware/extensions/sigs/Ed25519Sig.sol | 14 + 24 files changed, 1095 insertions(+), 226 deletions(-) create mode 100644 src/middleware/extensions/capture-timestamps/EpochCapture.sol create mode 100644 src/middleware/extensions/capture-timestamps/TimestampCapture.sol diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index 68dc50c..0c06061 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -13,6 +13,7 @@ import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; +import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; import {Ed25519Sig} from "../../middleware/extensions/sigs/Ed25519Sig.sol"; @@ -22,7 +23,8 @@ contract SelfRegisterEd25519Middleware is SelfRegisterOperators, KeyStorage256, Ed25519Sig, - NoAccessManager + NoAccessManager, + TimestampCapture { /* * @notice Constructor for initializing the SelfRegisterEd25519Middleware contract. diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index 2e48141..6b7738a 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -14,10 +14,18 @@ import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; import {ECDSASig} from "../../middleware/extensions/sigs/ECDSASig.sol"; import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; +import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; -contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyStorage256, ECDSASig, NoAccessManager { +contract SelfRegisterMiddleware is + SharedVaults, + SelfRegisterOperators, + KeyStorage256, + ECDSASig, + NoAccessManager, + TimestampCapture +{ /* * @notice Constructor for initializing the SelfRegisterMiddleware contract. * @param network The address of the network. diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 3df02c1..0e9a23f 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -13,10 +13,10 @@ import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/operators/Operators.sol"; import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; - +import {EpochCapture} from "../../middleware/extensions/capture-timestamps/EpochCapture.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; -contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableAccessManager { +contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableAccessManager, EpochCapture { using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key @@ -29,9 +29,6 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableA bytes32 key; // Key associated with the validator } - uint48 public immutable EPOCH_DURATION; // Duration of each epoch - uint48 public immutable START_TIMESTAMP; // Start timestamp of the network - struct SlashParams { uint48 epochStart; address operator; @@ -59,9 +56,7 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableA uint48 epochDuration, uint48 slashingWindow ) { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, owner); - EPOCH_DURATION = epochDuration; - START_TIMESTAMP = Time.timestamp(); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, owner, epochDuration); } function initialize( @@ -70,37 +65,12 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableA address vaultRegistry, address operatorRegistry, address operatorNetOptin, - address owner + address owner, + uint48 epochDuration ) public initializer { super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); __OwnableAccessManaged_init(owner); - } - - /* - * @notice Returns the start timestamp for a given epoch. - * @param epoch The epoch number. - * @return The start timestamp. - */ - function getEpochStart( - uint48 epoch - ) public view returns (uint48) { - return START_TIMESTAMP + epoch * EPOCH_DURATION; - } - - /* - * @notice Returns the current epoch. - * @return The current epoch. - */ - function getCurrentEpoch() public view returns (uint48) { - return (Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION; - } - - /* - * @notice Returns the capture timestamp for the current epoch. - * @return The capture timestamp. - */ - function getCaptureTimestamp() public view virtual override returns (uint48 timestamp) { - return getEpochStart(getCurrentEpoch()); + __EpochCapture_init(epochDuration); } /* diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index db3abd4..2f240b1 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -15,8 +15,16 @@ import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/operators/Operators.sol"; import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; - -contract SqrtTaskMiddleware is SharedVaults, Operators, KeyStorage256, EIP712, OwnableAccessManager { +import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; + +contract SqrtTaskMiddleware is + SharedVaults, + Operators, + KeyStorage256, + EIP712, + OwnableAccessManager, + TimestampCapture +{ using Subnetwork for address; using Math for uint256; @@ -43,7 +51,6 @@ contract SqrtTaskMiddleware is SharedVaults, Operators, KeyStorage256, EIP712, O address operatorRegistry, address vaultRegistry, address operatorNetOptin, - address owner, uint48 slashingWindow ) EIP712("SqrtTaskMiddleware", "1") { initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); diff --git a/src/key-storage/KeyStorage256.sol b/src/key-storage/KeyStorage256.sol index e9fabec..f9d176a 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/key-storage/KeyStorage256.sol @@ -6,6 +6,11 @@ import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +/** + * @title KeyStorage256 + * @notice Manages storage and validation of operator keys using bytes32 values + * @dev Extends BaseMiddleware to provide key management functionality + */ abstract contract KeyStorage256 is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; using PauseableEnumerableSet for PauseableEnumerableSet.Status; @@ -17,13 +22,15 @@ abstract contract KeyStorage256 is BaseMiddleware { bytes32 private constant ZERO_BYTES32 = bytes32(0); uint256 private constant MAX_DISABLED_KEYS = 1; - mapping(address => PauseableEnumerableSet.Bytes32Set) internal _keys; // Mapping from operator addresses to their current keys - mapping(bytes32 => address) internal _keyToOperator; // Mapping from keys to operator addresses + /// @notice Mapping from operator addresses to their keys + mapping(address => PauseableEnumerableSet.Bytes32Set) internal _keys; + /// @notice Mapping from keys to operator addresses + mapping(bytes32 => address) internal _keyToOperator; /** - * @notice Returns the operator address associated with a given key - * @param key The key for which to find the associated operator - * @return The address of the operator linked to the specified key + * @notice Gets the operator address associated with a key + * @param key The key to lookup + * @return The operator address that owns the key, or zero address if none */ function operatorByKey( bytes memory key @@ -32,10 +39,9 @@ abstract contract KeyStorage256 is BaseMiddleware { } /** - * @notice Returns the current or previous key for a given operator - * @dev Returns the previous key if the key was updated in the current epoch - * @param operator The address of the operator - * @return The key associated with the specified operator + * @notice Gets an operator's active key at the current capture timestamp + * @param operator The operator address to lookup + * @return The operator's active key encoded as bytes, or encoded zero bytes if none */ function operatorKey( address operator @@ -48,20 +54,22 @@ abstract contract KeyStorage256 is BaseMiddleware { } /** - * @notice Checks if a given key was active at a specified timestamp + * @notice Checks if a key was active at a specific timestamp * @param timestamp The timestamp to check * @param key The key to check - * @return A boolean indicating whether the key was active at the specified timestamp + * @return True if the key was active at the timestamp, false otherwise */ function keyWasActiveAt(uint48 timestamp, bytes32 key) public view returns (bool) { return _keys[_keyToOperator[key]].wasActiveAt(timestamp, key); } /** - * @notice Updates the key associated with an operator - * @dev Reverts if the key is already enabled or if another operator is using it - * @param operator The address of the operator whose key is to be updated - * @param key_ The new key to associate with the operator + * @notice Updates an operator's key + * @dev Handles key rotation by disabling old key and registering new one + * @param operator The operator address to update + * @param key_ The new key to register, encoded as bytes + * @custom:throws DuplicateKey if key is already registered to another operator + * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key_) internal override { bytes32 key = abi.decode(key_, (bytes32)); diff --git a/src/key-storage/KeyStorageBytes.sol b/src/key-storage/KeyStorageBytes.sol index e05447a..392756a 100644 --- a/src/key-storage/KeyStorageBytes.sol +++ b/src/key-storage/KeyStorageBytes.sol @@ -6,6 +6,11 @@ import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +/** + * @title KeyStorageBytes + * @notice Manages storage and validation of operator keys + * @dev Extends BaseManager to provide key management functionality + */ abstract contract KeyStorageBytes is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.BytesSet; using PauseableEnumerableSet for PauseableEnumerableSet.Status; @@ -21,9 +26,9 @@ abstract contract KeyStorageBytes is BaseManager { mapping(bytes => address) internal _keyToOperator; /** - * @notice Returns the operator address associated with a given key - * @param key The key for which to find the associated operator - * @return The address of the operator linked to the specified key + * @notice Gets the operator address associated with a key + * @param key The key to lookup + * @return The operator address that owns the key, or zero address if none */ function operatorByKey( bytes memory key @@ -32,10 +37,9 @@ abstract contract KeyStorageBytes is BaseManager { } /** - * @notice Returns the current or previous key for a given operator - * @dev Returns the previous key if the key was updated in the current epoch - * @param operator The address of the operator - * @return The key associated with the specified operator + * @notice Gets an operator's active key at the current capture timestamp + * @param operator The operator address to lookup + * @return The operator's active key, or empty bytes if none */ function operatorKey( address operator @@ -48,20 +52,22 @@ abstract contract KeyStorageBytes is BaseManager { } /** - * @notice Checks if a given key was active at a specified timestamp + * @notice Checks if a key was active at a specific timestamp * @param timestamp The timestamp to check * @param key The key to check - * @return A boolean indicating whether the key was active at the specified timestamp + * @return True if the key was active at the timestamp, false otherwise */ function keyWasActiveAt(uint48 timestamp, bytes memory key) public view returns (bool) { return _keys[_keyToOperator[key]].wasActiveAt(timestamp, key); } /** - * @notice Updates the key associated with an operator - * @dev Reverts if the key is already enabled or if another operator is using it - * @param operator The address of the operator whose key is to be updated - * @param key The new key to associate with the operator + * @notice Updates an operator's key + * @dev Handles key rotation by disabling old key and registering new one + * @param operator The operator address to update + * @param key The new key to register + * @custom:throws DuplicateKey if key is already registered to another operator + * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key) internal { bytes32 keyHash = keccak256(key); diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 45d626b..372e997 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -1,6 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +/** + * @title PauseableEnumerableSet + * @notice Library for managing sets of values that can be paused and unpaused + * @dev Provides functionality for managing sets of addresses, uint160s, bytes32s and bytes values + * Each value in a set has an associated status that tracks when it was enabled/disabled + */ library PauseableEnumerableSet { using PauseableEnumerableSet for Inner160; using PauseableEnumerableSet for Uint160Set; @@ -15,51 +21,85 @@ library PauseableEnumerableSet { error Enabled(); error ImmutablePeriodNotPassed(); + /** + * @dev Stores the enabled and disabled timestamps for a value + */ struct Status { uint48 enabled; uint48 disabled; } + /** + * @dev Stores a uint160 value and its status + */ struct Inner160 { uint160 value; Status status; } + /** + * @dev Stores a bytes32 value and its status + */ struct InnerBytes32 { bytes32 value; Status status; } + /** + * @dev Stores a bytes value and its status + */ struct InnerBytes { bytes value; Status status; } + /** + * @dev Set of uint160 values with their statuses + */ struct Uint160Set { Inner160[] array; mapping(uint160 => uint256) positions; } + /** + * @dev Set of address values, implemented using Uint160Set + */ struct AddressSet { Uint160Set set; } + /** + * @dev Set of bytes32 values with their statuses + */ struct Bytes32Set { InnerBytes32[] array; mapping(bytes32 => uint256) positions; } + /** + * @dev Set of bytes values with their statuses + */ struct BytesSet { InnerBytes[] array; mapping(bytes => uint256) positions; } + /** + * @notice Sets the initial status of a value + * @param self The status to modify + * @param timestamp The timestamp to set as enabled + */ function set(Status storage self, uint48 timestamp) internal { self.enabled = timestamp; self.disabled = 0; } - // Status functions + /** + * @notice Enables a previously disabled value + * @param self The status to modify + * @param timestamp The timestamp to set as enabled + * @param immutablePeriod The required waiting period after disabling + */ function enable(Status storage self, uint48 timestamp, uint48 immutablePeriod) internal { if (self.enabled != 0) revert AlreadyEnabled(); if (self.disabled + immutablePeriod > timestamp) revert ImmutablePeriodNotPassed(); @@ -68,17 +108,35 @@ library PauseableEnumerableSet { self.disabled = 0; } + /** + * @notice Disables an enabled value + * @param self The status to modify + * @param timestamp The timestamp to set as disabled + */ function disable(Status storage self, uint48 timestamp) internal { if (self.disabled != 0) revert NotEnabled(); self.enabled = 0; self.disabled = timestamp; } + /** + * @notice Validates if a value can be unregistered + * @param self The status to check + * @param timestamp The current timestamp + * @param immutablePeriod The required waiting period after disabling + */ function validateUnregister(Status storage self, uint48 timestamp, uint48 immutablePeriod) internal view { if (self.enabled != 0 || self.disabled == 0) revert Enabled(); if (self.disabled + immutablePeriod > timestamp) revert ImmutablePeriodNotPassed(); } + /** + * @notice Checks if a value can be unregistered + * @param self The status to check + * @param timestamp The current timestamp + * @param immutablePeriod The required waiting period after disabling + * @return bool Whether the value can be unregistered + */ function checkUnregister( Status storage self, uint48 timestamp, @@ -87,22 +145,43 @@ library PauseableEnumerableSet { return self.enabled != 0 && self.disabled == 0 && self.disabled + immutablePeriod > timestamp; } + /** + * @notice Checks if a value was active at a given timestamp + * @param self The status to check + * @param timestamp The timestamp to check + * @return bool Whether the value was active + */ function wasActiveAt(Status storage self, uint48 timestamp) internal view returns (bool) { return self.enabled < timestamp && (self.disabled == 0 || self.disabled >= timestamp); } + /** + * @notice Gets the value and status for an Inner160 + * @param self The Inner160 to get data from + * @return The value, enabled timestamp, and disabled timestamp + */ function get( Inner160 storage self ) internal view returns (uint160, uint48, uint48) { return (self.value, self.status.enabled, self.status.disabled); } + /** + * @notice Gets the value and status for an InnerBytes32 + * @param self The InnerBytes32 to get data from + * @return The value, enabled timestamp, and disabled timestamp + */ function get( InnerBytes32 storage self ) internal view returns (bytes32, uint48, uint48) { return (self.value, self.status.enabled, self.status.disabled); } + /** + * @notice Gets the value and status for an InnerBytes + * @param self The InnerBytes to get data from + * @return The value, enabled timestamp, and disabled timestamp + */ function get( InnerBytes storage self ) internal view returns (bytes memory, uint48, uint48) { @@ -110,17 +189,35 @@ library PauseableEnumerableSet { } // AddressSet functions + + /** + * @notice Gets the number of addresses in the set + * @param self The AddressSet to query + * @return uint256 The number of addresses + */ function length( AddressSet storage self ) internal view returns (uint256) { return self.set.length(); } + /** + * @notice Gets the address and status at a given position + * @param self The AddressSet to query + * @param pos The position to query + * @return The address, enabled timestamp, and disabled timestamp + */ function at(AddressSet storage self, uint256 pos) internal view returns (address, uint48, uint48) { (uint160 value, uint48 enabled, uint48 disabled) = self.set.at(pos); return (address(value), enabled, disabled); } + /** + * @notice Gets all active addresses at a given timestamp + * @param self The AddressSet to query + * @param timestamp The timestamp to check + * @return array Array of active addresses + */ function getActive(AddressSet storage self, uint48 timestamp) internal view returns (address[] memory array) { uint160[] memory uint160Array = self.set.getActive(timestamp); assembly { @@ -129,41 +226,98 @@ library PauseableEnumerableSet { return array; } + /** + * @notice Checks if an address was active at a given timestamp + * @param self The AddressSet to query + * @param timestamp The timestamp to check + * @param addr The address to check + * @return bool Whether the address was active + */ function wasActiveAt(AddressSet storage self, uint48 timestamp, address addr) internal view returns (bool) { return self.set.wasActiveAt(timestamp, uint160(addr)); } + /** + * @notice Registers a new address + * @param self The AddressSet to modify + * @param timestamp The timestamp to set as enabled + * @param addr The address to register + */ function register(AddressSet storage self, uint48 timestamp, address addr) internal { self.set.register(timestamp, uint160(addr)); } + /** + * @notice Pauses an address + * @param self The AddressSet to modify + * @param timestamp The timestamp to set as disabled + * @param addr The address to pause + */ function pause(AddressSet storage self, uint48 timestamp, address addr) internal { self.set.pause(timestamp, uint160(addr)); } + /** + * @notice Unpauses an address + * @param self The AddressSet to modify + * @param timestamp The timestamp to set as enabled + * @param immutablePeriod The required waiting period after disabling + * @param addr The address to unpause + */ function unpause(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address addr) internal { self.set.unpause(timestamp, immutablePeriod, uint160(addr)); } + /** + * @notice Unregisters an address + * @param self The AddressSet to modify + * @param timestamp The current timestamp + * @param immutablePeriod The required waiting period after disabling + * @param addr The address to unregister + */ function unregister(AddressSet storage self, uint48 timestamp, uint48 immutablePeriod, address addr) internal { self.set.unregister(timestamp, immutablePeriod, uint160(addr)); } + /** + * @notice Checks if an address is registered + * @param self The AddressSet to query + * @param addr The address to check + * @return bool Whether the address is registered + */ function contains(AddressSet storage self, address addr) internal view returns (bool) { return self.set.contains(uint160(addr)); } // Uint160Set functions + + /** + * @notice Gets the number of uint160s in the set + * @param self The Uint160Set to query + * @return uint256 The number of uint160s + */ function length( Uint160Set storage self ) internal view returns (uint256) { return self.array.length; } + /** + * @notice Gets the uint160 and status at a given position + * @param self The Uint160Set to query + * @param pos The position to query + * @return The uint160, enabled timestamp, and disabled timestamp + */ function at(Uint160Set storage self, uint256 pos) internal view returns (uint160, uint48, uint48) { return self.array[pos].get(); } + /** + * @notice Gets all active uint160s at a given timestamp + * @param self The Uint160Set to query + * @param timestamp The timestamp to check + * @return array Array of active uint160s + */ function getActive(Uint160Set storage self, uint48 timestamp) internal view returns (uint160[] memory array) { array = new uint160[](self.array.length); uint256 len; @@ -179,11 +333,24 @@ library PauseableEnumerableSet { return array; } + /** + * @notice Checks if a uint160 was active at a given timestamp + * @param self The Uint160Set to query + * @param timestamp The timestamp to check + * @param value The uint160 to check + * @return bool Whether the uint160 was active + */ function wasActiveAt(Uint160Set storage self, uint48 timestamp, uint160 value) internal view returns (bool) { uint256 pos = self.positions[value]; return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); } + /** + * @notice Registers a new uint160 + * @param self The Uint160Set to modify + * @param timestamp The timestamp to set as enabled + * @param value The uint160 to register + */ function register(Uint160Set storage self, uint48 timestamp, uint160 value) internal { if (self.positions[value] != 0) revert AlreadyRegistered(); @@ -193,16 +360,36 @@ library PauseableEnumerableSet { self.positions[value] = self.array.length; } + /** + * @notice Pauses a uint160 + * @param self The Uint160Set to modify + * @param timestamp The timestamp to set as disabled + * @param value The uint160 to pause + */ function pause(Uint160Set storage self, uint48 timestamp, uint160 value) internal { if (self.positions[value] == 0) revert NotRegistered(); self.array[self.positions[value] - 1].status.disable(timestamp); } + /** + * @notice Unpauses a uint160 + * @param self The Uint160Set to modify + * @param timestamp The timestamp to set as enabled + * @param immutablePeriod The required waiting period after disabling + * @param value The uint160 to unpause + */ function unpause(Uint160Set storage self, uint48 timestamp, uint48 immutablePeriod, uint160 value) internal { if (self.positions[value] == 0) revert NotRegistered(); self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } + /** + * @notice Unregisters a uint160 + * @param self The Uint160Set to modify + * @param timestamp The current timestamp + * @param immutablePeriod The required waiting period after disabling + * @param value The uint160 to unregister + */ function unregister(Uint160Set storage self, uint48 timestamp, uint48 immutablePeriod, uint160 value) internal { uint256 pos = self.positions[value]; if (pos == 0) revert NotRegistered(); @@ -223,21 +410,45 @@ library PauseableEnumerableSet { self.positions[self.array[pos].value] = pos + 1; } + /** + * @notice Checks if a uint160 is registered + * @param self The Uint160Set to query + * @param value The uint160 to check + * @return bool Whether the uint160 is registered + */ function contains(Uint160Set storage self, uint160 value) internal view returns (bool) { return self.positions[value] != 0; } // Bytes32Set functions + + /** + * @notice Gets the number of bytes32s in the set + * @param self The Bytes32Set to query + * @return uint256 The number of bytes32s + */ function length( Bytes32Set storage self ) internal view returns (uint256) { return self.array.length; } + /** + * @notice Gets the bytes32 and status at a given position + * @param self The Bytes32Set to query + * @param pos The position to query + * @return The bytes32, enabled timestamp, and disabled timestamp + */ function at(Bytes32Set storage self, uint256 pos) internal view returns (bytes32, uint48, uint48) { return self.array[pos].get(); } + /** + * @notice Gets all active bytes32s at a given timestamp + * @param self The Bytes32Set to query + * @param timestamp The timestamp to check + * @return array Array of active bytes32s + */ function getActive(Bytes32Set storage self, uint48 timestamp) internal view returns (bytes32[] memory array) { array = new bytes32[](self.array.length); uint256 len; @@ -253,11 +464,24 @@ library PauseableEnumerableSet { return array; } + /** + * @notice Checks if a bytes32 was active at a given timestamp + * @param self The Bytes32Set to query + * @param timestamp The timestamp to check + * @param value The bytes32 to check + * @return bool Whether the bytes32 was active + */ function wasActiveAt(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal view returns (bool) { uint256 pos = self.positions[value]; return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); } + /** + * @notice Registers a new bytes32 + * @param self The Bytes32Set to modify + * @param timestamp The timestamp to set as enabled + * @param value The bytes32 to register + */ function register(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal { if (self.positions[value] != 0) revert AlreadyRegistered(); @@ -268,16 +492,37 @@ library PauseableEnumerableSet { self.positions[value] = pos + 1; } + /** + * @notice Pauses a bytes32 + * @param self The Bytes32Set to modify + * @param timestamp The timestamp to set as disabled + * @param value The bytes32 to pause + */ function pause(Bytes32Set storage self, uint48 timestamp, bytes32 value) internal { if (self.positions[value] == 0) revert NotRegistered(); self.array[self.positions[value] - 1].status.disable(timestamp); } + /** + * @notice Unpauses a bytes32 + * @param self The Bytes32Set to modify + * @param timestamp The timestamp to set as enabled + * @param immutablePeriod The required waiting period after disabling + * @param value The bytes32 to unpause + */ function unpause(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal { if (self.positions[value] == 0) revert NotRegistered(); self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } + /** + * @notice Checks if a bytes32 can be unregistered + * @param self The Bytes32Set to query + * @param timestamp The current timestamp + * @param immutablePeriod The required waiting period after disabling + * @param value The bytes32 to check + * @return bool Whether the bytes32 can be unregistered + */ function checkUnregister( Bytes32Set storage self, uint48 timestamp, @@ -289,6 +534,13 @@ library PauseableEnumerableSet { return self.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); } + /** + * @notice Unregisters a bytes32 + * @param self The Bytes32Set to modify + * @param timestamp The current timestamp + * @param immutablePeriod The required waiting period after disabling + * @param value The bytes32 to unregister + */ function unregister(Bytes32Set storage self, uint48 timestamp, uint48 immutablePeriod, bytes32 value) internal { uint256 pos = self.positions[value]; if (pos == 0) revert NotRegistered(); @@ -309,21 +561,45 @@ library PauseableEnumerableSet { self.positions[self.array[pos].value] = pos + 1; } + /** + * @notice Checks if a bytes32 is registered + * @param self The Bytes32Set to query + * @param value The bytes32 to check + * @return bool Whether the bytes32 is registered + */ function contains(Bytes32Set storage self, bytes32 value) internal view returns (bool) { return self.positions[value] != 0; } // BytesSet functions + + /** + * @notice Gets the number of bytes values in the set + * @param self The BytesSet to query + * @return uint256 The number of bytes values + */ function length( BytesSet storage self ) internal view returns (uint256) { return self.array.length; } + /** + * @notice Gets the bytes value and status at a given position + * @param self The BytesSet to query + * @param pos The position to query + * @return The bytes value, enabled timestamp, and disabled timestamp + */ function at(BytesSet storage self, uint256 pos) internal view returns (bytes memory, uint48, uint48) { return self.array[pos].get(); } + /** + * @notice Gets all active bytes values at a given timestamp + * @param self The BytesSet to query + * @param timestamp The timestamp to check + * @return array Array of active bytes values + */ function getActive(BytesSet storage self, uint48 timestamp) internal view returns (bytes[] memory array) { array = new bytes[](self.array.length); uint256 len; @@ -339,11 +615,24 @@ library PauseableEnumerableSet { return array; } + /** + * @notice Checks if a bytes value was active at a given timestamp + * @param self The BytesSet to query + * @param timestamp The timestamp to check + * @param value The bytes value to check + * @return bool Whether the bytes value was active + */ function wasActiveAt(BytesSet storage self, uint48 timestamp, bytes memory value) internal view returns (bool) { uint256 pos = self.positions[value]; return pos != 0 && self.array[pos - 1].status.wasActiveAt(timestamp); } + /** + * @notice Registers a new bytes value + * @param self The BytesSet to modify + * @param timestamp The timestamp to set as enabled + * @param value The bytes value to register + */ function register(BytesSet storage self, uint48 timestamp, bytes memory value) internal { if (self.positions[value] != 0) revert AlreadyRegistered(); @@ -354,16 +643,37 @@ library PauseableEnumerableSet { self.positions[value] = pos + 1; } + /** + * @notice Pauses a bytes value + * @param self The BytesSet to modify + * @param timestamp The timestamp to set as disabled + * @param value The bytes value to pause + */ function pause(BytesSet storage self, uint48 timestamp, bytes memory value) internal { if (self.positions[value] == 0) revert NotRegistered(); self.array[self.positions[value] - 1].status.disable(timestamp); } + /** + * @notice Unpauses a bytes value + * @param self The BytesSet to modify + * @param timestamp The timestamp to set as enabled + * @param immutablePeriod The required waiting period after disabling + * @param value The bytes value to unpause + */ function unpause(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal { if (self.positions[value] == 0) revert NotRegistered(); self.array[self.positions[value] - 1].status.enable(timestamp, immutablePeriod); } + /** + * @notice Checks if a bytes value can be unregistered + * @param self The BytesSet to query + * @param timestamp The current timestamp + * @param immutablePeriod The required waiting period after disabling + * @param value The bytes value to check + * @return bool Whether the bytes value can be unregistered + */ function checkUnregister( BytesSet storage self, uint48 timestamp, @@ -375,6 +685,13 @@ library PauseableEnumerableSet { return self.array[pos - 1].status.checkUnregister(timestamp, immutablePeriod); } + /** + * @notice Unregisters a bytes value + * @param self The BytesSet to modify + * @param timestamp The current timestamp + * @param immutablePeriod The required waiting period after disabling + * @param value The bytes value to unregister + */ function unregister(BytesSet storage self, uint48 timestamp, uint48 immutablePeriod, bytes memory value) internal { uint256 pos = self.positions[value]; if (pos == 0) revert NotRegistered(); @@ -395,6 +712,12 @@ library PauseableEnumerableSet { self.positions[self.array[pos].value] = pos + 1; } + /** + * @notice Checks if a bytes value is registered + * @param self The BytesSet to query + * @param value The bytes value to check + * @return bool Whether the bytes value is registered + */ function contains(BytesSet storage self, bytes memory value) internal view returns (bool) { return self.positions[value] != 0; } diff --git a/src/managers/AccessManager.sol b/src/managers/AccessManager.sol index dc3c89c..70b0872 100644 --- a/src/managers/AccessManager.sol +++ b/src/managers/AccessManager.sol @@ -1,14 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +/** + * @title AccessManager + * @notice Abstract contract for managing access control + * @dev Provides a modifier and internal function for checking access permissions + */ abstract contract AccessManager { + /** + * @notice Modifier that checks access before executing a function + * @dev Calls internal _checkAccess function and continues if allowed + */ modifier checkAccess() { _checkAccess(); _; } /** - * @notice Checks if the user has access to the given selector. + * @notice Internal function to check if caller has required access + * @dev Must be implemented by inheriting contracts */ function _checkAccess() internal virtual; } diff --git a/src/managers/BaseManager.sol b/src/managers/BaseManager.sol index 4728e4f..51d6d4e 100644 --- a/src/managers/BaseManager.sol +++ b/src/managers/BaseManager.sol @@ -14,14 +14,13 @@ abstract contract BaseManager is Initializable { uint64 public constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type uint64 public constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type - /* - * @notice initalizer of the BaseManager contract. - * @param network The address of the network. - * @param epochDuration The duration of each epoch. - * @param slashingWindow The duration of the slashing window. - * @param vaultRegistry The address of the vault registry. - * @param operatorRegistry The address of the operator registry. - * @param operatorNetOptIn The address of the operator network opt-in service. + /** + * @notice Initializes the BaseManager contract + * @param network The address of the network + * @param slashingWindow The duration of the slashing window + * @param vaultRegistry The address of the vault registry + * @param operatorRegistry The address of the operator registry + * @param operatorNetOptIn The address of the operator network opt-in service */ function initialize( address network, @@ -37,17 +36,19 @@ abstract contract BaseManager is Initializable { OPERATOR_NET_OPTIN = operatorNetOptIn; } - /* + /** * @notice Returns the current capture timestamp - * @dev Returns block.timestamp - 1 by default but can be overrided - * @return The current capture timestamp + * @return timestamp The current capture timestamp */ - function getCaptureTimestamp() public view virtual returns (uint48 timestamp) { - return Time.timestamp() - 1; - } + function getCaptureTimestamp() public view virtual returns (uint48 timestamp); + /** + * @notice Converts stake amount to voting power + * @param vault The vault address + * @param stake The stake amount + * @return power The calculated voting power + */ function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256 power) { - vault; return stake; } } diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index aede709..fcfa677 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -21,18 +21,20 @@ abstract contract OperatorManager is BaseManager { PauseableEnumerableSet.AddressSet internal _operators; - /* - * @notice Returns the total number of registered operators, including both active and inactive. - * @return The number of registered operators. + /** + * @notice Returns the total number of registered operators, including both active and inactive + * @return The number of registered operators */ function operatorsLength() public view returns (uint256) { return _operators.length(); } - /* - * @notice Returns the operator and their associated enabled and disabled times at a specific position. - * @param pos The index position in the operators array. - * @return The address, enabled epoch, disabled epoch and enabled before disabled epoch of the operator. + /** + * @notice Returns the operator and their associated enabled and disabled times at a specific position + * @param pos The index position in the operators array + * @return The operator address + * @return The enabled timestamp + * @return The disabled timestamp */ function operatorWithTimesAt( uint256 pos @@ -40,18 +42,18 @@ abstract contract OperatorManager is BaseManager { return _operators.at(pos); } - /* - * @notice Returns a list of active operators. - * @return An array of addresses representing the active operators. + /** + * @notice Returns a list of active operators + * @return Array of addresses representing the active operators */ function activeOperators() public view returns (address[] memory) { return _operators.getActive(getCaptureTimestamp()); } - /* - * @notice Returns a list of active operators at a specific timestamp. - * @param timestamp The timestamp to check. - * @return An array of addresses representing the active operators. + /** + * @notice Returns a list of active operators at a specific timestamp + * @param timestamp The timestamp to check + * @return Array of addresses representing the active operators at the timestamp */ function activeOperatorsAt( uint48 timestamp @@ -59,20 +61,20 @@ abstract contract OperatorManager is BaseManager { return _operators.getActive(timestamp); } - /* - * @notice Checks if a given operator was active at a specified timestamp. - * @param timestamp The timestamp to check. - * @param operator The operator to check. - * @return A boolean indicating whether the operator was active at the specified timestamp. + /** + * @notice Checks if a given operator was active at a specified timestamp + * @param timestamp The timestamp to check + * @param operator The operator address to check + * @return True if the operator was active at the timestamp, false otherwise */ function operatorWasActiveAt(uint48 timestamp, address operator) public view returns (bool) { return _operators.wasActiveAt(timestamp, operator); } - /* - * @notice Checks if an operator is registered. - * @param operator The address of the operator to check. - * @return A boolean indicating whether the operator is registered. + /** + * @notice Checks if an operator is registered + * @param operator The address of the operator to check + * @return True if the operator is registered, false otherwise */ function isOperatorRegistered( address operator @@ -80,9 +82,11 @@ abstract contract OperatorManager is BaseManager { return _operators.contains(operator); } - /* - * @notice Registers a new operator. - * @param operator The address of the operator to register. + /** + * @notice Registers a new operator + * @param operator The address of the operator to register + * @custom:throws NotOperator if operator is not registered in the operator registry + * @custom:throws OperatorNotOptedIn if operator has not opted into the network */ function _registerOperator( address operator @@ -98,9 +102,9 @@ abstract contract OperatorManager is BaseManager { _operators.register(Time.timestamp(), operator); } - /* - * @notice Pauses a registered operator. - * @param operator The address of the operator to pause. + /** + * @notice Pauses a registered operator + * @param operator The address of the operator to pause */ function _pauseOperator( address operator @@ -108,9 +112,9 @@ abstract contract OperatorManager is BaseManager { _operators.pause(Time.timestamp(), operator); } - /* - * @notice Unpauses a paused operator. - * @param operator The address of the operator to unpause. + /** + * @notice Unpauses a paused operator + * @param operator The address of the operator to unpause */ function _unpauseOperator( address operator @@ -118,9 +122,9 @@ abstract contract OperatorManager is BaseManager { _operators.unpause(Time.timestamp(), SLASHING_WINDOW, operator); } - /* - * @notice Unregisters an operator. - * @param operator The address of the operator to unregister. + /** + * @notice Unregisters an operator + * @param operator The address of the operator to unregister */ function _unregisterOperator( address operator diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index c9aef49..6376189 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -17,6 +17,11 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {BaseManager} from "./BaseManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; +/** + * @title VaultManager + * @notice Abstract contract for managing vaults and their relationships with operators and subnetworks + * @dev Extends BaseManager and provides functionality for registering, pausing, and managing vaults + */ abstract contract VaultManager is BaseManager { using EnumerableMap for EnumerableMap.AddressToUintMap; using EnumerableMap for EnumerableMap.AddressToAddressMap; @@ -39,27 +44,34 @@ abstract contract VaultManager is BaseManager { mapping(address => PauseableEnumerableSet.AddressSet) internal _operatorVaults; EnumerableMap.AddressToAddressMap internal _vaultOperator; + /** + * @dev Struct containing information about a slash response + * @param vault The address of the vault being slashed + * @param slasherType The type identifier of the slasher + * @param subnetwork The subnetwork identifier where the slash occurred + * @param response For instant slashing: the slashed amount, for veto slashing: the slash index + */ struct SlashResponse { address vault; uint64 slasherType; bytes32 subnetwork; - uint256 response; // if instant slashed amount else slash index + uint256 response; } /** - * @notice Returns the number of subnetworks registered - * @return The count of registered subnetworks + * @notice Gets the total number of registered subnetworks + * @return uint256 The count of registered subnetworks */ function subnetworksLength() public view returns (uint256) { return _subnetworks.length(); } /** - * @notice Returns the subnetwork information at a specified position - * @param pos The index of the subnetwork - * @return The subnetwork address - * @return enableTime The time when the subnetwork was enabled - * @return disableTime The time when the subnetwork was disabled + * @notice Gets the subnetwork information at a specific index + * @param pos The index position to query + * @return uint160 The subnetwork address + * @return uint48 The time when the subnetwork was enabled + * @return uint48 The time when the subnetwork was disabled */ function subnetworkWithTimesAt( uint256 pos @@ -68,17 +80,17 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Returns an array of active subnetworks at the current capture timestamp - * @return An array of active subnetwork addresses + * @notice Gets all currently active subnetworks + * @return uint160[] Array of active subnetwork addresses */ function activeSubnetworks() public view returns (uint160[] memory) { return _subnetworks.getActive(getCaptureTimestamp()); } /** - * @notice Returns an array of active subnetworks at a specific timestamp - * @param timestamp The timestamp to check activity at - * @return An array of active subnetwork addresses + * @notice Gets all subnetworks that were active at a specific timestamp + * @param timestamp The timestamp to check + * @return uint160[] Array of subnetwork addresses that were active at the timestamp */ function activeSubnetworksAt( uint48 timestamp @@ -87,29 +99,29 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Checks if a given subnetwork was active at a specified timestamp + * @notice Checks if a subnetwork was active at a specific timestamp * @param timestamp The timestamp to check - * @param subnetwork The subnetwork to check - * @return True if the subnetwork was active at the timestamp + * @param subnetwork The subnetwork identifier + * @return bool True if the subnetwork was active at the timestamp */ function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) public view returns (bool) { return _subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); } /** - * @notice Returns the number of shared vaults - * @return The count of shared vaults + * @notice Gets the total number of shared vaults + * @return uint256 The count of shared vaults */ function sharedVaultsLength() public view returns (uint256) { return _sharedVaults.length(); } /** - * @notice Returns the vault information at a specified position - * @param pos The index position in the shared vaults array - * @return The vault address - * @return enableTime The time when the vault was enabled - * @return disableTime The time when the vault was disabled + * @notice Gets the vault information at a specific index + * @param pos The index position to query + * @return address The vault address + * @return uint48 The time when the vault was enabled + * @return uint48 The time when the vault was disabled */ function sharedVaultWithTimesAt( uint256 pos @@ -118,17 +130,17 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Returns an array of active shared vaults at the current capture timestamp - * @return An array of active shared vault addresses + * @notice Gets all currently active shared vaults + * @return address[] Array of active shared vault addresses */ function activeSharedVaults() public view returns (address[] memory) { return _sharedVaults.getActive(getCaptureTimestamp()); } /** - * @notice Returns the number of vaults associated with an operator - * @param operator The address of the operator - * @return The count of vaults for the operator + * @notice Gets the number of vaults associated with an operator + * @param operator The operator address to query + * @return uint256 The count of vaults for the operator */ function operatorVaultsLength( address operator @@ -137,21 +149,21 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Returns the vault information at a specified position for an operator - * @param operator The address of the operator - * @param pos The index position in the operator vaults array - * @return The vault address - * @return enableTime The time when the vault was enabled - * @return disableTime The time when the vault was disabled + * @notice Gets the vault information at a specific index for an operator + * @param operator The operator address + * @param pos The index position to query + * @return address The vault address + * @return uint48 The time when the vault was enabled + * @return uint48 The time when the vault was disabled */ function operatorVaultWithTimesAt(address operator, uint256 pos) public view returns (address, uint48, uint48) { return _operatorVaults[operator].at(pos); } /** - * @notice Returns an array of active vaults for a specific operator at the current capture timestamp - * @param operator The address of the operator - * @return An array of active vault addresses + * @notice Gets all currently active vaults for a specific operator + * @param operator The operator address + * @return address[] Array of active vault addresses */ function activeOperatorVaults( address operator @@ -160,8 +172,8 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Returns all active vaults at the current capture timestamp - * @return An array of active vault addresses + * @notice Gets all currently active vaults across all operators + * @return address[] Array of all active vault addresses */ function activeVaults() public view virtual returns (address[] memory) { uint48 timestamp = getCaptureTimestamp(); @@ -189,9 +201,9 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Returns all active vaults at a specific timestamp - * @param timestamp The timestamp to check activity at - * @return An array of active vault addresses + * @notice Gets all vaults that were active at a specific timestamp + * @param timestamp The timestamp to check + * @return address[] Array of vault addresses that were active at the timestamp */ function activeVaultsAt( uint48 timestamp @@ -220,9 +232,9 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Returns active vaults for a specific operator at the current capture timestamp - * @param operator The address of the operator - * @return An array of active vault addresses + * @notice Gets all currently active vaults for a specific operator + * @param operator The operator address + * @return address[] Array of active vault addresses for the operator */ function activeVaults( address operator @@ -244,10 +256,10 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Returns active vaults for a specific operator at a given timestamp - * @param timestamp The timestamp to check activity at - * @param operator The address of the operator - * @return An array of active vault addresses + * @notice Gets all vaults that were active for an operator at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @return address[] Array of vault addresses that were active at the timestamp */ function activeVaultsAt(uint48 timestamp, address operator) public view virtual returns (address[] memory) { address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); @@ -268,9 +280,9 @@ abstract contract VaultManager is BaseManager { /** * @notice Checks if a vault was active at a specific timestamp * @param timestamp The timestamp to check - * @param operator The address of operator - * @param vault The vault address to check - * @return True if the vault was active at the timestamp + * @param operator The operator address + * @param vault The vault address + * @return bool True if the vault was active at the timestamp */ function vaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { return sharedVaultWasActiveAt(timestamp, vault) || operatorVaultWasActiveAt(timestamp, operator, vault); @@ -279,8 +291,8 @@ abstract contract VaultManager is BaseManager { /** * @notice Checks if a shared vault was active at a specific timestamp * @param timestamp The timestamp to check - * @param vault The vault address to check - * @return True if the shared vault was active at the timestamp + * @param vault The vault address + * @return bool True if the shared vault was active at the timestamp */ function sharedVaultWasActiveAt(uint48 timestamp, address vault) public view returns (bool) { return _sharedVaults.wasActiveAt(timestamp, vault); @@ -289,9 +301,9 @@ abstract contract VaultManager is BaseManager { /** * @notice Checks if an operator vault was active at a specific timestamp * @param timestamp The timestamp to check - * @param operator The address of operator - * @param vault The vault address to check - * @return True if the operator vault was active at the timestamp + * @param operator The operator address + * @param vault The vault address + * @return bool True if the operator vault was active at the timestamp */ function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { return _operatorVaults[operator].wasActiveAt(timestamp, vault); @@ -299,10 +311,10 @@ abstract contract VaultManager is BaseManager { /** * @notice Gets the stake amount for an operator in a vault and subnetwork - * @param operator The address of the operator - * @param vault The address of the vault + * @param operator The operator address + * @param vault The vault address * @param subnetwork The subnetwork identifier - * @return The stake amount + * @return uint256 The stake amount */ function getOperatorStake(address operator, address vault, uint96 subnetwork) public view returns (uint256) { uint48 timestamp = getCaptureTimestamp(); @@ -312,11 +324,11 @@ abstract contract VaultManager is BaseManager { /** * @notice Gets the stake amount for an operator in a vault and subnetwork at a specific timestamp - * @param operator The address of the operator - * @param vault The address of the vault + * @param operator The operator address + * @param vault The vault address * @param subnetwork The subnetwork identifier - * @param timestamp The timestamp to check stake at - * @return The stake amount + * @param timestamp The timestamp to check + * @return uint256 The stake amount at the timestamp */ function getOperatorStakeAt( address operator, @@ -330,10 +342,10 @@ abstract contract VaultManager is BaseManager { /** * @notice Gets the power amount for an operator in a vault and subnetwork - * @param operator The address of the operator - * @param vault The address of the vault + * @param operator The operator address + * @param vault The vault address * @param subnetwork The subnetwork identifier - * @return The power amount + * @return uint256 The power amount */ function getOperatorPower(address operator, address vault, uint96 subnetwork) public view returns (uint256) { uint256 stake = getOperatorStake(operator, vault, subnetwork); @@ -342,11 +354,11 @@ abstract contract VaultManager is BaseManager { /** * @notice Gets the power amount for an operator in a vault and subnetwork at a specific timestamp - * @param operator The address of the operator - * @param vault The address of the vault + * @param operator The operator address + * @param vault The vault address * @param subnetwork The subnetwork identifier - * @param timestamp The timestamp to check power at - * @return The power amount + * @param timestamp The timestamp to check + * @return uint256 The power amount at the timestamp */ function getOperatorPowerAt( address operator, @@ -360,7 +372,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Gets the total stake amount for an operator across all vaults and subnetworks - * @param operator The address of the operator + * @param operator The operator address * @return stake The total stake amount */ function getOperatorStake( @@ -381,9 +393,9 @@ abstract contract VaultManager is BaseManager { /** * @notice Gets the total stake amount for an operator across all vaults and subnetworks at a specific timestamp - * @param operator The address of the operator - * @param timestamp The timestamp to check stake at - * @return stake The total stake amount + * @param operator The operator address + * @param timestamp The timestamp to check + * @return stake The total stake amount at the timestamp */ function getOperatorStakeAt(address operator, uint48 timestamp) public view virtual returns (uint256 stake) { address[] memory vaults = activeVaultsAt(timestamp, operator); @@ -401,7 +413,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Gets the total power amount for an operator across all vaults and subnetworks - * @param operator The address of the operator + * @param operator The operator address * @return power The total power amount */ function getOperatorPower( @@ -422,9 +434,9 @@ abstract contract VaultManager is BaseManager { /** * @notice Gets the total power amount for an operator across all vaults and subnetworks at a specific timestamp - * @param operator The address of the operator - * @param timestamp The timestamp to check power at - * @return power The total power amount + * @param operator The operator address + * @param timestamp The timestamp to check + * @return power The total power amount at the timestamp */ function getOperatorPowerAt(address operator, uint48 timestamp) public view virtual returns (uint256 power) { address[] memory vaults = activeVaultsAt(timestamp, operator); @@ -474,7 +486,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Registers a new subnetwork - * @param subnetwork The identifier of the subnetwork to register + * @param subnetwork The subnetwork identifier to register */ function _registerSubnetwork( uint96 subnetwork @@ -484,7 +496,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Pauses a subnetwork - * @param subnetwork The identifier of the subnetwork to pause + * @param subnetwork The subnetwork identifier to pause */ function _pauseSubnetwork( uint96 subnetwork @@ -494,7 +506,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Unpauses a subnetwork - * @param subnetwork The identifier of the subnetwork to unpause + * @param subnetwork The subnetwork identifier to unpause */ function _unpauseSubnetwork( uint96 subnetwork @@ -504,7 +516,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Unregisters a subnetwork - * @param subnetwork The identifier of the subnetwork to unregister + * @param subnetwork The subnetwork identifier to unregister */ function _unregisterSubnetwork( uint96 subnetwork @@ -514,7 +526,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Registers a new shared vault - * @param vault The address of the vault to register + * @param vault The vault address to register */ function _registerSharedVault( address vault @@ -525,8 +537,8 @@ abstract contract VaultManager is BaseManager { /** * @notice Registers a new operator vault - * @param operator The address of the operator - * @param vault The address of the vault to register + * @param operator The operator address + * @param vault The vault address to register */ function _registerOperatorVault(address operator, address vault) internal { _validateVault(vault); @@ -539,7 +551,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Pauses a shared vault - * @param vault The address of the vault to pause + * @param vault The vault address to pause */ function _pauseSharedVault( address vault @@ -549,7 +561,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Unpauses a shared vault - * @param vault The address of the vault to unpause + * @param vault The vault address to unpause */ function _unpauseSharedVault( address vault @@ -559,8 +571,8 @@ abstract contract VaultManager is BaseManager { /** * @notice Pauses an operator vault - * @param operator The address of the operator - * @param vault The address of the vault to pause + * @param operator The operator address + * @param vault The vault address to pause */ function _pauseOperatorVault(address operator, address vault) internal { _operatorVaults[operator].pause(Time.timestamp(), vault); @@ -568,8 +580,8 @@ abstract contract VaultManager is BaseManager { /** * @notice Unpauses an operator vault - * @param operator The address of the operator - * @param vault The address of the vault to unpause + * @param operator The operator address + * @param vault The vault address to unpause */ function _unpauseOperatorVault(address operator, address vault) internal { _operatorVaults[operator].unpause(Time.timestamp(), SLASHING_WINDOW, vault); @@ -577,7 +589,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Unregisters a shared vault - * @param vault The address of the vault to unregister + * @param vault The vault address to unregister */ function _unregisterSharedVault( address vault @@ -587,8 +599,8 @@ abstract contract VaultManager is BaseManager { /** * @notice Unregisters an operator vault - * @param operator The address of the operator - * @param vault The address of the vault to unregister + * @param operator The operator address + * @param vault The vault address to unregister */ function _unregisterOperatorVault(address operator, address vault) internal { _operatorVaults[operator].unregister(Time.timestamp(), SLASHING_WINDOW, vault); @@ -598,7 +610,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Slashes a vault based on provided conditions * @param timestamp The timestamp when the slash occurs - * @param vault The address of the vault + * @param vault The vault address * @param subnetwork The subnetwork identifier * @param operator The operator to slash * @param amount The amount to slash @@ -641,7 +653,7 @@ abstract contract VaultManager is BaseManager { /** * @notice Executes a veto-based slash for a vault - * @param vault The address of the vault + * @param vault The vault address * @param slashIndex The index of the slash to execute * @param hints Additional data for the veto slasher * @return slashedAmount The amount that was slashed @@ -661,8 +673,8 @@ abstract contract VaultManager is BaseManager { } /** - * @notice Validates if the vault is properly initialized and registered - * @param vault The address of the vault to validate + * @notice Validates if a vault is properly initialized and registered + * @param vault The vault address to validate */ function _validateVault( address vault diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 601e072..7f7bdb4 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -3,7 +3,16 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../BaseMiddleware.sol"; +/** + * @title SharedVaults + * @notice Contract for managing shared vaults that can be used by multiple operators + * @dev Extends BaseMiddleware to provide access control for vault management functions + */ abstract contract SharedVaults is BaseMiddleware { + /** + * @notice Registers a new shared vault + * @param sharedVault The address of the vault to register + */ function registerSharedVault( address sharedVault ) public checkAccess { @@ -11,6 +20,10 @@ abstract contract SharedVaults is BaseMiddleware { _registerSharedVault(sharedVault); } + /** + * @notice Pauses a shared vault + * @param sharedVault The address of the vault to pause + */ function pauseSharedVault( address sharedVault ) public checkAccess { @@ -18,6 +31,10 @@ abstract contract SharedVaults is BaseMiddleware { _pauseSharedVault(sharedVault); } + /** + * @notice Unpauses a shared vault + * @param sharedVault The address of the vault to unpause + */ function unpauseSharedVault( address sharedVault ) public checkAccess { @@ -25,6 +42,10 @@ abstract contract SharedVaults is BaseMiddleware { _unpauseSharedVault(sharedVault); } + /** + * @notice Unregisters a shared vault + * @param sharedVault The address of the vault to unregister + */ function unregisterSharedVault( address sharedVault ) public checkAccess { @@ -32,15 +53,34 @@ abstract contract SharedVaults is BaseMiddleware { _unregisterSharedVault(sharedVault); } + /** + * @notice Hook called before registering a shared vault + * @param sharedVault The vault address + */ function _beforeRegisterSharedVault( address sharedVault ) internal virtual {} + + /** + * @notice Hook called before pausing a shared vault + * @param sharedVault The vault address + */ function _beforePauseSharedVault( address sharedVault ) internal virtual {} + + /** + * @notice Hook called before unpausing a shared vault + * @param sharedVault The vault address + */ function _beforeUnpauseSharedVault( address sharedVault ) internal virtual {} + + /** + * @notice Hook called before unregistering a shared vault + * @param sharedVault The vault address + */ function _beforeUnregisterSharedVault( address sharedVault ) internal virtual {} diff --git a/src/middleware/extensions/Subnetworks.sol b/src/middleware/extensions/Subnetworks.sol index 362b942..ecda886 100644 --- a/src/middleware/extensions/Subnetworks.sol +++ b/src/middleware/extensions/Subnetworks.sol @@ -5,7 +5,16 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {BaseMiddleware} from "../BaseMiddleware.sol"; +/** + * @title Subnetworks + * @notice Contract for managing subnetworks that can be registered and controlled + * @dev Extends BaseMiddleware to provide access control for subnetwork management functions + */ abstract contract Subnetworks is BaseMiddleware { + /** + * @notice Registers a new subnetwork + * @param subnetwork The ID of the subnetwork to register + */ function registerSubnetwork( uint96 subnetwork ) public checkAccess { @@ -13,6 +22,10 @@ abstract contract Subnetworks is BaseMiddleware { _registerSubnetwork(subnetwork); } + /** + * @notice Pauses a subnetwork + * @param subnetwork The ID of the subnetwork to pause + */ function pauseSubnetwork( uint96 subnetwork ) public checkAccess { @@ -20,6 +33,10 @@ abstract contract Subnetworks is BaseMiddleware { _pauseSubnetwork(subnetwork); } + /** + * @notice Unpauses a subnetwork + * @param subnetwork The ID of the subnetwork to unpause + */ function unpauseSubnetwork( uint96 subnetwork ) public checkAccess { @@ -27,6 +44,10 @@ abstract contract Subnetworks is BaseMiddleware { _unpauseSubnetwork(subnetwork); } + /** + * @notice Unregisters a subnetwork + * @param subnetwork The ID of the subnetwork to unregister + */ function unregisterSubnetwork( uint96 subnetwork ) public checkAccess { @@ -34,15 +55,34 @@ abstract contract Subnetworks is BaseMiddleware { _unregisterSubnetwork(subnetwork); } + /** + * @notice Hook called before registering a subnetwork + * @param subnetwork The subnetwork ID + */ function _beforeRegisterSubnetwork( uint96 subnetwork ) internal virtual {} + + /** + * @notice Hook called before pausing a subnetwork + * @param subnetwork The subnetwork ID + */ function _beforePauseSubnetwork( uint96 subnetwork ) internal virtual {} + + /** + * @notice Hook called before unpausing a subnetwork + * @param subnetwork The subnetwork ID + */ function _beforeUnpauseSubnetwork( uint96 subnetwork ) internal virtual {} + + /** + * @notice Hook called before unregistering a subnetwork + * @param subnetwork The subnetwork ID + */ function _beforeUnregisterSubnetwork( uint96 subnetwork ) internal virtual {} diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/middleware/extensions/access-managers/NoAccessManager.sol index a3656e0..95cdda2 100644 --- a/src/middleware/extensions/access-managers/NoAccessManager.sol +++ b/src/middleware/extensions/access-managers/NoAccessManager.sol @@ -3,9 +3,21 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../../BaseMiddleware.sol"; +/** + * @title NoAccessManager + * @notice A middleware extension that denies all access by default + * @dev Implements BaseMiddleware and always reverts on access checks + */ abstract contract NoAccessManager is BaseMiddleware { + /** + * @notice Error thrown when access is denied + */ error NoAccess(); + /** + * @notice Checks access and always reverts + * @dev This function is called internally to enforce access control + */ function _checkAccess() internal pure override { revert NoAccess(); } diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/middleware/extensions/access-managers/OwnableAccessManager.sol index dd6dbf0..02c1994 100644 --- a/src/middleware/extensions/access-managers/OwnableAccessManager.sol +++ b/src/middleware/extensions/access-managers/OwnableAccessManager.sol @@ -3,24 +3,51 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../../BaseMiddleware.sol"; +/** + * @title OwnableAccessManager + * @notice A middleware extension that restricts access to a single owner address + * @dev Implements BaseMiddleware with owner-based access control + */ abstract contract OwnableAccessManager is BaseMiddleware { address public owner; + /** + * @notice Error thrown when a non-owner address attempts to call a restricted function + * @param sender The address that attempted the call + */ error OnlyOwnerCanCall(address sender); + + /** + * @notice Error thrown when trying to set an invalid owner address + * @param owner The invalid owner address + */ error InvalidOwner(address owner); + /** + * @notice Initializes the contract with an owner address + * @param _owner The address to set as the owner + */ function __OwnableAccessManaged_init( address _owner ) internal onlyInitializing { owner = _owner; } - function _checkAccess() internal override { + /** + * @notice Checks if the caller has access (is the owner) + * @dev Reverts if the caller is not the owner + */ + function _checkAccess() internal view override { if (msg.sender != owner) { revert OnlyOwnerCanCall(msg.sender); } } + /** + * @notice Updates the owner address + * @param _owner The new owner address + * @dev Can only be called by the current owner + */ function setOwner( address _owner ) public checkAccess { diff --git a/src/middleware/extensions/access-managers/OzAccessManaged.sol b/src/middleware/extensions/access-managers/OzAccessManaged.sol index 9c21ad1..e0849f9 100644 --- a/src/middleware/extensions/access-managers/OzAccessManaged.sol +++ b/src/middleware/extensions/access-managers/OzAccessManaged.sol @@ -5,13 +5,27 @@ import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/acce import {BaseMiddleware} from "../../BaseMiddleware.sol"; +/** + * @title OzAccessManaged + * @notice A middleware extension that integrates OpenZeppelin's AccessManager for access control + * @dev Implements BaseMiddleware with OpenZeppelin's AccessManagedUpgradeable functionality + */ abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { + /** + * @notice Initializes the contract with an authority address + * @param authority The address to set as the access manager authority + * @dev Can only be called during initialization + */ function __OzAccessManaged_init( address authority ) internal onlyInitializing { __AccessManaged_init(authority); } + /** + * @notice Checks if the caller has access through the OpenZeppelin AccessManager + * @dev Delegates access check to OpenZeppelin's _checkCanCall function + */ function _checkAccess() internal override { _checkCanCall(msg.sender, msg.data); } diff --git a/src/middleware/extensions/capture-timestamps/EpochCapture.sol b/src/middleware/extensions/capture-timestamps/EpochCapture.sol new file mode 100644 index 0000000..f1d3eed --- /dev/null +++ b/src/middleware/extensions/capture-timestamps/EpochCapture.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {BaseMiddleware} from "../../BaseMiddleware.sol"; + +abstract contract EpochCapture is BaseMiddleware { + uint48 public START_TIMESTAMP; // Start timestamp of the first epoch + uint48 public EPOCH_DURATION; // Duration of each epoch + + /* + * @notice initalizer of the Epochs contract. + * @param epochDuration The duration of each epoch. + */ + function __EpochCapture_init( + uint48 epochDuration + ) internal onlyInitializing { + EPOCH_DURATION = epochDuration; + START_TIMESTAMP = Time.timestamp(); + } + + /* + * @notice Returns the start timestamp for a given epoch. + * @param epoch The epoch number. + * @return The start timestamp. + */ + function getEpochStart( + uint48 epoch + ) public view returns (uint48) { + return START_TIMESTAMP + epoch * EPOCH_DURATION; + } + + /* + * @notice Returns the current epoch. + * @return The current epoch. + */ + function getCurrentEpoch() public view returns (uint48) { + return (Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION; + } + + /* + * @notice Returns the capture timestamp for the current epoch. + * @return The capture timestamp. + */ + function getCaptureTimestamp() public view override returns (uint48 timestamp) { + return getEpochStart(getCurrentEpoch()); + } +} diff --git a/src/middleware/extensions/capture-timestamps/TimestampCapture.sol b/src/middleware/extensions/capture-timestamps/TimestampCapture.sol new file mode 100644 index 0000000..e3a19d5 --- /dev/null +++ b/src/middleware/extensions/capture-timestamps/TimestampCapture.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {BaseMiddleware} from "../../BaseMiddleware.sol"; + +abstract contract TimestampCapture is BaseMiddleware { + /* + * @notice Returns the current timestamp minus 1 second. + * @return timestamp The current timestamp minus 1 second. + */ + function getCaptureTimestamp() public view override returns (uint48 timestamp) { + return Time.timestamp() - 1; + } +} diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index 4d4f152..a7b3cd5 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -3,7 +3,17 @@ pragma solidity ^0.8.25; import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; +/** + * @title ForcePauseSelfRegisterOperators + * @notice Extension of SelfRegisterOperators that allows authorized addresses to forcefully pause/unpause operators + * @dev Implements force pause/unpause functionality for both operators and operator-vault pairs + */ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { + /** + * @notice Forces an operator to be paused + * @param operator The address of the operator to pause + * @dev Can only be called by authorized addresses (checkAccess modifier) + */ function forcePauseOperator( address operator ) public checkAccess { @@ -11,6 +21,11 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { _pauseOperator(operator); } + /** + * @notice Forces an operator to be unpaused + * @param operator The address of the operator to unpause + * @dev Can only be called by authorized addresses (checkAccess modifier) + */ function forceUnpauseOperator( address operator ) public checkAccess { @@ -18,11 +33,23 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { _unpauseOperator(operator); } + /** + * @notice Forces a specific operator-vault pair to be paused + * @param operator The address of the operator + * @param vault The address of the vault + * @dev Can only be called by authorized addresses (checkAccess modifier) + */ function forcePauseOperatorVault(address operator, address vault) public checkAccess { _beforePauseOperatorVault(operator, vault); _pauseOperatorVault(operator, vault); } + /** + * @notice Forces a specific operator-vault pair to be unpaused + * @param operator The address of the operator + * @param vault The address of the vault + * @dev Can only be called by authorized addresses (checkAccess modifier) + */ function forceUnpauseOperatorVault(address operator, address vault) public checkAccess { _beforeUnpauseOperatorVault(operator, vault); _unpauseOperatorVault(operator, vault); diff --git a/src/middleware/extensions/operators/Operators.sol b/src/middleware/extensions/operators/Operators.sol index 3d8261f..94c682d 100644 --- a/src/middleware/extensions/operators/Operators.sol +++ b/src/middleware/extensions/operators/Operators.sol @@ -3,16 +3,32 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../../BaseMiddleware.sol"; +/** + * @title Operators + * @notice Base contract for managing operator registration, keys, and vault relationships + * @dev Provides core operator management functionality with hooks for customization + */ abstract contract Operators is BaseMiddleware { + /** + * @notice Registers a new operator with an optional vault association + * @param operator The address of the operator to register + * @param key The operator's public key + * @param vault Optional vault address to associate with the operator + */ function registerOperator(address operator, bytes memory key, address vault) public checkAccess { _beforeRegisterOperator(operator, key, vault); _registerOperator(operator); _updateKey(operator, key); if (vault != address(0)) { - registerOperatorVault(operator, vault); + _beforeRegisterOperatorVault(operator, vault); + _registerOperatorVault(operator, vault); } } + /** + * @notice Unregisters an operator + * @param operator The address of the operator to unregister + */ function unregisterOperator( address operator ) public checkAccess { @@ -20,6 +36,10 @@ abstract contract Operators is BaseMiddleware { _unregisterOperator(operator); } + /** + * @notice Pauses an operator + * @param operator The address of the operator to pause + */ function pauseOperator( address operator ) public checkAccess { @@ -27,6 +47,10 @@ abstract contract Operators is BaseMiddleware { _pauseOperator(operator); } + /** + * @notice Unpauses an operator + * @param operator The address of the operator to unpause + */ function unpauseOperator( address operator ) public checkAccess { @@ -34,47 +58,121 @@ abstract contract Operators is BaseMiddleware { _unpauseOperator(operator); } + /** + * @notice Updates an operator's public key + * @param operator The address of the operator + * @param key The new public key + */ function updateOperatorKey(address operator, bytes memory key) public checkAccess { _beforeUpdateOperatorKey(operator, key); _updateKey(operator, key); } + /** + * @notice Associates an operator with a vault + * @param operator The address of the operator + * @param vault The address of the vault + */ function registerOperatorVault(address operator, address vault) public checkAccess { require(isOperatorRegistered(operator), "Operator not registered"); _beforeRegisterOperatorVault(operator, vault); _registerOperatorVault(operator, vault); } + /** + * @notice Removes an operator's association with a vault + * @param operator The address of the operator + * @param vault The address of the vault + */ function unregisterOperatorVault(address operator, address vault) public checkAccess { _beforeUnregisterOperatorVault(operator, vault); _unregisterOperatorVault(operator, vault); } + /** + * @notice Pauses an operator's association with a specific vault + * @param operator The address of the operator + * @param vault The address of the vault + */ function pauseOperatorVault(address operator, address vault) public checkAccess { _beforePauseOperatorVault(operator, vault); _pauseOperatorVault(operator, vault); } + /** + * @notice Unpauses an operator's association with a specific vault + * @param operator The address of the operator + * @param vault The address of the vault + */ function unpauseOperatorVault(address operator, address vault) public checkAccess { _beforeUnpauseOperatorVault(operator, vault); _unpauseOperatorVault(operator, vault); } + /** + * @notice Hook called before updating an operator's key + * @param operator The operator address + * @param key The new key + */ function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} + /** + * @notice Hook called before registering an operator + * @param operator The operator address + * @param key The operator's key + * @param vault Optional vault address + */ function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} + + /** + * @notice Hook called before unregistering an operator + * @param operator The operator address + */ function _beforeUnregisterOperator( address operator ) internal virtual {} + + /** + * @notice Hook called before pausing an operator + * @param operator The operator address + */ function _beforePauseOperator( address operator ) internal virtual {} + + /** + * @notice Hook called before unpausing an operator + * @param operator The operator address + */ function _beforeUnpauseOperator( address operator ) internal virtual {} + /** + * @notice Hook called before registering an operator-vault pair + * @param operator The operator address + * @param vault The vault address + */ function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} + + /** + * @notice Hook called before unregistering an operator-vault pair + * @param operator The operator address + * @param vault The vault address + */ function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual {} + + /** + * @notice Hook called before pausing an operator-vault pair + * @param operator The operator address + * @param vault The vault address + */ function _beforePauseOperatorVault(address operator, address vault) internal virtual {} + + /** + * @notice Hook called before unpausing an operator-vault pair + * @param operator The operator address + * @param vault The vault address + */ function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual {} } diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index 048fe96..29ec1c6 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -6,6 +6,11 @@ import {BaseSig} from "../sigs/BaseSig.sol"; import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +/** + * @title SelfRegisterOperators + * @notice Contract for self-registration and management of operators with signature verification + * @dev Extends BaseMiddleware, BaseSig, and EIP712Upgradeable to provide signature-based operator management + */ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgradeable { error InvalidSignature(); @@ -27,14 +32,25 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad bytes32 private constant UNPAUSE_OPERATOR_VAULT_TYPEHASH = keccak256("UnpauseOperatorVault(address operator,address vault,uint256 nonce)"); + /// @notice Mapping of operator addresses to their nonces for signature verification mapping(address => uint256) public nonces; + /** + * @notice Initializes the contract with EIP712 domain separator + * @param name The name to use for the EIP712 domain separator + */ function __SelfRegisterOperators_init( string memory name ) internal onlyInitializing { __EIP712_init(name, "1.0"); } + /** + * @notice Allows an operator to self-register with a key and optional vault + * @param key The operator's public key + * @param vault Optional vault address to associate with the operator + * @param signature Signature proving ownership of the key + */ function registerOperator(bytes memory key, address vault, bytes memory signature) public { _verifyKey(msg.sender, key, signature); _beforeRegisterOperator(msg.sender, key, vault); @@ -47,6 +63,14 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad } } + /** + * @notice Registers an operator on behalf of another address with signature verification + * @param operator The address of the operator to register + * @param key The operator's public key + * @param vault Optional vault address to associate + * @param signature EIP712 signature authorizing registration + * @param keySignature Signature proving ownership of the key + */ function registerOperator( address operator, bytes memory key, @@ -70,11 +94,19 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad } } + /** + * @notice Allows an operator to unregister themselves + */ function unregisterOperator() public { _beforeUnregisterOperator(msg.sender); _unregisterOperator(msg.sender); } + /** + * @notice Unregisters an operator with signature verification + * @param operator The address of the operator to unregister + * @param signature EIP712 signature authorizing unregistration + */ function unregisterOperator(address operator, bytes memory signature) public { _beforeUnregisterOperator(operator); _verifyEIP712( @@ -83,22 +115,38 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _unregisterOperator(operator); } + /** + * @notice Allows an operator to pause themselves + */ function pauseOperator() public { _beforePauseOperator(msg.sender); _pauseOperator(msg.sender); } + /** + * @notice Pauses an operator with signature verification + * @param operator The address of the operator to pause + * @param signature EIP712 signature authorizing pause + */ function pauseOperator(address operator, bytes memory signature) public { _beforePauseOperator(operator); _verifyEIP712(operator, keccak256(abi.encode(PAUSE_OPERATOR_TYPEHASH, operator, nonces[operator]++)), signature); _pauseOperator(operator); } + /** + * @notice Allows an operator to unpause themselves + */ function unpauseOperator() public { _beforeUnpauseOperator(msg.sender); _unpauseOperator(msg.sender); } + /** + * @notice Unpauses an operator with signature verification + * @param operator The address of the operator to unpause + * @param signature EIP712 signature authorizing unpause + */ function unpauseOperator(address operator, bytes memory signature) public { _beforeUnpauseOperator(operator); _verifyEIP712( @@ -107,12 +155,24 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _unpauseOperator(operator); } + /** + * @notice Allows an operator to update their own key + * @param key The new public key + * @param signature Signature proving ownership of the key + */ function updateOperatorKey(bytes memory key, bytes memory signature) public { _verifyKey(msg.sender, key, signature); _beforeUpdateOperatorKey(msg.sender, key); _updateKey(msg.sender, key); } + /** + * @notice Updates an operator's key with signature verification + * @param operator The address of the operator + * @param key The new public key + * @param signature EIP712 signature authorizing key update + * @param keySignature Signature proving ownership of the new key + */ function updateOperatorKey( address operator, bytes memory key, @@ -129,6 +189,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _updateKey(operator, key); } + /** + * @notice Allows an operator to register a vault association + * @param vault The address of the vault to associate + */ function registerOperatorVault( address vault ) public { @@ -137,6 +201,12 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _registerOperatorVault(msg.sender, vault); } + /** + * @notice Registers a vault association with signature verification + * @param operator The address of the operator + * @param vault The address of the vault + * @param signature EIP712 signature authorizing vault registration + */ function registerOperatorVault(address operator, address vault, bytes memory signature) public { require(isOperatorRegistered(operator), "Operator not registered"); _beforeRegisterOperatorVault(operator, vault); @@ -148,6 +218,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _registerOperatorVault(operator, vault); } + /** + * @notice Allows an operator to unregister a vault association + * @param vault The address of the vault to unregister + */ function unregisterOperatorVault( address vault ) public { @@ -155,6 +229,12 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _unregisterOperatorVault(msg.sender, vault); } + /** + * @notice Unregisters a vault association with signature verification + * @param operator The address of the operator + * @param vault The address of the vault + * @param signature EIP712 signature authorizing vault unregistration + */ function unregisterOperatorVault(address operator, address vault, bytes memory signature) public { _beforeUnregisterOperatorVault(operator, vault); _verifyEIP712( @@ -165,6 +245,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _unregisterOperatorVault(operator, vault); } + /** + * @notice Allows an operator to pause a vault association + * @param vault The address of the vault to pause + */ function pauseOperatorVault( address vault ) public { @@ -172,6 +256,12 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _pauseOperatorVault(msg.sender, vault); } + /** + * @notice Pauses a vault association with signature verification + * @param operator The address of the operator + * @param vault The address of the vault + * @param signature EIP712 signature authorizing vault pause + */ function pauseOperatorVault(address operator, address vault, bytes memory signature) public { _beforePauseOperatorVault(operator, vault); _verifyEIP712( @@ -182,6 +272,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _pauseOperatorVault(operator, vault); } + /** + * @notice Allows an operator to unpause a vault association + * @param vault The address of the vault to unpause + */ function unpauseOperatorVault( address vault ) public { @@ -189,6 +283,12 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _unpauseOperatorVault(msg.sender, vault); } + /** + * @notice Unpauses a vault association with signature verification + * @param operator The address of the operator + * @param vault The address of the vault + * @param signature EIP712 signature authorizing vault unpause + */ function unpauseOperatorVault(address operator, address vault, bytes memory signature) public { _beforeUnpauseOperatorVault(operator, vault); _verifyEIP712( @@ -199,31 +299,94 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad _unpauseOperatorVault(operator, vault); } + /** + * @notice Verifies a key signature + * @param operator The address of the operator + * @param key The public key to verify + * @param signature The signature to verify + */ function _verifyKey(address operator, bytes memory key, bytes memory signature) internal view { if (key.length != 0 && !_verifyKeySignature(operator, key, signature)) { revert InvalidSignature(); } } + /** + * @notice Verifies an EIP712 signature + * @param operator The address of the operator + * @param structHash The hash of the EIP712 struct + * @param signature The signature to verify + */ function _verifyEIP712(address operator, bytes32 structHash, bytes memory signature) internal view { if (!SignatureChecker.isValidSignatureNow(operator, _hashTypedDataV4(structHash), signature)) { revert InvalidSignature(); } } + /** + * @notice Hook called before updating an operator's key + * @param operator The operator address + * @param key The new key + */ function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} + + /** + * @notice Hook called before registering an operator + * @param operator The operator address + * @param key The operator's key + * @param vault Optional vault address + */ function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} + + /** + * @notice Hook called before unregistering an operator + * @param operator The operator address + */ function _beforeUnregisterOperator( address operator ) internal virtual {} + + /** + * @notice Hook called before pausing an operator + * @param operator The operator address + */ function _beforePauseOperator( address operator ) internal virtual {} + + /** + * @notice Hook called before unpausing an operator + * @param operator The operator address + */ function _beforeUnpauseOperator( address operator ) internal virtual {} + + /** + * @notice Hook called before registering an operator vault + * @param operator The operator address + * @param vault The vault address + */ function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} + + /** + * @notice Hook called before unregistering an operator vault + * @param operator The operator address + * @param vault The vault address + */ function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual {} + + /** + * @notice Hook called before pausing an operator vault + * @param operator The operator address + * @param vault The vault address + */ function _beforePauseOperatorVault(address operator, address vault) internal virtual {} + + /** + * @notice Hook called before unpausing an operator vault + * @param operator The operator address + * @param vault The vault address + */ function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual {} } diff --git a/src/middleware/extensions/sigs/BaseSig.sol b/src/middleware/extensions/sigs/BaseSig.sol index 70f2e26..f86f9d5 100644 --- a/src/middleware/extensions/sigs/BaseSig.sol +++ b/src/middleware/extensions/sigs/BaseSig.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.25; abstract contract BaseSig { /** - * @notice Verifies the signature of a key - * @param operator The operator to verify - * @param key_ The key to verify + * @notice Verifies that a signature was created by the owner of a key + * @param operator The address of the operator that owns the key + * @param key_ The public key to verify against * @param signature The signature to verify - * @return True if the signature is valid, false otherwise + * @return True if the signature was created by the key owner, false otherwise */ function _verifyKeySignature( address operator, diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index 07967c0..2b6746a 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -4,9 +4,22 @@ pragma solidity ^0.8.25; import {BaseSig} from "./BaseSig.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +/** + * @title ECDSASig + * @notice Contract for verifying ECDSA signatures against operator keys + * @dev Implements BaseSig interface using OpenZeppelin's ECDSA library + */ abstract contract ECDSASig is BaseSig { using ECDSA for bytes32; + /** + * @notice Verifies that a signature was created by the owner of a key + * @param operator The address of the operator that owns the key + * @param key_ The public key to verify against, encoded as bytes + * @param signature The ECDSA signature to verify + * @return True if the signature was created by the key owner, false otherwise + * @dev The key is expected to be a bytes32 that can be converted to an Ethereum address + */ function _verifyKeySignature( address operator, bytes memory key_, @@ -19,6 +32,13 @@ abstract contract ECDSASig is BaseSig { return signer == keyAddress && signer != address(0); } + /** + * @notice Recovers the signer address from a hash and signature + * @param hash The message hash that was signed + * @param signature The ECDSA signature + * @return The address that created the signature + * @dev Wrapper around OpenZeppelin's ECDSA.recover + */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { return hash.recover(signature); } diff --git a/src/middleware/extensions/sigs/Ed25519Sig.sol b/src/middleware/extensions/sigs/Ed25519Sig.sol index 1b138f0..0c9d17c 100644 --- a/src/middleware/extensions/sigs/Ed25519Sig.sol +++ b/src/middleware/extensions/sigs/Ed25519Sig.sol @@ -4,7 +4,21 @@ pragma solidity ^0.8.25; import {Ed25519} from "../../../libraries/Ed25519.sol"; import {BaseSig} from "./BaseSig.sol"; +/** + * @title Ed25519Sig + * @notice Contract for verifying Ed25519 signatures against operator keys + * @dev Implements BaseSig interface using Ed25519 signature verification + */ abstract contract Ed25519Sig is BaseSig { + /** + * @notice Verifies that a signature was created by the owner of a key + * @param operator The address of the operator that owns the key + * @param key_ The public key to verify against, encoded as bytes + * @param signature The Ed25519 signature to verify, containing r and s components + * @return True if the signature was created by the key owner, false otherwise + * @dev The key is expected to be a bytes32 that represents an Ed25519 public key + * The signature is expected to be 64 bytes containing r (32 bytes) and s (32 bytes) + */ function _verifyKeySignature( address operator, bytes memory key_, From 4bd51ed8a988ed767b435db3ae81270b6ad78cf1 Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 21 Nov 2024 14:19:20 +0700 Subject: [PATCH 058/115] refactor: add check for ed25519sig contract --- src/middleware/extensions/sigs/Ed25519Sig.sol | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/middleware/extensions/sigs/Ed25519Sig.sol b/src/middleware/extensions/sigs/Ed25519Sig.sol index 0c9d17c..a9ec404 100644 --- a/src/middleware/extensions/sigs/Ed25519Sig.sol +++ b/src/middleware/extensions/sigs/Ed25519Sig.sol @@ -25,13 +25,25 @@ abstract contract Ed25519Sig is BaseSig { bytes memory signature ) internal pure override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); + bytes32 message = keccak256(abi.encodePacked(operator, key)); + return check(key, signature, message); + } + + /** + * @notice Checks an Ed25519 signature against a message and public key + * @param key The Ed25519 public key + * @param signature The Ed25519 signature to verify + * @param message The message that was signed + * @return True if the signature is valid, false otherwise + * @dev Wrapper around Ed25519.check + */ + function check(bytes32 key, bytes memory signature, bytes32 message) internal pure returns (bool) { bytes32 r; bytes32 s; assembly { r := mload(add(signature, 32)) s := mload(add(signature, 64)) } - bytes32 message = keccak256(abi.encodePacked(operator, key)); return Ed25519.check(key, r, s, message, bytes9(0)); } } From 07ba6f1a7bc76e0285bcb1403be79b9080b8f242 Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 21 Nov 2024 14:33:09 +0700 Subject: [PATCH 059/115] fix: disallow unpause/unregister in forceoperators --- .../ForcePauseSelfRegisterOperators.sol | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index a7b3cd5..23008a7 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -5,10 +5,19 @@ import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; /** * @title ForcePauseSelfRegisterOperators - * @notice Extension of SelfRegisterOperators that allows authorized addresses to forcefully pause/unpause operators - * @dev Implements force pause/unpause functionality for both operators and operator-vault pairs + * @notice Extension of SelfRegisterOperators that allows authorized addresses to forcefully pause operators + * @dev Implements force pause functionality and prevents unpausing of force-paused operators */ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { + /// @notice Mapping to track which operators have been force paused + mapping(address => bool) public forcePaused; + + /// @notice Mapping to track which operator-vault pairs have been force paused + mapping(address => mapping(address => bool)) public forcePausedVault; + + error OperatorForcePaused(); + error OperatorVaultForcePaused(); + /** * @notice Forces an operator to be paused * @param operator The address of the operator to pause @@ -17,6 +26,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { function forcePauseOperator( address operator ) public checkAccess { + forcePaused[operator] = true; _beforePauseOperator(operator); _pauseOperator(operator); } @@ -29,6 +39,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { function forceUnpauseOperator( address operator ) public checkAccess { + forcePaused[operator] = false; _beforeUnpauseOperator(operator); _unpauseOperator(operator); } @@ -40,6 +51,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @dev Can only be called by authorized addresses (checkAccess modifier) */ function forcePauseOperatorVault(address operator, address vault) public checkAccess { + forcePausedVault[operator][vault] = true; _beforePauseOperatorVault(operator, vault); _pauseOperatorVault(operator, vault); } @@ -51,7 +63,50 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @dev Can only be called by authorized addresses (checkAccess modifier) */ function forceUnpauseOperatorVault(address operator, address vault) public checkAccess { + forcePausedVault[operator][vault] = false; _beforeUnpauseOperatorVault(operator, vault); _unpauseOperatorVault(operator, vault); } + + /** + * @notice Override to prevent unpausing force-paused operators + * @param operator The operator address + */ + function _beforeUnpauseOperator( + address operator + ) internal virtual override { + if (forcePaused[operator]) revert OperatorForcePaused(); + super._beforeUnpauseOperator(operator); + } + + /** + * @notice Override to prevent unregistering force-paused operators + * @param operator The operator address + */ + function _beforeUnregisterOperator( + address operator + ) internal virtual override { + if (forcePaused[operator]) revert OperatorForcePaused(); + super._beforeUnregisterOperator(operator); + } + + /** + * @notice Override to prevent unpausing force-paused operator-vault pairs + * @param operator The operator address + * @param vault The vault address + */ + function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual override { + if (forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); + super._beforeUnpauseOperatorVault(operator, vault); + } + + /** + * @notice Override to prevent unregistering force-paused operator-vault pairs + * @param operator The operator address + * @param vault The vault address + */ + function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual override { + if (forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); + super._beforeUnregisterOperatorVault(operator, vault); + } } From 75d9a732f83fd82ff0ff878a30811a356b670c49 Mon Sep 17 00:00:00 2001 From: Kresh Date: Thu, 21 Nov 2024 11:55:15 +0400 Subject: [PATCH 060/115] chore: adjust workflows --- .github/workflows/lint.yml | 24 ++++++++++++++++++++++++ .github/workflows/test.yml | 25 ++++++++++--------------- 2 files changed, 34 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..2e6997d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,24 @@ +name: lint + +on: + push: + branches: + - main + pull_request: + +jobs: + run-linters: + name: Run linters + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Lint + run: forge fmt --check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9282e82..4233e27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,17 +1,16 @@ name: test -on: workflow_dispatch - -env: - FOUNDRY_PROFILE: ci +on: + push: + branches: + - main + pull_request: jobs: - check: - strategy: - fail-fast: true - - name: Foundry project + run-tests: + name: Run tests runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 with: @@ -22,13 +21,9 @@ jobs: with: version: nightly - - name: Run Forge build - run: | - forge --version - forge build --sizes - id: build - - name: Run Forge tests run: | forge test -vvv id: test + env: + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} From 9787a88d984d97f2c37dd5fb0ca0f0dabe4657a7 Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 21 Nov 2024 16:11:56 +0700 Subject: [PATCH 061/115] feat: make direct storage access --- .../SimplePosMiddleware.sol | 2 +- .../sqrt-task-network/SqrtTaskMiddleware.sol | 2 +- src/key-storage/KeyStorage256.sol | 53 ++++--- src/key-storage/KeyStorageBytes.sol | 49 ++++-- src/managers/BaseManager.sol | 61 +++++-- src/managers/OperatorManager.sol | 49 ++++-- src/managers/VaultManager.sol | 150 +++++++++++------- .../access-managers/OwnableAccessManager.sol | 45 ++++-- .../capture-timestamps/EpochCapture.sol | 28 +++- .../ForcePauseSelfRegisterOperators.sol | 42 +++-- .../operators/SelfRegisterOperators.sol | 51 ++++-- 11 files changed, 380 insertions(+), 152 deletions(-) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 0e9a23f..9f8620b 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -157,7 +157,7 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableA address vault = params.vaults[i]; for (uint256 j; j < params.subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK.subnetwork(uint96(params.subnetworks[j])); + bytes32 subnetwork = NETWORK().subnetwork(uint96(params.subnetworks[j])); uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( subnetwork, params.operator, params.epochStart, stakeHints[i][j] ); diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 2f240b1..a08c458 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -133,7 +133,7 @@ contract SqrtTaskMiddleware is revert InvalidHints(); } - bytes32 subnetwork = NETWORK.subnetwork(0); + bytes32 subnetwork = NETWORK().subnetwork(0); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; uint256 slashAmount = IBaseDelegator(IVault(vault).delegator()).stakeAt( diff --git a/src/key-storage/KeyStorage256.sol b/src/key-storage/KeyStorage256.sol index f9d176a..e153e41 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/key-storage/KeyStorage256.sol @@ -22,10 +22,23 @@ abstract contract KeyStorage256 is BaseMiddleware { bytes32 private constant ZERO_BYTES32 = bytes32(0); uint256 private constant MAX_DISABLED_KEYS = 1; - /// @notice Mapping from operator addresses to their keys - mapping(address => PauseableEnumerableSet.Bytes32Set) internal _keys; - /// @notice Mapping from keys to operator addresses - mapping(bytes32 => address) internal _keyToOperator; + struct KeyStorage256Storage { + /// @notice Mapping from operator addresses to their keys + mapping(address => PauseableEnumerableSet.Bytes32Set) keys; + /// @notice Mapping from keys to operator addresses + mapping(bytes32 => address) keyToOperator; + } + + // keccak256(abi.encode(uint256(keccak256("symbioticfi.storage.KeyStorage256")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant KeyStorage256StorageLocation = + 0x00c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c800; + + function _getStorage() private pure returns (KeyStorage256Storage storage s) { + bytes32 location = KeyStorage256StorageLocation; + assembly { + s.slot := location + } + } /** * @notice Gets the operator address associated with a key @@ -35,7 +48,8 @@ abstract contract KeyStorage256 is BaseMiddleware { function operatorByKey( bytes memory key ) public view override returns (address) { - return _keyToOperator[abi.decode(key, (bytes32))]; + KeyStorage256Storage storage s = _getStorage(); + return s.keyToOperator[abi.decode(key, (bytes32))]; } /** @@ -46,7 +60,8 @@ abstract contract KeyStorage256 is BaseMiddleware { function operatorKey( address operator ) public view override returns (bytes memory) { - bytes32[] memory active = _keys[operator].getActive(getCaptureTimestamp()); + KeyStorage256Storage storage s = _getStorage(); + bytes32[] memory active = s.keys[operator].getActive(getCaptureTimestamp()); if (active.length == 0) { return abi.encode(ZERO_BYTES32); } @@ -60,7 +75,8 @@ abstract contract KeyStorage256 is BaseMiddleware { * @return True if the key was active at the timestamp, false otherwise */ function keyWasActiveAt(uint48 timestamp, bytes32 key) public view returns (bool) { - return _keys[_keyToOperator[key]].wasActiveAt(timestamp, key); + KeyStorage256Storage storage s = _getStorage(); + return s.keys[s.keyToOperator[key]].wasActiveAt(timestamp, key); } /** @@ -72,33 +88,34 @@ abstract contract KeyStorage256 is BaseMiddleware { * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key_) internal override { + KeyStorage256Storage storage s = _getStorage(); bytes32 key = abi.decode(key_, (bytes32)); - if (_keyToOperator[key] != address(0)) { + if (s.keyToOperator[key] != address(0)) { revert DuplicateKey(); } // check if we have reached the max number of disabled keys // this allow us to limit the number times we can change the key - if (key != ZERO_BYTES32 && _keys[operator].length() > MAX_DISABLED_KEYS + 1) { + if (key != ZERO_BYTES32 && s.keys[operator].length() > MAX_DISABLED_KEYS + 1) { revert MaxDisabledKeysReached(); } - if (_keys[operator].length() > 0) { + if (s.keys[operator].length() > 0) { // try to remove disabled keys - bytes32 prevKey = _keys[operator].array[0].value; - if (_keys[operator].checkUnregister(Time.timestamp(), SLASHING_WINDOW, prevKey)) { - _keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW, prevKey); - delete _keyToOperator[prevKey]; - } else if (_keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { - _keys[operator].pause(Time.timestamp(), prevKey); + bytes32 prevKey = s.keys[operator].array[0].value; + if (s.keys[operator].checkUnregister(Time.timestamp(), SLASHING_WINDOW(), prevKey)) { + s.keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW(), prevKey); + delete s.keyToOperator[prevKey]; + } else if (s.keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { + s.keys[operator].pause(Time.timestamp(), prevKey); } } if (key != ZERO_BYTES32) { // register the new key - _keys[operator].register(Time.timestamp(), key); - _keyToOperator[key] = operator; + s.keys[operator].register(Time.timestamp(), key); + s.keyToOperator[key] = operator; } } } diff --git a/src/key-storage/KeyStorageBytes.sol b/src/key-storage/KeyStorageBytes.sol index 392756a..0f0b2b2 100644 --- a/src/key-storage/KeyStorageBytes.sol +++ b/src/key-storage/KeyStorageBytes.sol @@ -22,8 +22,21 @@ abstract contract KeyStorageBytes is BaseManager { bytes private constant ZERO_BYTES = ""; bytes32 private constant ZERO_BYTES_HASH = keccak256(""); - mapping(address => PauseableEnumerableSet.BytesSet) internal _keys; - mapping(bytes => address) internal _keyToOperator; + struct KeyStorageBytesStorage { + mapping(address => PauseableEnumerableSet.BytesSet) _keys; + mapping(bytes => address) _keyToOperator; + } + + // keccak256(abi.encode(uint256(keccak256("symbioticfi.storage.KeyStorageBytes")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant KeyStorageBytesStorageLocation = + 0x00c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c800; + + function _getStorage() private pure returns (KeyStorageBytesStorage storage s) { + bytes32 location = KeyStorageBytesStorageLocation; + assembly { + s.slot := location + } + } /** * @notice Gets the operator address associated with a key @@ -33,7 +46,8 @@ abstract contract KeyStorageBytes is BaseManager { function operatorByKey( bytes memory key ) public view returns (address) { - return _keyToOperator[key]; + KeyStorageBytesStorage storage $ = _getStorage(); + return $._keyToOperator[key]; } /** @@ -44,7 +58,8 @@ abstract contract KeyStorageBytes is BaseManager { function operatorKey( address operator ) public view returns (bytes memory) { - bytes[] memory active = _keys[operator].getActive(getCaptureTimestamp()); + KeyStorageBytesStorage storage $ = _getStorage(); + bytes[] memory active = $._keys[operator].getActive(getCaptureTimestamp()); if (active.length == 0) { return ZERO_BYTES; } @@ -58,7 +73,8 @@ abstract contract KeyStorageBytes is BaseManager { * @return True if the key was active at the timestamp, false otherwise */ function keyWasActiveAt(uint48 timestamp, bytes memory key) public view returns (bool) { - return _keys[_keyToOperator[key]].wasActiveAt(timestamp, key); + KeyStorageBytesStorage storage $ = _getStorage(); + return $._keys[$._keyToOperator[key]].wasActiveAt(timestamp, key); } /** @@ -70,33 +86,34 @@ abstract contract KeyStorageBytes is BaseManager { * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key) internal { + KeyStorageBytesStorage storage $ = _getStorage(); bytes32 keyHash = keccak256(key); - if (_keyToOperator[key] != address(0)) { + if ($._keyToOperator[key] != address(0)) { revert DuplicateKey(); } // check if we have reached the max number of disabled keys // this allow us to limit the number times we can change the key - if (keyHash != ZERO_BYTES_HASH && _keys[operator].length() > MAX_DISABLED_KEYS + 1) { + if (keyHash != ZERO_BYTES_HASH && $._keys[operator].length() > MAX_DISABLED_KEYS + 1) { revert MaxDisabledKeysReached(); } - if (_keys[operator].length() > 0) { + if ($._keys[operator].length() > 0) { // try to remove disabled keys - bytes memory prevKey = _keys[operator].array[0].value; - if (_keys[operator].checkUnregister(Time.timestamp(), SLASHING_WINDOW, prevKey)) { - _keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW, prevKey); - delete _keyToOperator[prevKey]; - } else if (_keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { - _keys[operator].pause(Time.timestamp(), prevKey); + bytes memory prevKey = $._keys[operator].array[0].value; + if ($._keys[operator].checkUnregister(Time.timestamp(), SLASHING_WINDOW(), prevKey)) { + $._keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW(), prevKey); + delete $._keyToOperator[prevKey]; + } else if ($._keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { + $._keys[operator].pause(Time.timestamp(), prevKey); } } if (keyHash != ZERO_BYTES_HASH) { // register the new key - _keys[operator].register(Time.timestamp(), key); - _keyToOperator[key] = operator; + $._keys[operator].register(Time.timestamp(), key); + $._keyToOperator[key] = operator; } } } diff --git a/src/managers/BaseManager.sol b/src/managers/BaseManager.sol index 51d6d4e..e8d06a0 100644 --- a/src/managers/BaseManager.sol +++ b/src/managers/BaseManager.sol @@ -5,15 +5,28 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; abstract contract BaseManager is Initializable { - address public NETWORK; // Address of the network - uint48 public SLASHING_WINDOW; // Duration of the slashing window - address public VAULT_REGISTRY; // Address of the vault registry - address public OPERATOR_REGISTRY; // Address of the operator registry - address public OPERATOR_NET_OPTIN; // Address of the operator network opt-in service - uint64 public constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type uint64 public constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type + /// @custom:storage-location erc7201:symbiotic.storage.BaseManager + struct BaseManagerStorage { + address _network; // Address of the network + uint48 _slashingWindow; // Duration of the slashing window + address _vaultRegistry; // Address of the vault registry + address _operatorRegistry; // Address of the operator registry + address _operatorNetOptin; // Address of the operator network opt-in service + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant BaseManagerStorageLocation = + 0xc0625060998ed0caa490c91b79737728a736aeb8ed65081a52c24738d2978d00; + + function _getBaseManagerStorage() private pure returns (BaseManagerStorage storage $) { + assembly { + $.slot := BaseManagerStorageLocation + } + } + /** * @notice Initializes the BaseManager contract * @param network The address of the network @@ -29,11 +42,37 @@ abstract contract BaseManager is Initializable { address operatorRegistry, address operatorNetOptIn ) public virtual initializer { - NETWORK = network; - SLASHING_WINDOW = slashingWindow; - VAULT_REGISTRY = vaultRegistry; - OPERATOR_REGISTRY = operatorRegistry; - OPERATOR_NET_OPTIN = operatorNetOptIn; + BaseManagerStorage storage $ = _getBaseManagerStorage(); + $._network = network; + $._slashingWindow = slashingWindow; + $._vaultRegistry = vaultRegistry; + $._operatorRegistry = operatorRegistry; + $._operatorNetOptin = operatorNetOptIn; + } + + function NETWORK() public view returns (address) { + BaseManagerStorage storage $ = _getBaseManagerStorage(); + return $._network; + } + + function SLASHING_WINDOW() public view returns (uint48) { + BaseManagerStorage storage $ = _getBaseManagerStorage(); + return $._slashingWindow; + } + + function VAULT_REGISTRY() public view returns (address) { + BaseManagerStorage storage $ = _getBaseManagerStorage(); + return $._vaultRegistry; + } + + function OPERATOR_REGISTRY() public view returns (address) { + BaseManagerStorage storage $ = _getBaseManagerStorage(); + return $._operatorRegistry; + } + + function OPERATOR_NET_OPTIN() public view returns (address) { + BaseManagerStorage storage $ = _getBaseManagerStorage(); + return $._operatorNetOptin; } /** diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index fcfa677..e49a70b 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -19,14 +19,28 @@ abstract contract OperatorManager is BaseManager { error OperatorNotRegistered(); error OperatorAlreadyRegistred(); - PauseableEnumerableSet.AddressSet internal _operators; + /// @custom:storage-location erc7201:symbiotic.storage.OperatorManager + struct OperatorManagerStorage { + PauseableEnumerableSet.AddressSet _operators; + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.OperatorManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant OperatorManagerStorageLocation = + 0x819b71b9578fceb0968f87c9e32befffbf335e42bec212b90debd10f2f3fdb00; + + function _getOperatorManagerStorage() private pure returns (OperatorManagerStorage storage $) { + assembly { + $.slot := OperatorManagerStorageLocation + } + } /** * @notice Returns the total number of registered operators, including both active and inactive * @return The number of registered operators */ function operatorsLength() public view returns (uint256) { - return _operators.length(); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operators.length(); } /** @@ -39,7 +53,8 @@ abstract contract OperatorManager is BaseManager { function operatorWithTimesAt( uint256 pos ) public view returns (address, uint48, uint48) { - return _operators.at(pos); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operators.at(pos); } /** @@ -47,7 +62,8 @@ abstract contract OperatorManager is BaseManager { * @return Array of addresses representing the active operators */ function activeOperators() public view returns (address[] memory) { - return _operators.getActive(getCaptureTimestamp()); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operators.getActive(getCaptureTimestamp()); } /** @@ -58,7 +74,8 @@ abstract contract OperatorManager is BaseManager { function activeOperatorsAt( uint48 timestamp ) public view returns (address[] memory) { - return _operators.getActive(timestamp); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operators.getActive(timestamp); } /** @@ -68,7 +85,8 @@ abstract contract OperatorManager is BaseManager { * @return True if the operator was active at the timestamp, false otherwise */ function operatorWasActiveAt(uint48 timestamp, address operator) public view returns (bool) { - return _operators.wasActiveAt(timestamp, operator); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operators.wasActiveAt(timestamp, operator); } /** @@ -79,7 +97,8 @@ abstract contract OperatorManager is BaseManager { function isOperatorRegistered( address operator ) public view returns (bool) { - return _operators.contains(operator); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operators.contains(operator); } /** @@ -91,15 +110,16 @@ abstract contract OperatorManager is BaseManager { function _registerOperator( address operator ) internal { - if (!IRegistry(OPERATOR_REGISTRY).isEntity(operator)) { + if (!IRegistry(OPERATOR_REGISTRY()).isEntity(operator)) { revert NotOperator(); } - if (!IOptInService(OPERATOR_NET_OPTIN).isOptedIn(operator, NETWORK)) { + if (!IOptInService(OPERATOR_NET_OPTIN()).isOptedIn(operator, NETWORK())) { revert OperatorNotOptedIn(); } - _operators.register(Time.timestamp(), operator); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + $._operators.register(Time.timestamp(), operator); } /** @@ -109,7 +129,8 @@ abstract contract OperatorManager is BaseManager { function _pauseOperator( address operator ) internal { - _operators.pause(Time.timestamp(), operator); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + $._operators.pause(Time.timestamp(), operator); } /** @@ -119,7 +140,8 @@ abstract contract OperatorManager is BaseManager { function _unpauseOperator( address operator ) internal { - _operators.unpause(Time.timestamp(), SLASHING_WINDOW, operator); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + $._operators.unpause(Time.timestamp(), SLASHING_WINDOW(), operator); } /** @@ -129,6 +151,7 @@ abstract contract OperatorManager is BaseManager { function _unregisterOperator( address operator ) internal { - _operators.unregister(Time.timestamp(), SLASHING_WINDOW, operator); + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + $._operators.unregister(Time.timestamp(), SLASHING_WINDOW(), operator); } } diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 6376189..b87cedc 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -39,10 +39,23 @@ abstract contract VaultManager is BaseManager { error NonVetoSlasher(); error TooOldTimestampSlash(); - PauseableEnumerableSet.Uint160Set internal _subnetworks; - PauseableEnumerableSet.AddressSet internal _sharedVaults; - mapping(address => PauseableEnumerableSet.AddressSet) internal _operatorVaults; - EnumerableMap.AddressToAddressMap internal _vaultOperator; + /// @custom:storage-location erc7201:symbiotic.storage.VaultManager + struct VaultManagerStorage { + PauseableEnumerableSet.Uint160Set _subnetworks; + PauseableEnumerableSet.AddressSet _sharedVaults; + mapping(address => PauseableEnumerableSet.AddressSet) _operatorVaults; + EnumerableMap.AddressToAddressMap _vaultOperator; + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.VaultManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant VaultManagerStorageLocation = + 0x2d53fdb1dd96fda6f8b11e221b5832c7a1e485a4745ba2d81260927c2c462900; + + function _getVaultManagerStorage() private pure returns (VaultManagerStorage storage $) { + assembly { + $.slot := VaultManagerStorageLocation + } + } /** * @dev Struct containing information about a slash response @@ -63,7 +76,8 @@ abstract contract VaultManager is BaseManager { * @return uint256 The count of registered subnetworks */ function subnetworksLength() public view returns (uint256) { - return _subnetworks.length(); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.length(); } /** @@ -76,7 +90,8 @@ abstract contract VaultManager is BaseManager { function subnetworkWithTimesAt( uint256 pos ) public view returns (uint160, uint48, uint48) { - return _subnetworks.at(pos); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.at(pos); } /** @@ -84,7 +99,8 @@ abstract contract VaultManager is BaseManager { * @return uint160[] Array of active subnetwork addresses */ function activeSubnetworks() public view returns (uint160[] memory) { - return _subnetworks.getActive(getCaptureTimestamp()); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.getActive(getCaptureTimestamp()); } /** @@ -95,7 +111,8 @@ abstract contract VaultManager is BaseManager { function activeSubnetworksAt( uint48 timestamp ) public view returns (uint160[] memory) { - return _subnetworks.getActive(timestamp); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.getActive(timestamp); } /** @@ -105,7 +122,8 @@ abstract contract VaultManager is BaseManager { * @return bool True if the subnetwork was active at the timestamp */ function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) public view returns (bool) { - return _subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); } /** @@ -113,7 +131,8 @@ abstract contract VaultManager is BaseManager { * @return uint256 The count of shared vaults */ function sharedVaultsLength() public view returns (uint256) { - return _sharedVaults.length(); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.length(); } /** @@ -126,7 +145,8 @@ abstract contract VaultManager is BaseManager { function sharedVaultWithTimesAt( uint256 pos ) public view returns (address, uint48, uint48) { - return _sharedVaults.at(pos); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.at(pos); } /** @@ -134,7 +154,8 @@ abstract contract VaultManager is BaseManager { * @return address[] Array of active shared vault addresses */ function activeSharedVaults() public view returns (address[] memory) { - return _sharedVaults.getActive(getCaptureTimestamp()); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.getActive(getCaptureTimestamp()); } /** @@ -145,7 +166,8 @@ abstract contract VaultManager is BaseManager { function operatorVaultsLength( address operator ) public view returns (uint256) { - return _operatorVaults[operator].length(); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._operatorVaults[operator].length(); } /** @@ -157,7 +179,8 @@ abstract contract VaultManager is BaseManager { * @return uint48 The time when the vault was disabled */ function operatorVaultWithTimesAt(address operator, uint256 pos) public view returns (address, uint48, uint48) { - return _operatorVaults[operator].at(pos); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._operatorVaults[operator].at(pos); } /** @@ -168,7 +191,8 @@ abstract contract VaultManager is BaseManager { function activeOperatorVaults( address operator ) public view returns (address[] memory) { - return _operatorVaults[operator].getActive(getCaptureTimestamp()); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._operatorVaults[operator].getActive(getCaptureTimestamp()); } /** @@ -176,19 +200,20 @@ abstract contract VaultManager is BaseManager { * @return address[] Array of all active vault addresses */ function activeVaults() public view virtual returns (address[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); uint48 timestamp = getCaptureTimestamp(); - address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); + address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); uint256 len = activeSharedVaults_.length; - address[] memory vaults = new address[](len + _vaultOperator.length()); + address[] memory vaults = new address[](len + $._vaultOperator.length()); for (uint256 i; i < len; ++i) { vaults[i] = activeSharedVaults_[i]; } - uint256 operatorVaultsLen = _vaultOperator.length(); + uint256 operatorVaultsLen = $._vaultOperator.length(); for (uint256 i; i < operatorVaultsLen; ++i) { - (address vault, address operator) = _vaultOperator.at(i); - if (_operatorVaults[operator].wasActiveAt(timestamp, vault)) { + (address vault, address operator) = $._vaultOperator.at(i); + if ($._operatorVaults[operator].wasActiveAt(timestamp, vault)) { vaults[len++] = vault; } } @@ -208,18 +233,19 @@ abstract contract VaultManager is BaseManager { function activeVaultsAt( uint48 timestamp ) public view virtual returns (address[] memory) { - address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); uint256 len = activeSharedVaults_.length; - address[] memory vaults = new address[](len + _vaultOperator.length()); + address[] memory vaults = new address[](len + $._vaultOperator.length()); for (uint256 i; i < len; ++i) { vaults[i] = activeSharedVaults_[i]; } - uint256 operatorVaultsLen = _vaultOperator.length(); + uint256 operatorVaultsLen = $._vaultOperator.length(); for (uint256 i; i < operatorVaultsLen; ++i) { - (address vault, address operator) = _vaultOperator.at(i); - if (_operatorVaults[operator].wasActiveAt(timestamp, vault)) { + (address vault, address operator) = $._vaultOperator.at(i); + if ($._operatorVaults[operator].wasActiveAt(timestamp, vault)) { vaults[len++] = vault; } } @@ -239,9 +265,10 @@ abstract contract VaultManager is BaseManager { function activeVaults( address operator ) public view virtual returns (address[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); uint48 timestamp = getCaptureTimestamp(); - address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); - address[] memory activeOperatorVaults_ = _operatorVaults[operator].getActive(timestamp); + address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); + address[] memory activeOperatorVaults_ = $._operatorVaults[operator].getActive(timestamp); uint256 activeSharedVaultsLen = activeSharedVaults_.length; address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults_.length); @@ -262,8 +289,9 @@ abstract contract VaultManager is BaseManager { * @return address[] Array of vault addresses that were active at the timestamp */ function activeVaultsAt(uint48 timestamp, address operator) public view virtual returns (address[] memory) { - address[] memory activeSharedVaults_ = _sharedVaults.getActive(timestamp); - address[] memory activeOperatorVaults_ = _operatorVaults[operator].getActive(timestamp); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); + address[] memory activeOperatorVaults_ = $._operatorVaults[operator].getActive(timestamp); uint256 activeSharedVaultsLen = activeSharedVaults_.length; address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults_.length); @@ -295,7 +323,8 @@ abstract contract VaultManager is BaseManager { * @return bool True if the shared vault was active at the timestamp */ function sharedVaultWasActiveAt(uint48 timestamp, address vault) public view returns (bool) { - return _sharedVaults.wasActiveAt(timestamp, vault); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.wasActiveAt(timestamp, vault); } /** @@ -306,7 +335,8 @@ abstract contract VaultManager is BaseManager { * @return bool True if the operator vault was active at the timestamp */ function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { - return _operatorVaults[operator].wasActiveAt(timestamp, vault); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._operatorVaults[operator].wasActiveAt(timestamp, vault); } /** @@ -318,7 +348,7 @@ abstract contract VaultManager is BaseManager { */ function getOperatorStake(address operator, address vault, uint96 subnetwork) public view returns (uint256) { uint48 timestamp = getCaptureTimestamp(); - bytes32 subnetworkId = NETWORK.subnetwork(subnetwork); + bytes32 subnetworkId = NETWORK().subnetwork(subnetwork); return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); } @@ -336,7 +366,7 @@ abstract contract VaultManager is BaseManager { uint96 subnetwork, uint48 timestamp ) public view returns (uint256) { - bytes32 subnetworkId = NETWORK.subnetwork(subnetwork); + bytes32 subnetworkId = NETWORK().subnetwork(subnetwork); return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); } @@ -491,7 +521,8 @@ abstract contract VaultManager is BaseManager { function _registerSubnetwork( uint96 subnetwork ) internal { - _subnetworks.register(Time.timestamp(), uint160(subnetwork)); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._subnetworks.register(Time.timestamp(), uint160(subnetwork)); } /** @@ -501,7 +532,8 @@ abstract contract VaultManager is BaseManager { function _pauseSubnetwork( uint96 subnetwork ) internal { - _subnetworks.pause(Time.timestamp(), uint160(subnetwork)); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._subnetworks.pause(Time.timestamp(), uint160(subnetwork)); } /** @@ -511,7 +543,8 @@ abstract contract VaultManager is BaseManager { function _unpauseSubnetwork( uint96 subnetwork ) internal { - _subnetworks.unpause(Time.timestamp(), SLASHING_WINDOW, uint160(subnetwork)); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._subnetworks.unpause(Time.timestamp(), SLASHING_WINDOW(), uint160(subnetwork)); } /** @@ -521,7 +554,8 @@ abstract contract VaultManager is BaseManager { function _unregisterSubnetwork( uint96 subnetwork ) internal { - _subnetworks.unregister(Time.timestamp(), SLASHING_WINDOW, uint160(subnetwork)); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._subnetworks.unregister(Time.timestamp(), SLASHING_WINDOW(), uint160(subnetwork)); } /** @@ -531,8 +565,9 @@ abstract contract VaultManager is BaseManager { function _registerSharedVault( address vault ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); _validateVault(vault); - _sharedVaults.register(Time.timestamp(), vault); + $._sharedVaults.register(Time.timestamp(), vault); } /** @@ -541,12 +576,13 @@ abstract contract VaultManager is BaseManager { * @param vault The vault address to register */ function _registerOperatorVault(address operator, address vault) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); _validateVault(vault); - if (_sharedVaults.contains(vault)) { + if ($._sharedVaults.contains(vault)) { revert VaultAlreadyRegistred(); } - _operatorVaults[operator].register(Time.timestamp(), vault); - _vaultOperator.set(vault, operator); + $._operatorVaults[operator].register(Time.timestamp(), vault); + $._vaultOperator.set(vault, operator); } /** @@ -556,7 +592,8 @@ abstract contract VaultManager is BaseManager { function _pauseSharedVault( address vault ) internal { - _sharedVaults.pause(Time.timestamp(), vault); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._sharedVaults.pause(Time.timestamp(), vault); } /** @@ -566,7 +603,8 @@ abstract contract VaultManager is BaseManager { function _unpauseSharedVault( address vault ) internal { - _sharedVaults.unpause(Time.timestamp(), SLASHING_WINDOW, vault); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._sharedVaults.unpause(Time.timestamp(), SLASHING_WINDOW(), vault); } /** @@ -575,7 +613,8 @@ abstract contract VaultManager is BaseManager { * @param vault The vault address to pause */ function _pauseOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].pause(Time.timestamp(), vault); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._operatorVaults[operator].pause(Time.timestamp(), vault); } /** @@ -584,7 +623,8 @@ abstract contract VaultManager is BaseManager { * @param vault The vault address to unpause */ function _unpauseOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].unpause(Time.timestamp(), SLASHING_WINDOW, vault); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._operatorVaults[operator].unpause(Time.timestamp(), SLASHING_WINDOW(), vault); } /** @@ -594,7 +634,8 @@ abstract contract VaultManager is BaseManager { function _unregisterSharedVault( address vault ) internal { - _sharedVaults.unregister(Time.timestamp(), SLASHING_WINDOW, vault); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._sharedVaults.unregister(Time.timestamp(), SLASHING_WINDOW(), vault); } /** @@ -603,8 +644,9 @@ abstract contract VaultManager is BaseManager { * @param vault The vault address to unregister */ function _unregisterOperatorVault(address operator, address vault) internal { - _operatorVaults[operator].unregister(Time.timestamp(), SLASHING_WINDOW, vault); - _vaultOperator.remove(vault); + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._operatorVaults[operator].unregister(Time.timestamp(), SLASHING_WINDOW(), vault); + $._vaultOperator.remove(vault); } /** @@ -625,7 +667,8 @@ abstract contract VaultManager is BaseManager { uint256 amount, bytes memory hints ) internal returns (SlashResponse memory resp) { - if (!(_sharedVaults.contains(vault) || _operatorVaults[operator].contains(vault))) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + if (!($._sharedVaults.contains(vault) || $._operatorVaults[operator].contains(vault))) { revert NotOperatorVault(); } @@ -633,7 +676,7 @@ abstract contract VaultManager is BaseManager { revert InactiveVaultSlash(); } - if (timestamp + SLASHING_WINDOW < Time.timestamp()) { + if (timestamp + SLASHING_WINDOW() < Time.timestamp()) { revert TooOldTimestampSlash(); } @@ -679,7 +722,8 @@ abstract contract VaultManager is BaseManager { function _validateVault( address vault ) private view { - if (!IRegistry(VAULT_REGISTRY).isEntity(vault)) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + if (!IRegistry(VAULT_REGISTRY()).isEntity(vault)) { revert NotVault(); } @@ -687,7 +731,7 @@ abstract contract VaultManager is BaseManager { revert VaultNotInitialized(); } - if (_vaultOperator.contains(vault)) { + if ($._vaultOperator.contains(vault)) { revert VaultAlreadyRegistred(); } @@ -698,7 +742,7 @@ abstract contract VaultManager is BaseManager { vaultEpoch -= IVetoSlasher(slasher).vetoDuration(); } - if (vaultEpoch < SLASHING_WINDOW) { + if (vaultEpoch < SLASHING_WINDOW()) { revert VaultEpochTooShort(); } } diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/middleware/extensions/access-managers/OwnableAccessManager.sol index 02c1994..926046c 100644 --- a/src/middleware/extensions/access-managers/OwnableAccessManager.sol +++ b/src/middleware/extensions/access-managers/OwnableAccessManager.sol @@ -9,8 +9,6 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Implements BaseMiddleware with owner-based access control */ abstract contract OwnableAccessManager is BaseMiddleware { - address public owner; - /** * @notice Error thrown when a non-owner address attempts to call a restricted function * @param sender The address that attempted the call @@ -23,14 +21,41 @@ abstract contract OwnableAccessManager is BaseMiddleware { */ error InvalidOwner(address owner); + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.OwnableAccessManager.owner")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant OnwerStorageLocation = 0x1a2e92a5f10b92a479fe01ec4964026ce147416d5a91524db21dfcebc947d100; + + function _owner() private view returns (address owner_) { + bytes32 location = OnwerStorageLocation; + assembly { + owner_ := sload(location) + } + } + + function _setOwner( + address owner_ + ) private { + bytes32 location = OnwerStorageLocation; + assembly { + sstore(location, owner_) + } + } + + /** + * @notice Gets the current owner address + * @return The owner address + */ + function owner() public view returns (address) { + return _owner(); + } + /** * @notice Initializes the contract with an owner address - * @param _owner The address to set as the owner + * @param owner_ The address to set as the owner */ function __OwnableAccessManaged_init( - address _owner + address owner_ ) internal onlyInitializing { - owner = _owner; + _setOwner(owner_); } /** @@ -38,22 +63,22 @@ abstract contract OwnableAccessManager is BaseMiddleware { * @dev Reverts if the caller is not the owner */ function _checkAccess() internal view override { - if (msg.sender != owner) { + if (msg.sender != _owner()) { revert OnlyOwnerCanCall(msg.sender); } } /** * @notice Updates the owner address - * @param _owner The new owner address + * @param owner_ The new owner address * @dev Can only be called by the current owner */ function setOwner( - address _owner + address owner_ ) public checkAccess { - if (_owner == address(0)) { + if (owner_ == address(0)) { revert InvalidOwner(address(0)); } - owner = _owner; + _setOwner(owner_); } } diff --git a/src/middleware/extensions/capture-timestamps/EpochCapture.sol b/src/middleware/extensions/capture-timestamps/EpochCapture.sol index f1d3eed..da54b6d 100644 --- a/src/middleware/extensions/capture-timestamps/EpochCapture.sol +++ b/src/middleware/extensions/capture-timestamps/EpochCapture.sol @@ -5,8 +5,21 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {BaseMiddleware} from "../../BaseMiddleware.sol"; abstract contract EpochCapture is BaseMiddleware { - uint48 public START_TIMESTAMP; // Start timestamp of the first epoch - uint48 public EPOCH_DURATION; // Duration of each epoch + struct EpochCaptureStorage { + uint48 startTimestamp; + uint48 epochDuration; + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.EpochCapture")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant EpochCaptureStorageLocation = + 0xe67dbdffe3bfc71af681fa5640e3b9c1dc23200b8ae4b657cba896e439a22800; + + function _getEpochCaptureStorage() private pure returns (EpochCaptureStorage storage $) { + bytes32 location = EpochCaptureStorageLocation; + assembly { + $.slot := location + } + } /* * @notice initalizer of the Epochs contract. @@ -15,8 +28,9 @@ abstract contract EpochCapture is BaseMiddleware { function __EpochCapture_init( uint48 epochDuration ) internal onlyInitializing { - EPOCH_DURATION = epochDuration; - START_TIMESTAMP = Time.timestamp(); + EpochCaptureStorage storage $ = _getEpochCaptureStorage(); + $.epochDuration = epochDuration; + $.startTimestamp = Time.timestamp(); } /* @@ -27,7 +41,8 @@ abstract contract EpochCapture is BaseMiddleware { function getEpochStart( uint48 epoch ) public view returns (uint48) { - return START_TIMESTAMP + epoch * EPOCH_DURATION; + EpochCaptureStorage storage $ = _getEpochCaptureStorage(); + return $.startTimestamp + epoch * $.epochDuration; } /* @@ -35,7 +50,8 @@ abstract contract EpochCapture is BaseMiddleware { * @return The current epoch. */ function getCurrentEpoch() public view returns (uint48) { - return (Time.timestamp() - START_TIMESTAMP) / EPOCH_DURATION; + EpochCaptureStorage storage $ = _getEpochCaptureStorage(); + return (Time.timestamp() - $.startTimestamp) / $.epochDuration; } /* diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index 23008a7..91705ba 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -9,11 +9,21 @@ import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; * @dev Implements force pause functionality and prevents unpausing of force-paused operators */ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { - /// @notice Mapping to track which operators have been force paused - mapping(address => bool) public forcePaused; + struct ForcePauseSelfRegisterOperatorsStorage { + mapping(address => bool) forcePaused; + mapping(address => mapping(address => bool)) forcePausedVault; + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.ForcePauseSelfRegisterOperators")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant ForcePauseSelfRegisterOperatorsStorageLocation = + 0xdd349876372c6239a846e50336b70d810259637738fd42711d201341e1e8d600; - /// @notice Mapping to track which operator-vault pairs have been force paused - mapping(address => mapping(address => bool)) public forcePausedVault; + function _getForcePauseStorage() private pure returns (ForcePauseSelfRegisterOperatorsStorage storage $) { + bytes32 location = ForcePauseSelfRegisterOperatorsStorageLocation; + assembly { + $.slot := location + } + } error OperatorForcePaused(); error OperatorVaultForcePaused(); @@ -26,7 +36,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { function forcePauseOperator( address operator ) public checkAccess { - forcePaused[operator] = true; + ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); + $.forcePaused[operator] = true; _beforePauseOperator(operator); _pauseOperator(operator); } @@ -39,7 +50,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { function forceUnpauseOperator( address operator ) public checkAccess { - forcePaused[operator] = false; + ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); + $.forcePaused[operator] = false; _beforeUnpauseOperator(operator); _unpauseOperator(operator); } @@ -51,7 +63,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @dev Can only be called by authorized addresses (checkAccess modifier) */ function forcePauseOperatorVault(address operator, address vault) public checkAccess { - forcePausedVault[operator][vault] = true; + ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); + $.forcePausedVault[operator][vault] = true; _beforePauseOperatorVault(operator, vault); _pauseOperatorVault(operator, vault); } @@ -63,7 +76,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @dev Can only be called by authorized addresses (checkAccess modifier) */ function forceUnpauseOperatorVault(address operator, address vault) public checkAccess { - forcePausedVault[operator][vault] = false; + ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); + $.forcePausedVault[operator][vault] = false; _beforeUnpauseOperatorVault(operator, vault); _unpauseOperatorVault(operator, vault); } @@ -75,7 +89,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { function _beforeUnpauseOperator( address operator ) internal virtual override { - if (forcePaused[operator]) revert OperatorForcePaused(); + ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); + if ($.forcePaused[operator]) revert OperatorForcePaused(); super._beforeUnpauseOperator(operator); } @@ -86,7 +101,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { function _beforeUnregisterOperator( address operator ) internal virtual override { - if (forcePaused[operator]) revert OperatorForcePaused(); + ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); + if ($.forcePaused[operator]) revert OperatorForcePaused(); super._beforeUnregisterOperator(operator); } @@ -96,7 +112,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @param vault The vault address */ function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual override { - if (forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); + ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); + if ($.forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); super._beforeUnpauseOperatorVault(operator, vault); } @@ -106,7 +123,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @param vault The vault address */ function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual override { - if (forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); + ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); + if ($.forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); super._beforeUnregisterOperatorVault(operator, vault); } } diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index 29ec1c6..2f2b2b9 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -32,8 +32,26 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad bytes32 private constant UNPAUSE_OPERATOR_VAULT_TYPEHASH = keccak256("UnpauseOperatorVault(address operator,address vault,uint256 nonce)"); - /// @notice Mapping of operator addresses to their nonces for signature verification - mapping(address => uint256) public nonces; + struct SelfRegisterOperatorsStorage { + mapping(address => uint256) nonces; + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.SelfRegisterOperators")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant SelfResgisterOperators_STORAGE_LOCATION = + 0xefff5050c06ffc4cd47bb2affac9b172a9afa4faaca821adedcc3651bef4ba00; + + function _getSelfRegisterOperatorsStorage() private pure returns (SelfRegisterOperatorsStorage storage $) { + bytes32 location = SelfResgisterOperators_STORAGE_LOCATION; + assembly { + $.slot := location + } + } + + function nonces( + address operator + ) public view returns (uint256) { + return _getSelfRegisterOperatorsStorage().nonces[operator]; + } /** * @notice Initializes the contract with EIP712 domain separator @@ -78,9 +96,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad bytes memory signature, bytes memory keySignature ) public { + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( operator, - keccak256(abi.encode(REGISTER_OPERATOR_TYPEHASH, operator, keccak256(key), vault, nonces[operator]++)), + keccak256(abi.encode(REGISTER_OPERATOR_TYPEHASH, operator, keccak256(key), vault, $.nonces[operator]++)), signature ); _verifyKey(operator, key, keySignature); @@ -109,8 +128,9 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function unregisterOperator(address operator, bytes memory signature) public { _beforeUnregisterOperator(operator); + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( - operator, keccak256(abi.encode(UNREGISTER_OPERATOR_TYPEHASH, operator, nonces[operator]++)), signature + operator, keccak256(abi.encode(UNREGISTER_OPERATOR_TYPEHASH, operator, $.nonces[operator]++)), signature ); _unregisterOperator(operator); } @@ -130,7 +150,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function pauseOperator(address operator, bytes memory signature) public { _beforePauseOperator(operator); - _verifyEIP712(operator, keccak256(abi.encode(PAUSE_OPERATOR_TYPEHASH, operator, nonces[operator]++)), signature); + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); + _verifyEIP712( + operator, keccak256(abi.encode(PAUSE_OPERATOR_TYPEHASH, operator, $.nonces[operator]++)), signature + ); _pauseOperator(operator); } @@ -149,8 +172,9 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function unpauseOperator(address operator, bytes memory signature) public { _beforeUnpauseOperator(operator); + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( - operator, keccak256(abi.encode(UNPAUSE_OPERATOR_TYPEHASH, operator, nonces[operator]++)), signature + operator, keccak256(abi.encode(UNPAUSE_OPERATOR_TYPEHASH, operator, $.nonces[operator]++)), signature ); _unpauseOperator(operator); } @@ -179,9 +203,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad bytes memory signature, bytes memory keySignature ) public { + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( operator, - keccak256(abi.encode(UPDATE_OPERATOR_KEY_TYPEHASH, operator, keccak256(key), nonces[operator]++)), + keccak256(abi.encode(UPDATE_OPERATOR_KEY_TYPEHASH, operator, keccak256(key), $.nonces[operator]++)), signature ); _verifyKey(operator, key, keySignature); @@ -210,9 +235,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad function registerOperatorVault(address operator, address vault, bytes memory signature) public { require(isOperatorRegistered(operator), "Operator not registered"); _beforeRegisterOperatorVault(operator, vault); + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( operator, - keccak256(abi.encode(REGISTER_OPERATOR_VAULT_TYPEHASH, operator, vault, nonces[operator]++)), + keccak256(abi.encode(REGISTER_OPERATOR_VAULT_TYPEHASH, operator, vault, $.nonces[operator]++)), signature ); _registerOperatorVault(operator, vault); @@ -237,9 +263,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function unregisterOperatorVault(address operator, address vault, bytes memory signature) public { _beforeUnregisterOperatorVault(operator, vault); + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( operator, - keccak256(abi.encode(UNREGISTER_OPERATOR_VAULT_TYPEHASH, operator, vault, nonces[operator]++)), + keccak256(abi.encode(UNREGISTER_OPERATOR_VAULT_TYPEHASH, operator, vault, $.nonces[operator]++)), signature ); _unregisterOperatorVault(operator, vault); @@ -264,9 +291,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function pauseOperatorVault(address operator, address vault, bytes memory signature) public { _beforePauseOperatorVault(operator, vault); + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( operator, - keccak256(abi.encode(PAUSE_OPERATOR_VAULT_TYPEHASH, operator, vault, nonces[operator]++)), + keccak256(abi.encode(PAUSE_OPERATOR_VAULT_TYPEHASH, operator, vault, $.nonces[operator]++)), signature ); _pauseOperatorVault(operator, vault); @@ -291,9 +319,10 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function unpauseOperatorVault(address operator, address vault, bytes memory signature) public { _beforeUnpauseOperatorVault(operator, vault); + SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( operator, - keccak256(abi.encode(UNPAUSE_OPERATOR_VAULT_TYPEHASH, operator, vault, nonces[operator]++)), + keccak256(abi.encode(UNPAUSE_OPERATOR_VAULT_TYPEHASH, operator, vault, $.nonces[operator]++)), signature ); _unpauseOperatorVault(operator, vault); From 22add8a5cf6f9ceab5c0d382f2ad8e351dd45b13 Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 21 Nov 2024 16:18:34 +0700 Subject: [PATCH 062/115] feat: add enabled constant var getter for extensions --- src/middleware/extensions/SharedVaults.sol | 2 ++ src/middleware/extensions/Subnetworks.sol | 2 ++ src/middleware/extensions/access-managers/NoAccessManager.sol | 2 ++ .../extensions/access-managers/OwnableAccessManager.sol | 2 ++ src/middleware/extensions/access-managers/OzAccessManaged.sol | 2 ++ src/middleware/extensions/capture-timestamps/EpochCapture.sol | 2 ++ .../extensions/capture-timestamps/TimestampCapture.sol | 2 ++ .../extensions/operators/ForcePauseSelfRegisterOperators.sol | 2 ++ src/middleware/extensions/operators/Operators.sol | 2 ++ src/middleware/extensions/operators/SelfRegisterOperators.sol | 2 ++ src/middleware/extensions/sigs/ECDSASig.sol | 2 ++ src/middleware/extensions/sigs/Ed25519Sig.sol | 2 ++ 12 files changed, 24 insertions(+) diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 7f7bdb4..1348604 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -9,6 +9,8 @@ import {BaseMiddleware} from "../BaseMiddleware.sol"; * @dev Extends BaseMiddleware to provide access control for vault management functions */ abstract contract SharedVaults is BaseMiddleware { + bool public constant SharedVaultsEnabled = true; + /** * @notice Registers a new shared vault * @param sharedVault The address of the vault to register diff --git a/src/middleware/extensions/Subnetworks.sol b/src/middleware/extensions/Subnetworks.sol index ecda886..e4367c0 100644 --- a/src/middleware/extensions/Subnetworks.sol +++ b/src/middleware/extensions/Subnetworks.sol @@ -11,6 +11,8 @@ import {BaseMiddleware} from "../BaseMiddleware.sol"; * @dev Extends BaseMiddleware to provide access control for subnetwork management functions */ abstract contract Subnetworks is BaseMiddleware { + bool public constant SubnetworksEnabled = true; + /** * @notice Registers a new subnetwork * @param subnetwork The ID of the subnetwork to register diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/middleware/extensions/access-managers/NoAccessManager.sol index 95cdda2..940d9b9 100644 --- a/src/middleware/extensions/access-managers/NoAccessManager.sol +++ b/src/middleware/extensions/access-managers/NoAccessManager.sol @@ -9,6 +9,8 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Implements BaseMiddleware and always reverts on access checks */ abstract contract NoAccessManager is BaseMiddleware { + bool public constant NoAccessManagerEnabled = true; + /** * @notice Error thrown when access is denied */ diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/middleware/extensions/access-managers/OwnableAccessManager.sol index 926046c..d0b86dd 100644 --- a/src/middleware/extensions/access-managers/OwnableAccessManager.sol +++ b/src/middleware/extensions/access-managers/OwnableAccessManager.sol @@ -9,6 +9,8 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Implements BaseMiddleware with owner-based access control */ abstract contract OwnableAccessManager is BaseMiddleware { + bool public constant OwnableAccessManagerEnabled = true; + /** * @notice Error thrown when a non-owner address attempts to call a restricted function * @param sender The address that attempted the call diff --git a/src/middleware/extensions/access-managers/OzAccessManaged.sol b/src/middleware/extensions/access-managers/OzAccessManaged.sol index e0849f9..7075228 100644 --- a/src/middleware/extensions/access-managers/OzAccessManaged.sol +++ b/src/middleware/extensions/access-managers/OzAccessManaged.sol @@ -11,11 +11,13 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Implements BaseMiddleware with OpenZeppelin's AccessManagedUpgradeable functionality */ abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { + bool public constant OzAccessManagedEnabled = true; /** * @notice Initializes the contract with an authority address * @param authority The address to set as the access manager authority * @dev Can only be called during initialization */ + function __OzAccessManaged_init( address authority ) internal onlyInitializing { diff --git a/src/middleware/extensions/capture-timestamps/EpochCapture.sol b/src/middleware/extensions/capture-timestamps/EpochCapture.sol index da54b6d..9ec615b 100644 --- a/src/middleware/extensions/capture-timestamps/EpochCapture.sol +++ b/src/middleware/extensions/capture-timestamps/EpochCapture.sol @@ -5,6 +5,8 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {BaseMiddleware} from "../../BaseMiddleware.sol"; abstract contract EpochCapture is BaseMiddleware { + bool public constant EpochCaptureEnabled = true; + struct EpochCaptureStorage { uint48 startTimestamp; uint48 epochDuration; diff --git a/src/middleware/extensions/capture-timestamps/TimestampCapture.sol b/src/middleware/extensions/capture-timestamps/TimestampCapture.sol index e3a19d5..54d37f1 100644 --- a/src/middleware/extensions/capture-timestamps/TimestampCapture.sol +++ b/src/middleware/extensions/capture-timestamps/TimestampCapture.sol @@ -5,6 +5,8 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {BaseMiddleware} from "../../BaseMiddleware.sol"; abstract contract TimestampCapture is BaseMiddleware { + bool public constant TimestampCaptureEnabled = true; + /* * @notice Returns the current timestamp minus 1 second. * @return timestamp The current timestamp minus 1 second. diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index 91705ba..154ae8b 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -9,6 +9,8 @@ import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; * @dev Implements force pause functionality and prevents unpausing of force-paused operators */ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { + bool public constant ForcePauseSelfRegisterOperatorsEnabled = true; + struct ForcePauseSelfRegisterOperatorsStorage { mapping(address => bool) forcePaused; mapping(address => mapping(address => bool)) forcePausedVault; diff --git a/src/middleware/extensions/operators/Operators.sol b/src/middleware/extensions/operators/Operators.sol index 94c682d..330ea59 100644 --- a/src/middleware/extensions/operators/Operators.sol +++ b/src/middleware/extensions/operators/Operators.sol @@ -9,6 +9,8 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Provides core operator management functionality with hooks for customization */ abstract contract Operators is BaseMiddleware { + bool public constant OperatorsEnabled = true; + /** * @notice Registers a new operator with an optional vault association * @param operator The address of the operator to register diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index 2f2b2b9..b6b24cf 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -12,6 +12,8 @@ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/Signa * @dev Extends BaseMiddleware, BaseSig, and EIP712Upgradeable to provide signature-based operator management */ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgradeable { + bool public constant SelfRegisterOperatorsEnabled = true; + error InvalidSignature(); // EIP-712 TypeHash constants diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index 2b6746a..8629d7f 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -10,6 +10,8 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; * @dev Implements BaseSig interface using OpenZeppelin's ECDSA library */ abstract contract ECDSASig is BaseSig { + bool public constant ECDSASigEnabled = true; + using ECDSA for bytes32; /** diff --git a/src/middleware/extensions/sigs/Ed25519Sig.sol b/src/middleware/extensions/sigs/Ed25519Sig.sol index a9ec404..92cc498 100644 --- a/src/middleware/extensions/sigs/Ed25519Sig.sol +++ b/src/middleware/extensions/sigs/Ed25519Sig.sol @@ -10,6 +10,8 @@ import {BaseSig} from "./BaseSig.sol"; * @dev Implements BaseSig interface using Ed25519 signature verification */ abstract contract Ed25519Sig is BaseSig { + bool public constant Ed25519SigEnabled = true; + /** * @notice Verifies that a signature was created by the owner of a key * @param operator The address of the operator that owns the key From 31167e4fda09e165eb608d5aa2d784e9c7fe07d8 Mon Sep 17 00:00:00 2001 From: Kresh Date: Thu, 21 Nov 2024 15:03:24 +0400 Subject: [PATCH 063/115] fix: adjust slot hashes --- src/managers/BaseManager.sol | 2 +- src/managers/OperatorManager.sol | 2 +- src/managers/VaultManager.sol | 2 +- .../extensions/access-managers/OwnableAccessManager.sol | 7 ++++--- .../extensions/capture-timestamps/EpochCapture.sol | 2 +- .../operators/ForcePauseSelfRegisterOperators.sol | 2 +- .../extensions/operators/SelfRegisterOperators.sol | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/managers/BaseManager.sol b/src/managers/BaseManager.sol index e8d06a0..c45486d 100644 --- a/src/managers/BaseManager.sol +++ b/src/managers/BaseManager.sol @@ -19,7 +19,7 @@ abstract contract BaseManager is Initializable { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseManager")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant BaseManagerStorageLocation = - 0xc0625060998ed0caa490c91b79737728a736aeb8ed65081a52c24738d2978d00; + 0xb3503c3f5ee7753561129bea19627692ca916ecb48491bfcd223db17a12b8e00; function _getBaseManagerStorage() private pure returns (BaseManagerStorage storage $) { assembly { diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index e49a70b..6166807 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -26,7 +26,7 @@ abstract contract OperatorManager is BaseManager { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.OperatorManager")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant OperatorManagerStorageLocation = - 0x819b71b9578fceb0968f87c9e32befffbf335e42bec212b90debd10f2f3fdb00; + 0x3b2b549db680c436ebf9aa3c8eeee850852f16da5cdb5137dbc0299ebb219e00; function _getOperatorManagerStorage() private pure returns (OperatorManagerStorage storage $) { assembly { diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index b87cedc..cbaac9b 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -49,7 +49,7 @@ abstract contract VaultManager is BaseManager { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.VaultManager")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant VaultManagerStorageLocation = - 0x2d53fdb1dd96fda6f8b11e221b5832c7a1e485a4745ba2d81260927c2c462900; + 0x485f0695561726d087d0cb5cf546efed37ef61dfced21455f1ba7eb5e5b3db00; function _getVaultManagerStorage() private pure returns (VaultManagerStorage storage $) { assembly { diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/middleware/extensions/access-managers/OwnableAccessManager.sol index d0b86dd..99c73fd 100644 --- a/src/middleware/extensions/access-managers/OwnableAccessManager.sol +++ b/src/middleware/extensions/access-managers/OwnableAccessManager.sol @@ -24,10 +24,11 @@ abstract contract OwnableAccessManager is BaseMiddleware { error InvalidOwner(address owner); // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.OwnableAccessManager.owner")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant OnwerStorageLocation = 0x1a2e92a5f10b92a479fe01ec4964026ce147416d5a91524db21dfcebc947d100; + bytes32 private constant OwnableAccessManagerStorageLocation = + 0xeeb01dcf9eb4176c944794e8cb12d9caba4faa2514a692b173894bc3e9135d00; function _owner() private view returns (address owner_) { - bytes32 location = OnwerStorageLocation; + bytes32 location = OwnableAccessManagerStorageLocation; assembly { owner_ := sload(location) } @@ -36,7 +37,7 @@ abstract contract OwnableAccessManager is BaseMiddleware { function _setOwner( address owner_ ) private { - bytes32 location = OnwerStorageLocation; + bytes32 location = OwnableAccessManagerStorageLocation; assembly { sstore(location, owner_) } diff --git a/src/middleware/extensions/capture-timestamps/EpochCapture.sol b/src/middleware/extensions/capture-timestamps/EpochCapture.sol index 9ec615b..801e440 100644 --- a/src/middleware/extensions/capture-timestamps/EpochCapture.sol +++ b/src/middleware/extensions/capture-timestamps/EpochCapture.sol @@ -14,7 +14,7 @@ abstract contract EpochCapture is BaseMiddleware { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.EpochCapture")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant EpochCaptureStorageLocation = - 0xe67dbdffe3bfc71af681fa5640e3b9c1dc23200b8ae4b657cba896e439a22800; + 0x4e241e104e7ef4df0fc8eb6aad7b0f201c6126c722652f1bd1305b6b75c86d00; function _getEpochCaptureStorage() private pure returns (EpochCaptureStorage storage $) { bytes32 location = EpochCaptureStorageLocation; diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index 154ae8b..3cdd5ae 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -18,7 +18,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.ForcePauseSelfRegisterOperators")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant ForcePauseSelfRegisterOperatorsStorageLocation = - 0xdd349876372c6239a846e50336b70d810259637738fd42711d201341e1e8d600; + 0xf3871d05fd4da42686c3c56dfd4be98b1d278da4bf1fd61b1d6e7a6e37722600; function _getForcePauseStorage() private pure returns (ForcePauseSelfRegisterOperatorsStorage storage $) { bytes32 location = ForcePauseSelfRegisterOperatorsStorageLocation; diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index b6b24cf..9070159 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -40,7 +40,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.SelfRegisterOperators")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant SelfResgisterOperators_STORAGE_LOCATION = - 0xefff5050c06ffc4cd47bb2affac9b172a9afa4faaca821adedcc3651bef4ba00; + 0x7c1bcd600c3fcfbc53470fac03a90d5cf6aa7b77c3f1ed10e6c6bd4d192eaf00; function _getSelfRegisterOperatorsStorage() private pure returns (SelfRegisterOperatorsStorage storage $) { bytes32 location = SelfResgisterOperators_STORAGE_LOCATION; From 8e322a84dbeed914decf65c67ccb83778c103f25 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 25 Nov 2024 19:50:50 +0700 Subject: [PATCH 064/115] forge install: crypto-lib --- .gitmodules | 3 +++ lib/crypto-lib | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/crypto-lib diff --git a/.gitmodules b/.gitmodules index 2c84a91..464a368 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/core"] path = lib/core url = https://github.com/symbioticfi/core +[submodule "lib/crypto-lib"] + path = lib/crypto-lib + url = https://github.com/get-smooth/crypto-lib diff --git a/lib/crypto-lib b/lib/crypto-lib new file mode 160000 index 0000000..f2c00ec --- /dev/null +++ b/lib/crypto-lib @@ -0,0 +1 @@ +Subproject commit f2c00ecced1df96fe81894d19a6b8ec754beedb9 From 05ecd75ec16bc91c4d5a2e303922ed8e80f085cd Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 25 Nov 2024 21:37:35 +0700 Subject: [PATCH 065/115] fix: arbitrary len ed25519 sig --- remappings.txt | 3 +- src/libraries/Ed25519.sol | 1609 +---------------- .../operators/SelfRegisterOperators.sol | 2 +- src/middleware/extensions/sigs/BaseSig.sol | 2 +- src/middleware/extensions/sigs/Ed25519Sig.sol | 32 +- test/helpers/ed25519TestData.json | 10 +- test/helpers/ed25519TestGenerator.js | 11 - 7 files changed, 72 insertions(+), 1597 deletions(-) diff --git a/remappings.txt b/remappings.txt index 6309dcb..984694b 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,4 +1,5 @@ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @symbiotic/=lib/core/src/ -@symbiotic-test=lib/core/test \ No newline at end of file +@symbiotic-test=lib/core/test +@crypto-lib/=lib/crypto-lib/src/ \ No newline at end of file diff --git a/src/libraries/Ed25519.sol b/src/libraries/Ed25519.sol index 084b82d..d2832ca 100644 --- a/src/libraries/Ed25519.sol +++ b/src/libraries/Ed25519.sol @@ -1,1567 +1,58 @@ // SPDX-License-Identifier: GPL-3.0-or-later -// -// Original code by Near Foundation, 2021. -pragma solidity ^0.8; +pragma solidity ^0.8.26; +import {SCL_EIP6565} from "@crypto-lib/lib/libSCL_EIP6565.sol"; +import {SCL_sha512} from "@crypto-lib/hash/SCL_sha512.sol"; +import {SqrtMod} from "@crypto-lib/modular/SCL_sqrtMod_5mod8.sol"; +import {p, d, pMINUS_1} from "@crypto-lib/fields/SCL_wei25519.sol"; +import {ModInv} from "@crypto-lib/modular/SCL_modular.sol"; + +/** + * @title Ed25519 + * @notice Library for verifying Ed25519 signatures on the Edwards curve + * @dev Implements signature verification and point decompression for Ed25519 + */ library Ed25519 { - // Computes (v^(2^250-1), v^11) mod p - function pow22501( - uint256 v - ) private pure returns (uint256 p22501, uint256 p11) { - p11 = mulmod(v, v, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - p22501 = mulmod(p11, p11, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - p22501 = mulmod( - mulmod(p22501, p22501, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), - v, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - p11 = mulmod(p22501, p11, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - p22501 = mulmod( - mulmod(p11, p11, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), - p22501, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 a = mulmod(p22501, p22501, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - p22501 = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(p22501, p22501, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 b = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - p22501 = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(p22501, p22501, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - p22501 = mulmod(p22501, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + /** + * @notice Verifies an Ed25519 signature against a message and public key + * @param message The message that was signed + * @param signature The signature to verify, encoded as (r,s) coordinates + * @param pubkey The compressed public key to verify against + * @return bool True if the signature is valid, false otherwise + * @dev Decompresses the public key, converts to Weierstrass form, and verifies using EIP-6565 + */ + function verify(bytes memory message, bytes memory signature, bytes32 pubkey) public returns (bool) { + (uint256 r, uint256 s) = abi.decode(signature, (uint256, uint256)); + // Decompress pubkey into extended coordinates + uint256[5] memory extPubkey; + extPubkey[4] = uint256(pubkey); // Compressed pubkey + (extPubkey[0], extPubkey[1]) = edDecompress(SCL_sha512.Swap256(extPubkey[4])); + (extPubkey[0], extPubkey[1]) = SCL_EIP6565.Edwards2WeierStrass(extPubkey[0], extPubkey[1]); + return SCL_EIP6565.Verify_LE(string(message), r, s, extPubkey); } - function check(bytes32 k, bytes32 r, bytes32 s, bytes32 m1, bytes9 m2) public pure returns (bool) { - unchecked { - uint256 hh; - // Step 1: compute SHA-512(R, A, M) - { - uint256[5][16] memory kk = [ - [ - uint256(0x428a2f98_d728ae22), - uint256(0xe49b69c1_9ef14ad2), - uint256(0x27b70a85_46d22ffc), - uint256(0x19a4c116_b8d2d0c8), - uint256(0xca273ece_ea26619c) - ], - [ - uint256(0x71374491_23ef65cd), - uint256(0xefbe4786_384f25e3), - uint256(0x2e1b2138_5c26c926), - uint256(0x1e376c08_5141ab53), - uint256(0xd186b8c7_21c0c207) - ], - [ - uint256(0xb5c0fbcf_ec4d3b2f), - uint256(0xfc19dc6_8b8cd5b5), - uint256(0x4d2c6dfc_5ac42aed), - uint256(0x2748774c_df8eeb99), - uint256(0xeada7dd6_cde0eb1e) - ], - [ - uint256(0xe9b5dba5_8189dbbc), - uint256(0x240ca1cc_77ac9c65), - uint256(0x53380d13_9d95b3df), - uint256(0x34b0bcb5_e19b48a8), - uint256(0xf57d4f7f_ee6ed178) - ], - [ - uint256(0x3956c25b_f348b538), - uint256(0x2de92c6f_592b0275), - uint256(0x650a7354_8baf63de), - uint256(0x391c0cb3_c5c95a63), - uint256(0x6f067aa_72176fba) - ], - [ - uint256(0x59f111f1_b605d019), - uint256(0x4a7484aa_6ea6e483), - uint256(0x766a0abb_3c77b2a8), - uint256(0x4ed8aa4a_e3418acb), - uint256(0xa637dc5_a2c898a6) - ], - [ - uint256(0x923f82a4_af194f9b), - uint256(0x5cb0a9dc_bd41fbd4), - uint256(0x81c2c92e_47edaee6), - uint256(0x5b9cca4f_7763e373), - uint256(0x113f9804_bef90dae) - ], - [ - uint256(0xab1c5ed5_da6d8118), - uint256(0x76f988da_831153b5), - uint256(0x92722c85_1482353b), - uint256(0x682e6ff3_d6b2b8a3), - uint256(0x1b710b35_131c471b) - ], - [ - uint256(0xd807aa98_a3030242), - uint256(0x983e5152_ee66dfab), - uint256(0xa2bfe8a1_4cf10364), - uint256(0x748f82ee_5defb2fc), - uint256(0x28db77f5_23047d84) - ], - [ - uint256(0x12835b01_45706fbe), - uint256(0xa831c66d_2db43210), - uint256(0xa81a664b_bc423001), - uint256(0x78a5636f_43172f60), - uint256(0x32caab7b_40c72493) - ], - [ - uint256(0x243185be_4ee4b28c), - uint256(0xb00327c8_98fb213f), - uint256(0xc24b8b70_d0f89791), - uint256(0x84c87814_a1f0ab72), - uint256(0x3c9ebe0a_15c9bebc) - ], - [ - uint256(0x550c7dc3_d5ffb4e2), - uint256(0xbf597fc7_beef0ee4), - uint256(0xc76c51a3_0654be30), - uint256(0x8cc70208_1a6439ec), - uint256(0x431d67c4_9c100d4c) - ], - [ - uint256(0x72be5d74_f27b896f), - uint256(0xc6e00bf3_3da88fc2), - uint256(0xd192e819_d6ef5218), - uint256(0x90befffa_23631e28), - uint256(0x4cc5d4be_cb3e42b6) - ], - [ - uint256(0x80deb1fe_3b1696b1), - uint256(0xd5a79147_930aa725), - uint256(0xd6990624_5565a910), - uint256(0xa4506ceb_de82bde9), - uint256(0x597f299c_fc657e2a) - ], - [ - uint256(0x9bdc06a7_25c71235), - uint256(0x6ca6351_e003826f), - uint256(0xf40e3585_5771202a), - uint256(0xbef9a3f7_b2c67915), - uint256(0x5fcb6fab_3ad6faec) - ], - [ - uint256(0xc19bf174_cf692694), - uint256(0x14292967_0a0e6e70), - uint256(0x106aa070_32bbd1b8), - uint256(0xc67178f2_e372532b), - uint256(0x6c44198c_4a475817) - ] - ]; - uint256 w0 = (uint256(r) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000_ffffffff_ffffffff) - | ((uint256(r) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64) - | ((uint256(r) & 0xffffffff_ffffffff_00000000_00000000) << 64); - uint256 w1 = (uint256(k) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000_ffffffff_ffffffff) - | ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64) - | ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000) << 64); - uint256 w2 = (uint256(m1) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000_ffffffff_ffffffff) - | ((uint256(m1) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64) - | ((uint256(m1) & 0xffffffff_ffffffff_00000000_00000000) << 64); - uint256 w3 = ( - uint256(bytes32(m2)) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000_00000000_00000000 - ) | ((uint256(bytes32(m2)) & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64) - | 0x800000_00000000_00000000_00000348; - uint256 a = 0x6a09e667_f3bcc908; - uint256 b = 0xbb67ae85_84caa73b; - uint256 c = 0x3c6ef372_fe94f82b; - uint256 d = 0xa54ff53a_5f1d36f1; - uint256 e = 0x510e527f_ade682d1; - uint256 f = 0x9b05688c_2b3e6c1f; - uint256 g = 0x1f83d9ab_fb41bd6b; - uint256 h = 0x5be0cd19_137e2179; - for (uint256 i = 0;; i++) { - // Round 16 * i - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[0][i]; - temp1 += w0 >> 192; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 1 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[1][i]; - temp1 += w0 >> 64; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 2 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[2][i]; - temp1 += w0 >> 128; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 3 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[3][i]; - temp1 += w0; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 4 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[4][i]; - temp1 += w1 >> 192; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 5 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[5][i]; - temp1 += w1 >> 64; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 6 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[6][i]; - temp1 += w1 >> 128; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 7 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[7][i]; - temp1 += w1; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 8 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[8][i]; - temp1 += w2 >> 192; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 9 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[9][i]; - temp1 += w2 >> 64; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 10 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[10][i]; - temp1 += w2 >> 128; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 11 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[11][i]; - temp1 += w2; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 12 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[12][i]; - temp1 += w3 >> 192; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 13 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[13][i]; - temp1 += w3 >> 64; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 14 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[14][i]; - temp1 += w3 >> 128; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - // Round 16 * i + 15 - { - uint256 temp1; - uint256 temp2; - e &= 0xffffffff_ffffffff; - { - uint256 ss = e | (e << 64); - uint256 s1 = (ss >> 14) ^ (ss >> 18) ^ (ss >> 41); - uint256 ch = (e & (f ^ g)) ^ g; - temp1 = h + s1 + ch; - } - temp1 += kk[15][i]; - temp1 += w3; - a &= 0xffffffff_ffffffff; - { - uint256 ss = a | (a << 64); - uint256 s0 = (ss >> 28) ^ (ss >> 34) ^ (ss >> 39); - uint256 maj = (a & (b | c)) | (b & c); - temp2 = s0 + maj; - } - h = g; - g = f; - f = e; - e = d + temp1; - d = c; - c = b; - b = a; - a = temp1 + temp2; - } - if (i == 4) { - break; - } - // Message expansion - uint256 t0 = w0; - uint256 t1 = w1; - { - uint256 t2 = w2; - uint256 t3 = w3; - { - uint256 n1 = t0 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - n1 += ((t2 & 0xffffffff_ffffffff_00000000_00000000) << 128) - | ((t2 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64); - { - uint256 u1 = ((t0 & 0xffffffff_ffffffff_00000000_00000000) << 64) - | ((t0 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 128); - uint256 uu1 = u1 | (u1 << 64); - n1 += ((uu1 << 63) ^ (uu1 << 56) ^ (u1 << 57)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - } - { - uint256 v1 = t3 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - uint256 vv1 = v1 | (v1 << 64); - n1 += ((vv1 << 45) ^ (vv1 << 3) ^ (v1 << 58)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - } - n1 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - uint256 n2 = t0 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - n2 += ((t2 & 0xffffffff_ffffffff) << 128) | (t3 >> 192); - { - uint256 u2 = ((t0 & 0xffffffff_ffffffff) << 128) | (t1 >> 192); - uint256 uu2 = u2 | (u2 << 64); - n2 += ((uu2 >> 1) ^ (uu2 >> 8) ^ (u2 >> 7)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - } - { - uint256 vv2 = n1 | (n1 >> 64); - n2 += ((vv2 >> 19) ^ (vv2 >> 61) ^ (n1 >> 70)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - } - n2 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - t0 = n1 | n2; - } - { - uint256 n1 = t1 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - n1 += ((t3 & 0xffffffff_ffffffff_00000000_00000000) << 128) - | ((t3 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64); - { - uint256 u1 = ((t1 & 0xffffffff_ffffffff_00000000_00000000) << 64) - | ((t1 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 128); - uint256 uu1 = u1 | (u1 << 64); - n1 += ((uu1 << 63) ^ (uu1 << 56) ^ (u1 << 57)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - } - { - uint256 v1 = t0 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - uint256 vv1 = v1 | (v1 << 64); - n1 += ((vv1 << 45) ^ (vv1 << 3) ^ (v1 << 58)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - } - n1 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - uint256 n2 = t1 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - n2 += ((t3 & 0xffffffff_ffffffff) << 128) | (t0 >> 192); - { - uint256 u2 = ((t1 & 0xffffffff_ffffffff) << 128) | (t2 >> 192); - uint256 uu2 = u2 | (u2 << 64); - n2 += ((uu2 >> 1) ^ (uu2 >> 8) ^ (u2 >> 7)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - } - { - uint256 vv2 = n1 | (n1 >> 64); - n2 += ((vv2 >> 19) ^ (vv2 >> 61) ^ (n1 >> 70)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - } - n2 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - t1 = n1 | n2; - } - { - uint256 n1 = t2 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - n1 += ((t0 & 0xffffffff_ffffffff_00000000_00000000) << 128) - | ((t0 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64); - { - uint256 u1 = ((t2 & 0xffffffff_ffffffff_00000000_00000000) << 64) - | ((t2 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 128); - uint256 uu1 = u1 | (u1 << 64); - n1 += ((uu1 << 63) ^ (uu1 << 56) ^ (u1 << 57)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - } - { - uint256 v1 = t1 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - uint256 vv1 = v1 | (v1 << 64); - n1 += ((vv1 << 45) ^ (vv1 << 3) ^ (v1 << 58)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - } - n1 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - uint256 n2 = t2 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - n2 += ((t0 & 0xffffffff_ffffffff) << 128) | (t1 >> 192); - { - uint256 u2 = ((t2 & 0xffffffff_ffffffff) << 128) | (t3 >> 192); - uint256 uu2 = u2 | (u2 << 64); - n2 += ((uu2 >> 1) ^ (uu2 >> 8) ^ (u2 >> 7)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - } - { - uint256 vv2 = n1 | (n1 >> 64); - n2 += ((vv2 >> 19) ^ (vv2 >> 61) ^ (n1 >> 70)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - } - n2 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - t2 = n1 | n2; - } - { - uint256 n1 = t3 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - n1 += ((t1 & 0xffffffff_ffffffff_00000000_00000000) << 128) - | ((t1 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 64); - { - uint256 u1 = ((t3 & 0xffffffff_ffffffff_00000000_00000000) << 64) - | ((t3 & 0xffffffff_ffffffff_00000000_00000000_00000000_00000000) >> 128); - uint256 uu1 = u1 | (u1 << 64); - n1 += ((uu1 << 63) ^ (uu1 << 56) ^ (u1 << 57)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - } - { - uint256 v1 = t2 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - uint256 vv1 = v1 | (v1 << 64); - n1 += ((vv1 << 45) ^ (vv1 << 3) ^ (v1 << 58)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - } - n1 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000; - uint256 n2 = t3 & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - n2 += ((t1 & 0xffffffff_ffffffff) << 128) | (t2 >> 192); - { - uint256 u2 = ((t3 & 0xffffffff_ffffffff) << 128) | (t0 >> 192); - uint256 uu2 = u2 | (u2 << 64); - n2 += ((uu2 >> 1) ^ (uu2 >> 8) ^ (u2 >> 7)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - } - { - uint256 vv2 = n1 | (n1 >> 64); - n2 += ((vv2 >> 19) ^ (vv2 >> 61) ^ (n1 >> 70)) - & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - } - n2 &= 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff; - t3 = n1 | n2; - } - w3 = t3; - w2 = t2; - } - w1 = t1; - w0 = t0; - } - uint256 h0 = ((a + 0x6a09e667_f3bcc908) & 0xffffffff_ffffffff) - | (((b + 0xbb67ae85_84caa73b) & 0xffffffff_ffffffff) << 64) - | (((c + 0x3c6ef372_fe94f82b) & 0xffffffff_ffffffff) << 128) | ((d + 0xa54ff53a_5f1d36f1) << 192); - h0 = ((h0 & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) - | ((h0 & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8); - h0 = ((h0 & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) - | ((h0 & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16); - h0 = ((h0 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) - | ((h0 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32); - uint256 h1 = ((e + 0x510e527f_ade682d1) & 0xffffffff_ffffffff) - | (((f + 0x9b05688c_2b3e6c1f) & 0xffffffff_ffffffff) << 64) - | (((g + 0x1f83d9ab_fb41bd6b) & 0xffffffff_ffffffff) << 128) | ((h + 0x5be0cd19_137e2179) << 192); - h1 = ((h1 & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) - | ((h1 & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8); - h1 = ((h1 & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) - | ((h1 & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16); - h1 = ((h1 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) - | ((h1 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32); - hh = addmod( - h0, - mulmod( - h1, - 0xfffffff_ffffffff_ffffffff_fffffffe_c6ef5bf4_737dcf70_d6ec3174_8d98951d, - 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed - ), - 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed - ); - } - // Step 2: unpack k - k = bytes32( - ((uint256(k) & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) - | ((uint256(k) & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8) - ); - k = bytes32( - ((uint256(k) & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) - | ((uint256(k) & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16) - ); - k = bytes32( - ((uint256(k) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) - | ((uint256(k) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32) - ); - k = bytes32( - ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) - | ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> 64) - ); - k = bytes32((uint256(k) << 128) | (uint256(k) >> 128)); - uint256 ky = uint256(k) & 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff; - uint256 kx; - { - uint256 ky2 = mulmod(ky, ky, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 u = addmod( - ky2, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffec, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 v = mulmod( - ky2, - 0x52036cee_2b6ffe73_8cc74079_7779e898_00700a4d_4141d8ab_75eb4dca_135978a3, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ) + 1; - uint256 t = mulmod(u, v, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - (kx,) = pow22501(t); - kx = mulmod(kx, kx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - kx = mulmod( - u, - mulmod( - mulmod(kx, kx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), - t, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ), - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - t = mulmod( - mulmod(kx, kx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), - v, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - if (t != u) { - if (t != 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - u) { - return false; - } - kx = mulmod( - kx, - 0x2b832480_4fc1df0b_2b4d0099_3dfbd7a7_2f431806_ad2fe478_c4ee1b27_4a0ea0b0, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - } - if ((kx & 1) != uint256(k) >> 255) { - kx = 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - kx; - } - // Verify s - s = bytes32( - ((uint256(s) & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) - | ((uint256(s) & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8) - ); - s = bytes32( - ((uint256(s) & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) - | ((uint256(s) & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16) - ); - s = bytes32( - ((uint256(s) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) - | ((uint256(s) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32) - ); - s = bytes32( - ((uint256(s) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) - | ((uint256(s) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> 64) - ); - s = bytes32((uint256(s) << 128) | (uint256(s) >> 128)); - if (uint256(s) >= 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed) { - return false; - } - uint256 vx; - uint256 vu; - uint256 vy; - uint256 vv; - // Step 3: compute multiples of k - uint256[8][3][2] memory tables; - { - uint256 ks = ky + kx; - uint256 kd = ky + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - kx; - uint256 k2dt = mulmod( - mulmod(kx, ky, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), - 0x2406d9dc_56dffce7_198e80f2_eef3d130_00e0149a_8283b156_ebd69b94_26b2f159, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 kky = ky; - uint256 kkx = kx; - uint256 kku = 1; - uint256 kkv = 1; - { - uint256 xx = - mulmod(kkx, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 yy = - mulmod(kky, kku, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 zz = - mulmod(kku, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 xx2 = - mulmod(xx, xx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 yy2 = - mulmod(yy, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 xxyy = - mulmod(xx, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 zz2 = - mulmod(zz, zz, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - kkx = xxyy + xxyy; - kku = yy2 - xx2 + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - kky = xx2 + yy2; - kkv = addmod( - zz2 + zz2, - 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - kku, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - { - uint256 xx = - mulmod(kkx, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 yy = - mulmod(kky, kku, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 zz = - mulmod(kku, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 xx2 = - mulmod(xx, xx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 yy2 = - mulmod(yy, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 xxyy = - mulmod(xx, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 zz2 = - mulmod(zz, zz, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - kkx = xxyy + xxyy; - kku = yy2 - xx2 + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - kky = xx2 + yy2; - kkv = addmod( - zz2 + zz2, - 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - kku, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - { - uint256 xx = - mulmod(kkx, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 yy = - mulmod(kky, kku, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 zz = - mulmod(kku, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 xx2 = - mulmod(xx, xx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 yy2 = - mulmod(yy, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 xxyy = - mulmod(xx, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 zz2 = - mulmod(zz, zz, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - kkx = xxyy + xxyy; - kku = yy2 - xx2 + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - kky = xx2 + yy2; - kkv = addmod( - zz2 + zz2, - 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - kku, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - uint256 cprod = 1; - uint256[8][3][2] memory tables_ = tables; - for (uint256 i = 0;; i++) { - uint256 cs; - uint256 cd; - uint256 ct; - uint256 c2z; - { - uint256 cx = - mulmod(kkx, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 cy = - mulmod(kky, kku, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 cz = - mulmod(kku, kkv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - ct = mulmod(kkx, kky, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - cs = cy + cx; - cd = cy - cx + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - c2z = cz + cz; - } - tables_[1][0][i] = cs; - tables_[1][1][i] = cd; - tables_[1][2][i] = mulmod( - ct, - 0x2406d9dc_56dffce7_198e80f2_eef3d130_00e0149a_8283b156_ebd69b94_26b2f159, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - tables_[0][0][i] = c2z; - tables_[0][1][i] = cprod; - cprod = - mulmod(cprod, c2z, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - if (i == 7) { - break; - } - uint256 ab = - mulmod(cs, ks, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 aa = - mulmod(cd, kd, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 ac = - mulmod(ct, k2dt, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - kkx = ab - aa + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - kku = addmod(c2z, ac, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - kky = ab + aa; - kkv = addmod( - c2z, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ac, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - uint256 t; - (cprod, t) = pow22501(cprod); - cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - cprod = mulmod(cprod, cprod, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - cprod = mulmod(cprod, t, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - for (uint256 i = 7;; i--) { - uint256 cinv = mulmod( - cprod, - tables_[0][1][i], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - tables_[1][0][i] = mulmod( - tables_[1][0][i], - cinv, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - tables_[1][1][i] = mulmod( - tables_[1][1][i], - cinv, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - tables_[1][2][i] = mulmod( - tables_[1][2][i], - cinv, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - if (i == 0) { - break; - } - cprod = mulmod( - cprod, - tables_[0][0][i], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - tables_[0] = [ - [ - 0x43e7ce9d_19ea5d32_9385a44c_321ea161_67c996e3_7dc6070c_97de49e3_7ac61db9, - 0x40cff344_25d8ec30_a3bb74ba_58cd5854_fa1e3818_6ad0d31e_bc8ae251_ceb2c97e, - 0x459bd270_46e8dd45_aea7008d_b87a5a8f_79067792_53d64523_58951859_9fdfbf4b, - 0x69fdd1e2_8c23cc38_94d0c8ff_90e76f6d_5b6e4c2e_620136d0_4dd83c4a_51581ab9, - 0x54dceb34_13ce5cfa_11196dfc_960b6eda_f4b380c6_d4d23784_19cc0279_ba49c5f3, - 0x4e24184d_d71a3d77_eef3729f_7f8cf7c1_7224cf40_aa7b9548_b9942f3c_5084ceed, - 0x5a0e5aab_20262674_ae117576_1cbf5e88_9b52a55f_d7ac5027_c228cebd_c8d2360a, - 0x26239334_073e9b38_c6285955_6d451c3d_cc8d30e8_4b361174_f488eadd_e2cf17d9 - ], - [ - 0x227e97c9_4c7c0933_d2e0c21a_3447c504_fe9ccf82_e8a05f59_ce881c82_eba0489f, - 0x226a3e0e_cc4afec6_fd0d2884_13014a9d_bddecf06_c1a2f0bb_702ba77c_613d8209, - 0x34d7efc8_51d45c5e_71efeb0f_235b7946_91de6228_877569b3_a8d52bf0_58b8a4a0, - 0x3c1f5fb3_ca7166fc_e1471c9b_752b6d28_c56301ad_7b65e845_1b2c8c55_26726e12, - 0x6102416c_f02f02ff_5be75275_f55f28db_89b2a9d2_456b860c_e22fc0e5_031f7cc5, - 0x40adf677_f1bfdae0_57f0fd17_9c126179_18ddaa28_91a6530f_b1a4294f_a8665490, - 0x61936f3c_41560904_6187b8ba_a978cbc9_b4789336_3ae5a3cc_7d909f36_35ae7f48, - 0x562a9662_b6ec47f9_e979d473_c02b51e4_42336823_8c58ddb5_2f0e5c6a_180e6410 - ], - [ - 0x3788bdb4_4f8632d4_2d0dbee5_eea1acc6_136cf411_e655624f_55e48902_c3bd5534, - 0x6190cf2c_2a7b5ad7_69d594a8_2844f23b_4167fa7c_8ac30e51_aa6cfbeb_dcd4b945, - 0x65f77870_96be9204_123a71f3_ac88a87b_e1513217_737d6a1e_2f3a13a4_3d7e3a9a, - 0x23af32d_bfa67975_536479a7_a7ce74a0_2142147f_ac048018_7f1f1334_9cda1f2d, - 0x64fc44b7_fc6841bd_db0ced8b_8b0fe675_9137ef87_ee966512_15fc1dbc_d25c64dc, - 0x1434aa37_48b701d5_b69df3d7_d340c1fe_3f6b9c1e_fc617484_caadb47e_382f4475, - 0x457a6da8_c962ef35_f2b21742_3e5844e9_d2353452_7e8ea429_0d24e3dd_f21720c6, - 0x63b9540c_eb60ccb5_1e4d989d_956e053c_f2511837_efb79089_d2ff4028_4202c53d - ] - ]; - } - // Step 4: compute s*G - h*A - { - uint256 ss = uint256(s) << 3; - uint256 hhh = hh + 0x80000000_00000000_00000000_00000000_a6f7cef5_17bce6b2_c09318d2_e7ae9f60; - uint256 vvx = 0; - uint256 vvu = 1; - uint256 vvy = 1; - uint256 vvv = 1; - for (uint256 i = 252;; i--) { - uint256 bit = 8 << i; - if ((ss & bit) != 0) { - uint256 ws; - uint256 wd; - uint256 wz; - uint256 wt; - { - uint256 wx = mulmod( - vvx, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 wy = mulmod( - vvy, vvu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - ws = wy + wx; - wd = wy - wx + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - wz = mulmod( - vvu, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - wt = mulmod( - vvx, vvy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - uint256 j = (ss >> i) & 7; - ss &= ~(7 << i); - uint256[8][3][2] memory tables_ = tables; - uint256 aa = mulmod( - wd, - tables_[0][1][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 ab = mulmod( - ws, - tables_[0][0][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 ac = mulmod( - wt, - tables_[0][2][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - vvx = ab - aa + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - vvu = wz + ac; - vvy = ab + aa; - vvv = wz - ac + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - } - if ((hhh & bit) != 0) { - uint256 ws; - uint256 wd; - uint256 wz; - uint256 wt; - { - uint256 wx = mulmod( - vvx, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 wy = mulmod( - vvy, vvu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - ws = wy + wx; - wd = wy - wx + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - wz = mulmod( - vvu, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - wt = mulmod( - vvx, vvy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - uint256 j = (hhh >> i) & 7; - hhh &= ~(7 << i); - uint256[8][3][2] memory tables_ = tables; - uint256 aa = mulmod( - wd, - tables_[1][0][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 ab = mulmod( - ws, - tables_[1][1][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 ac = mulmod( - wt, - tables_[1][2][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - vvx = ab - aa + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - vvu = wz - ac + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - vvy = ab + aa; - vvv = wz + ac; - } - if (i == 0) { - uint256 ws; - uint256 wd; - uint256 wz; - uint256 wt; - { - uint256 wx = mulmod( - vvx, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 wy = mulmod( - vvy, vvu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - ws = wy + wx; - wd = wy - wx + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - wz = mulmod( - vvu, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - wt = mulmod( - vvx, vvy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - uint256 j = hhh & 7; - uint256[8][3][2] memory tables_ = tables; - uint256 aa = mulmod( - wd, - tables_[1][0][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 ab = mulmod( - ws, - tables_[1][1][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - uint256 ac = mulmod( - wt, - tables_[1][2][j], - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - vvx = ab - aa + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - vvu = wz - ac + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - vvy = ab + aa; - vvv = wz + ac; - break; - } - { - uint256 xx = - mulmod(vvx, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 yy = - mulmod(vvy, vvu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 zz = - mulmod(vvu, vvv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 xx2 = - mulmod(xx, xx, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 yy2 = - mulmod(yy, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 xxyy = - mulmod(xx, yy, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - uint256 zz2 = - mulmod(zz, zz, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - vvx = xxyy + xxyy; - vvu = yy2 - xx2 + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; - vvy = xx2 + yy2; - vvv = addmod( - zz2 + zz2, - 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - vvu, - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - } - } - vx = vvx; - vu = vvu; - vy = vvy; - vv = vvv; - } - // Step 5: compare the points - (uint256 vi, uint256 vj) = - pow22501(mulmod(vu, vv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed)); - vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - vi = mulmod(vi, vi, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - vi = mulmod(vi, vj, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); - vx = mulmod( - vx, - mulmod(vi, vv, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - vy = mulmod( - vy, - mulmod(vi, vu, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed), - 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - ); - bytes32 vr = bytes32(vy | (vx << 255)); - vr = bytes32( - ((uint256(vr) & 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) - | ((uint256(vr) & 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> 8) - ); - vr = bytes32( - ((uint256(vr) & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) - | ((uint256(vr) & 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> 16) - ); - vr = bytes32( - ((uint256(vr) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << 32) - | ((uint256(vr) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> 32) - ); - vr = bytes32( - ((uint256(vr) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) - | ((uint256(vr) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> 64) - ); - vr = bytes32((uint256(vr) << 128) | (uint256(vr) >> 128)); - return vr == r; + /** + * @notice Decompresses an Ed25519 public key from its compressed form + * from here https://github.com/get-smooth/crypto-lib/blob/f2c00ecced1df96fe81894d19a6b8ec754beedb9/test/libSCL_eip6565.t.sol#L44 + * @param KPubC The compressed public key point in Edwards form, with sign bit in MSB + * @return x The x-coordinate of the decompressed point on Edwards curve + * @return y The y-coordinate of the decompressed point on Edwards curve + * @dev Recovers x-coordinate using the curve equation: -x^2 + y^2 = 1 + d*x^2*y^2 + * @dev The compressed form stores the y-coordinate in the lower 255 bits and sign of x in bit 255 + * @dev If computed x doesn't match the sign bit, negates x mod p + */ + function edDecompress( + uint256 KPubC + ) public returns (uint256 x, uint256 y) { + uint256 sign = (KPubC >> 255) & 1; //parity bit is the highest bit of compressed point + y = KPubC & 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + uint256 x2; + uint256 y2 = mulmod(y, y, p); + + x2 = mulmod(addmod(y2, pMINUS_1, p), ModInv(addmod(mulmod(d, y2, p), 1, p), p), p); + x = SqrtMod(x2); + if ((x & 1) != sign) { + x = p - x; } } } diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index 9070159..10f4868 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -336,7 +336,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad * @param key The public key to verify * @param signature The signature to verify */ - function _verifyKey(address operator, bytes memory key, bytes memory signature) internal view { + function _verifyKey(address operator, bytes memory key, bytes memory signature) internal { if (key.length != 0 && !_verifyKeySignature(operator, key, signature)) { revert InvalidSignature(); } diff --git a/src/middleware/extensions/sigs/BaseSig.sol b/src/middleware/extensions/sigs/BaseSig.sol index f86f9d5..9276721 100644 --- a/src/middleware/extensions/sigs/BaseSig.sol +++ b/src/middleware/extensions/sigs/BaseSig.sol @@ -13,5 +13,5 @@ abstract contract BaseSig { address operator, bytes memory key_, bytes memory signature - ) internal view virtual returns (bool); + ) internal virtual returns (bool); } diff --git a/src/middleware/extensions/sigs/Ed25519Sig.sol b/src/middleware/extensions/sigs/Ed25519Sig.sol index 92cc498..ea41282 100644 --- a/src/middleware/extensions/sigs/Ed25519Sig.sol +++ b/src/middleware/extensions/sigs/Ed25519Sig.sol @@ -15,37 +15,31 @@ abstract contract Ed25519Sig is BaseSig { /** * @notice Verifies that a signature was created by the owner of a key * @param operator The address of the operator that owns the key - * @param key_ The public key to verify against, encoded as bytes - * @param signature The Ed25519 signature to verify, containing r and s components + * @param key_ The public key to verify against + * @param signature The Ed25519 signature to verify * @return True if the signature was created by the key owner, false otherwise - * @dev The key is expected to be a bytes32 that represents an Ed25519 public key - * The signature is expected to be 64 bytes containing r (32 bytes) and s (32 bytes) + * @dev The key must be a valid Ed25519 public key point compressed to 32 bytes + * The signature must be 64 bytes containing r and s components encoded as uint256 */ function _verifyKeySignature( address operator, bytes memory key_, bytes memory signature - ) internal pure override returns (bool) { + ) internal override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); - bytes32 message = keccak256(abi.encodePacked(operator, key)); - return check(key, signature, message); + bytes memory message = abi.encode(keccak256(abi.encodePacked(operator, key))); + return verify(message, signature, key); } /** - * @notice Checks an Ed25519 signature against a message and public key - * @param key The Ed25519 public key - * @param signature The Ed25519 signature to verify + * @notice Verifies an Ed25519 signature against a message and public key * @param message The message that was signed + * @param signature The Ed25519 signature to verify + * @param key The Ed25519 public key compressed to 32 bytes * @return True if the signature is valid, false otherwise - * @dev Wrapper around Ed25519.check + * @dev Wrapper around Ed25519.verify which handles decompression and curve operations */ - function check(bytes32 key, bytes memory signature, bytes32 message) internal pure returns (bool) { - bytes32 r; - bytes32 s; - assembly { - r := mload(add(signature, 32)) - s := mload(add(signature, 64)) - } - return Ed25519.check(key, r, s, message, bytes9(0)); + function verify(bytes memory message, bytes memory signature, bytes32 key) internal returns (bool) { + return Ed25519.verify(message, signature, key); } } diff --git a/test/helpers/ed25519TestData.json b/test/helpers/ed25519TestData.json index f09a97a..cf0c6cd 100644 --- a/test/helpers/ed25519TestData.json +++ b/test/helpers/ed25519TestData.json @@ -1,7 +1,7 @@ { - "operator": "0xc2c1697Fe88772f844D1b622F4fc0E6E0b16Cb77", - "key": "0x121516a0d84c94a28d9f9cd2263d3fe8eceee3cef43aa043e388cce914f85205", - "signature": "0x5d590ecf3f7019846aba3c363ddb8f640109f9fa35a5d8ce027407e8d4507045b651075cfbecbb86ede90a4f20ac99fa969419c81ee4d7066868bfcd5e47d50d", - "invalidKey": "0x98c6edf296f06a1b23e56d56020c0ea8289d07a8b0a23e953b24239697cf5a96", - "invalidSignature": "0x8f3d84ae06d3d912328d6c5c97622d880c2de9255d50c697314fc5f61ce0d211b8603ac2c3287ed607364d8cd1e89675a1f3e0892c11ddc7116cf92221b63600" + "operator": "0xFFa6DD45436695c0185c9E1721638bc951b6853d", + "key": "0xf5240b978fc69dc2fdc62775572794e8cd0fd8ac4c0510336fb07232e8086692", + "signature": "0x78dd2320878fc2fb9f818d56442926eab183f7b94acfb5eaf5d7a24a018f4c200582b0b0cfc195991e1b827187abd35eb099dda6cd6f3458eb53d086db82c808", + "invalidKey": "0xc3e65061bd3c7857f68ab167faa3da83d964d2ec9d2447d367a7cdb4a3256d24", + "invalidSignature": "0x4b829ff953a071273aca67046a71a7f2c0d74ab8dcb3636cfeaf6c4a8f3c9ba5a6de5754c5189fc0e38e3df225657043fbfc0786f4b676ffab319bd92af20307" } \ No newline at end of file diff --git a/test/helpers/ed25519TestGenerator.js b/test/helpers/ed25519TestGenerator.js index 7096f75..94f6dc4 100644 --- a/test/helpers/ed25519TestGenerator.js +++ b/test/helpers/ed25519TestGenerator.js @@ -24,12 +24,6 @@ function generateTestData(operatorAddress) { ]) ); - // Add 9 bytes of zeros to the message - message = Buffer.concat([ - message, - Buffer.alloc(9) - ]); - // Sign the message const signature = "0x" + bytesToHex(ed25519.sign(message, privateKey)); @@ -71,11 +65,6 @@ function generateInvalidTestData(operatorAddress) { ]) ); - message = Buffer.concat([ - message, - Buffer.alloc(9) - ]); - // Sign with privateKey2 (mismatch) const signature = "0x" + bytesToHex(ed25519.sign(message, privateKey2)); From c7efb343659e5330aa7983db5371013cc36d48f9 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 25 Nov 2024 22:09:46 +0700 Subject: [PATCH 066/115] fix: ed25519 licence --- src/libraries/Ed25519.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Ed25519.sol b/src/libraries/Ed25519.sol index d2832ca..2969fe8 100644 --- a/src/libraries/Ed25519.sol +++ b/src/libraries/Ed25519.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; import {SCL_EIP6565} from "@crypto-lib/lib/libSCL_EIP6565.sol"; From 8247e9bcdcae364f230ae7c192605c6af486a6b2 Mon Sep 17 00:00:00 2001 From: alrxy Date: Tue, 26 Nov 2024 14:23:46 +0700 Subject: [PATCH 067/115] refactor: rename ed25519 to eddsa, key storage to extensions, add base key manager --- .../SelfRegisterEd25519Middleware.sol | 6 +-- .../SelfRegisterMiddleware.sol | 2 +- .../SimplePosMiddleware.sol | 6 +-- .../sqrt-task-network/SqrtTaskMiddleware.sol | 4 +- src/libraries/{Ed25519.sol => EdDSA.sol} | 12 ++--- src/managers/KeyManager.sol | 45 ++++++++++++++++ src/middleware/BaseMiddleware.sol | 46 ++++------------ .../key-storages}/KeyStorage256.sol | 9 ++-- .../key-storages}/KeyStorageBytes.sol | 14 ++--- .../extensions/key-storages/NoKeyStorage.sol | 52 +++++++++++++++++++ .../sigs/{Ed25519Sig.sol => EdDSASig.sol} | 14 ++--- 11 files changed, 140 insertions(+), 70 deletions(-) rename src/libraries/{Ed25519.sol => EdDSA.sol} (92%) create mode 100644 src/managers/KeyManager.sol rename src/{key-storage => middleware/extensions/key-storages}/KeyStorage256.sol (93%) rename src/{key-storage => middleware/extensions/key-storages}/KeyStorageBytes.sol (92%) create mode 100644 src/middleware/extensions/key-storages/NoKeyStorage.sol rename src/middleware/extensions/sigs/{Ed25519Sig.sol => EdDSASig.sol} (80%) diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index 0c06061..aa4067f 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -15,14 +15,14 @@ import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfR import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; -import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; -import {Ed25519Sig} from "../../middleware/extensions/sigs/Ed25519Sig.sol"; +import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; +import {EdDSASig} from "../../middleware/extensions/sigs/EdDSASig.sol"; contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, KeyStorage256, - Ed25519Sig, + EdDSASig, NoAccessManager, TimestampCapture { diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index 6b7738a..a063e35 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -16,7 +16,7 @@ import {ECDSASig} from "../../middleware/extensions/sigs/ECDSASig.sol"; import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; -import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; +import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; contract SelfRegisterMiddleware is SharedVaults, diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 9f8620b..5a4b05b 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -14,7 +14,7 @@ import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/operators/Operators.sol"; import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; import {EpochCapture} from "../../middleware/extensions/capture-timestamps/EpochCapture.sol"; -import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; +import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableAccessManager, EpochCapture { using Subnetwork for address; @@ -104,7 +104,7 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableA address operator = operators[i]; // Get the operator address bytes32 key = abi.decode(operatorKey(operator), (bytes32)); // Get the key for the operator - if (key == bytes32(0) || !keyWasActiveAt(getCaptureTimestamp(), key)) { + if (key == bytes32(0) || !keyWasActiveAt(getCaptureTimestamp(), abi.encode(key))) { continue; // Skip if the key is inactive } @@ -180,7 +180,7 @@ contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableA revert NotExistKeySlash(); // Revert if the operator does not exist } - if (!keyWasActiveAt(epochStart, key)) { + if (!keyWasActiveAt(epochStart, abi.encode(key))) { revert InactiveKeySlash(); // Revert if the key is inactive } diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index a08c458..51f43fd 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -14,13 +14,13 @@ import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/operators/Operators.sol"; import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; -import {KeyStorage256} from "../../key-storage/KeyStorage256.sol"; +import {NoKeyStorage} from "../../middleware/extensions/key-storages/NoKeyStorage.sol"; import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; contract SqrtTaskMiddleware is SharedVaults, Operators, - KeyStorage256, + NoKeyStorage, EIP712, OwnableAccessManager, TimestampCapture diff --git a/src/libraries/Ed25519.sol b/src/libraries/EdDSA.sol similarity index 92% rename from src/libraries/Ed25519.sol rename to src/libraries/EdDSA.sol index 2969fe8..0dc0284 100644 --- a/src/libraries/Ed25519.sol +++ b/src/libraries/EdDSA.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.26; +pragma solidity ^0.8.25; import {SCL_EIP6565} from "@crypto-lib/lib/libSCL_EIP6565.sol"; import {SCL_sha512} from "@crypto-lib/hash/SCL_sha512.sol"; @@ -8,13 +8,13 @@ import {p, d, pMINUS_1} from "@crypto-lib/fields/SCL_wei25519.sol"; import {ModInv} from "@crypto-lib/modular/SCL_modular.sol"; /** - * @title Ed25519 - * @notice Library for verifying Ed25519 signatures on the Edwards curve - * @dev Implements signature verification and point decompression for Ed25519 + * @title EdDSA + * @notice Library for verifying EdDSA signatures on the Ed25519 curve + * @dev Implements signature verification and point decompression for EdDSA */ -library Ed25519 { +library EdDSA { /** - * @notice Verifies an Ed25519 signature against a message and public key + * @notice Verifies an EdDSA signature against a message and public key * @param message The message that was signed * @param signature The signature to verify, encoded as (r,s) coordinates * @param pubkey The compressed public key to verify against diff --git a/src/managers/KeyManager.sol b/src/managers/KeyManager.sol new file mode 100644 index 0000000..203c979 --- /dev/null +++ b/src/managers/KeyManager.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseManager} from "./BaseManager.sol"; + +/** + * @title KeyManager + * @notice Abstract contract for managing keys + */ +abstract contract KeyManager is BaseManager { + /** + * @notice Updates the key associated with an operator + * @param operator The address of the operator + * @param key The key to update + */ + + function _updateKey(address operator, bytes memory key) internal virtual; + + /** + * @notice Returns the operator address associated with a given key + * @param key The key for which to find the associated operator + * @return The address of the operator linked to the specified key + */ + function operatorByKey( + bytes memory key + ) public view virtual returns (address); + + /** + * @notice Returns the current or previous key for a given operator + * @dev Returns the previous key if the key was updated in the current epoch + * @param operator The address of the operator + * @return The key associated with the specified operator + */ + function operatorKey( + address operator + ) public view virtual returns (bytes memory); + + /** + * @notice Checks if a key was active at a specific timestamp + * @param timestamp The timestamp to check + * @param key The key to check + * @return True if the key was active at the timestamp, false otherwise + */ + function keyWasActiveAt(uint48 timestamp, bytes memory key) public view virtual returns (bool); +} diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index bcb5956..4bbcdb7 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -1,44 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - import {VaultManager} from "../managers/VaultManager.sol"; import {OperatorManager} from "../managers/OperatorManager.sol"; import {AccessManager} from "../managers/AccessManager.sol"; +import {KeyManager} from "../managers/KeyManager.sol"; -abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager { - using Subnetwork for address; - /** - * @notice Updates the key associated with an operator - * @param operator The address of the operator - * @param key The key to update - */ - - function _updateKey(address operator, bytes memory key) internal virtual; - - /** - * @notice Returns the operator address associated with a given key - * @param key The key for which to find the associated operator - * @return The address of the operator linked to the specified key - */ - function operatorByKey( - bytes memory key - ) public view virtual returns (address); - - /** - * @notice Returns the current or previous key for a given operator - * @dev Returns the previous key if the key was updated in the current epoch - * @param operator The address of the operator - * @return The key associated with the specified operator - */ - function operatorKey( - address operator - ) public view virtual returns (bytes memory); -} +/** + * @title BaseMiddleware + * @notice Abstract base contract that combines core manager functionality + * @dev Inherits from VaultManager, OperatorManager, AccessManager and KeyManager to provide + * comprehensive middleware capabilities for vault and operator management, access control, + * and key management + */ +abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager {} diff --git a/src/key-storage/KeyStorage256.sol b/src/middleware/extensions/key-storages/KeyStorage256.sol similarity index 93% rename from src/key-storage/KeyStorage256.sol rename to src/middleware/extensions/key-storages/KeyStorage256.sol index e153e41..281033b 100644 --- a/src/key-storage/KeyStorage256.sol +++ b/src/middleware/extensions/key-storages/KeyStorage256.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../middleware/BaseMiddleware.sol"; -import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; +import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; @@ -71,11 +71,12 @@ abstract contract KeyStorage256 is BaseMiddleware { /** * @notice Checks if a key was active at a specific timestamp * @param timestamp The timestamp to check - * @param key The key to check + * @param key_ The key to check * @return True if the key was active at the timestamp, false otherwise */ - function keyWasActiveAt(uint48 timestamp, bytes32 key) public view returns (bool) { + function keyWasActiveAt(uint48 timestamp, bytes memory key_) public view override returns (bool) { KeyStorage256Storage storage s = _getStorage(); + bytes32 key = abi.decode(key_, (bytes32)); return s.keys[s.keyToOperator[key]].wasActiveAt(timestamp, key); } diff --git a/src/key-storage/KeyStorageBytes.sol b/src/middleware/extensions/key-storages/KeyStorageBytes.sol similarity index 92% rename from src/key-storage/KeyStorageBytes.sol rename to src/middleware/extensions/key-storages/KeyStorageBytes.sol index 0f0b2b2..62ef739 100644 --- a/src/key-storage/KeyStorageBytes.sol +++ b/src/middleware/extensions/key-storages/KeyStorageBytes.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseManager} from "../managers/BaseManager.sol"; -import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; +import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; @@ -11,7 +11,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; * @notice Manages storage and validation of operator keys * @dev Extends BaseManager to provide key management functionality */ -abstract contract KeyStorageBytes is BaseManager { +abstract contract KeyStorageBytes is BaseMiddleware { using PauseableEnumerableSet for PauseableEnumerableSet.BytesSet; using PauseableEnumerableSet for PauseableEnumerableSet.Status; @@ -45,7 +45,7 @@ abstract contract KeyStorageBytes is BaseManager { */ function operatorByKey( bytes memory key - ) public view returns (address) { + ) public view override returns (address) { KeyStorageBytesStorage storage $ = _getStorage(); return $._keyToOperator[key]; } @@ -57,7 +57,7 @@ abstract contract KeyStorageBytes is BaseManager { */ function operatorKey( address operator - ) public view returns (bytes memory) { + ) public view override returns (bytes memory) { KeyStorageBytesStorage storage $ = _getStorage(); bytes[] memory active = $._keys[operator].getActive(getCaptureTimestamp()); if (active.length == 0) { @@ -72,7 +72,7 @@ abstract contract KeyStorageBytes is BaseManager { * @param key The key to check * @return True if the key was active at the timestamp, false otherwise */ - function keyWasActiveAt(uint48 timestamp, bytes memory key) public view returns (bool) { + function keyWasActiveAt(uint48 timestamp, bytes memory key) public view override returns (bool) { KeyStorageBytesStorage storage $ = _getStorage(); return $._keys[$._keyToOperator[key]].wasActiveAt(timestamp, key); } @@ -85,7 +85,7 @@ abstract contract KeyStorageBytes is BaseManager { * @custom:throws DuplicateKey if key is already registered to another operator * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ - function _updateKey(address operator, bytes memory key) internal { + function _updateKey(address operator, bytes memory key) internal override { KeyStorageBytesStorage storage $ = _getStorage(); bytes32 keyHash = keccak256(key); diff --git a/src/middleware/extensions/key-storages/NoKeyStorage.sol b/src/middleware/extensions/key-storages/NoKeyStorage.sol new file mode 100644 index 0000000..659d2f2 --- /dev/null +++ b/src/middleware/extensions/key-storages/NoKeyStorage.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseMiddleware} from "../../BaseMiddleware.sol"; + +/** + * @title NoKeyStorage + * @notice A middleware extension that provides no key storage functionality + * @dev Implements BaseMiddleware and always reverts on key operations + */ +abstract contract NoKeyStorage is BaseMiddleware { + bool public constant NoKeyStorageEnabled = true; + + error KeyStorageDisabled(); + + /** + * @notice Gets the operator address associated with a key + * @param key The key to lookup (unused) + * @return The operator address (always reverts) + */ + function operatorByKey(bytes memory key) public pure override returns (address) { + revert KeyStorageDisabled(); + } + + /** + * @notice Gets an operator's active key + * @param operator The operator address to lookup (unused) + * @return The operator's key (always reverts) + */ + function operatorKey(address operator) public pure override returns (bytes memory) { + revert KeyStorageDisabled(); + } + + /** + * @notice Checks if a key was active at a specific timestamp + * @param timestamp The timestamp to check (unused) + * @param key The key to check (unused) + * @return Whether key was active (always reverts) + */ + function keyWasActiveAt(uint48 timestamp, bytes memory key) public pure override returns (bool) { + revert KeyStorageDisabled(); + } + + /** + * @notice Updates an operator's key + * @param operator The operator address (unused) + * @param key The new key (unused) + */ + function _updateKey(address operator, bytes memory key) internal virtual override { + revert KeyStorageDisabled(); + } +} diff --git a/src/middleware/extensions/sigs/Ed25519Sig.sol b/src/middleware/extensions/sigs/EdDSASig.sol similarity index 80% rename from src/middleware/extensions/sigs/Ed25519Sig.sol rename to src/middleware/extensions/sigs/EdDSASig.sol index ea41282..35a8435 100644 --- a/src/middleware/extensions/sigs/Ed25519Sig.sol +++ b/src/middleware/extensions/sigs/EdDSASig.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {Ed25519} from "../../../libraries/Ed25519.sol"; +import {EdDSA} from "../../../libraries/EdDSA.sol"; import {BaseSig} from "./BaseSig.sol"; /** - * @title Ed25519Sig - * @notice Contract for verifying Ed25519 signatures against operator keys - * @dev Implements BaseSig interface using Ed25519 signature verification + * @title EdDSASig + * @notice Contract for verifying EdDSA signatures over Ed25519 against operator keys + * @dev Implements BaseSig interface using EdDSA signature verification */ -abstract contract Ed25519Sig is BaseSig { - bool public constant Ed25519SigEnabled = true; +abstract contract EdDSASig is BaseSig { + bool public constant EdDSASigEnabled = true; /** * @notice Verifies that a signature was created by the owner of a key @@ -40,6 +40,6 @@ abstract contract Ed25519Sig is BaseSig { * @dev Wrapper around Ed25519.verify which handles decompression and curve operations */ function verify(bytes memory message, bytes memory signature, bytes32 key) internal returns (bool) { - return Ed25519.verify(message, signature, key); + return EdDSA.verify(message, signature, key); } } From dacbed3fec12ee70dc4f74f673831597c960550f Mon Sep 17 00:00:00 2001 From: alrxy Date: Tue, 26 Nov 2024 14:27:28 +0700 Subject: [PATCH 068/115] fix: lint --- src/examples/sqrt-task-network/SqrtTaskMiddleware.sol | 9 +-------- src/managers/KeyManager.sol | 1 - src/middleware/extensions/key-storages/NoKeyStorage.sol | 8 ++++++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 51f43fd..e968c1f 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -17,14 +17,7 @@ import {OwnableAccessManager} from "../../middleware/extensions/access-managers/ import {NoKeyStorage} from "../../middleware/extensions/key-storages/NoKeyStorage.sol"; import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; -contract SqrtTaskMiddleware is - SharedVaults, - Operators, - NoKeyStorage, - EIP712, - OwnableAccessManager, - TimestampCapture -{ +contract SqrtTaskMiddleware is SharedVaults, Operators, NoKeyStorage, EIP712, OwnableAccessManager, TimestampCapture { using Subnetwork for address; using Math for uint256; diff --git a/src/managers/KeyManager.sol b/src/managers/KeyManager.sol index 203c979..6f811b8 100644 --- a/src/managers/KeyManager.sol +++ b/src/managers/KeyManager.sol @@ -13,7 +13,6 @@ abstract contract KeyManager is BaseManager { * @param operator The address of the operator * @param key The key to update */ - function _updateKey(address operator, bytes memory key) internal virtual; /** diff --git a/src/middleware/extensions/key-storages/NoKeyStorage.sol b/src/middleware/extensions/key-storages/NoKeyStorage.sol index 659d2f2..62d2781 100644 --- a/src/middleware/extensions/key-storages/NoKeyStorage.sol +++ b/src/middleware/extensions/key-storages/NoKeyStorage.sol @@ -18,7 +18,9 @@ abstract contract NoKeyStorage is BaseMiddleware { * @param key The key to lookup (unused) * @return The operator address (always reverts) */ - function operatorByKey(bytes memory key) public pure override returns (address) { + function operatorByKey( + bytes memory key + ) public pure override returns (address) { revert KeyStorageDisabled(); } @@ -27,7 +29,9 @@ abstract contract NoKeyStorage is BaseMiddleware { * @param operator The operator address to lookup (unused) * @return The operator's key (always reverts) */ - function operatorKey(address operator) public pure override returns (bytes memory) { + function operatorKey( + address operator + ) public pure override returns (bytes memory) { revert KeyStorageDisabled(); } From 7818c559278e56a284ed51224ca17e126489b81f Mon Sep 17 00:00:00 2001 From: alrxy Date: Tue, 26 Nov 2024 14:50:13 +0700 Subject: [PATCH 069/115] feat: versions instead of enabled --- src/middleware/extensions/SharedVaults.sol | 2 +- src/middleware/extensions/Subnetworks.sol | 2 +- .../extensions/access-managers/NoAccessManager.sol | 2 +- .../extensions/access-managers/OwnableAccessManager.sol | 2 +- .../extensions/access-managers/OzAccessManaged.sol | 2 +- .../extensions/capture-timestamps/EpochCapture.sol | 2 +- .../extensions/capture-timestamps/TimestampCapture.sol | 2 +- src/middleware/extensions/key-storages/KeyStorage256.sol | 5 +++-- src/middleware/extensions/key-storages/KeyStorageBytes.sol | 3 ++- src/middleware/extensions/key-storages/NoKeyStorage.sol | 2 +- .../extensions/operators/ForcePauseSelfRegisterOperators.sol | 2 +- src/middleware/extensions/operators/Operators.sol | 2 +- .../extensions/operators/SelfRegisterOperators.sol | 2 +- src/middleware/extensions/sigs/ECDSASig.sol | 2 +- src/middleware/extensions/sigs/EdDSASig.sol | 2 +- 15 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 1348604..9afffcc 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -9,7 +9,7 @@ import {BaseMiddleware} from "../BaseMiddleware.sol"; * @dev Extends BaseMiddleware to provide access control for vault management functions */ abstract contract SharedVaults is BaseMiddleware { - bool public constant SharedVaultsEnabled = true; + uint64 public constant SharedVaults_VERSION = 1; /** * @notice Registers a new shared vault diff --git a/src/middleware/extensions/Subnetworks.sol b/src/middleware/extensions/Subnetworks.sol index e4367c0..cec458e 100644 --- a/src/middleware/extensions/Subnetworks.sol +++ b/src/middleware/extensions/Subnetworks.sol @@ -11,7 +11,7 @@ import {BaseMiddleware} from "../BaseMiddleware.sol"; * @dev Extends BaseMiddleware to provide access control for subnetwork management functions */ abstract contract Subnetworks is BaseMiddleware { - bool public constant SubnetworksEnabled = true; + uint64 public constant Subnetworks_VERSION = 1; /** * @notice Registers a new subnetwork diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/middleware/extensions/access-managers/NoAccessManager.sol index 940d9b9..8d0b3eb 100644 --- a/src/middleware/extensions/access-managers/NoAccessManager.sol +++ b/src/middleware/extensions/access-managers/NoAccessManager.sol @@ -9,7 +9,7 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Implements BaseMiddleware and always reverts on access checks */ abstract contract NoAccessManager is BaseMiddleware { - bool public constant NoAccessManagerEnabled = true; + uint64 public constant NoAccessManager_VERSION = 1; /** * @notice Error thrown when access is denied diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/middleware/extensions/access-managers/OwnableAccessManager.sol index 99c73fd..4b270fa 100644 --- a/src/middleware/extensions/access-managers/OwnableAccessManager.sol +++ b/src/middleware/extensions/access-managers/OwnableAccessManager.sol @@ -9,7 +9,7 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Implements BaseMiddleware with owner-based access control */ abstract contract OwnableAccessManager is BaseMiddleware { - bool public constant OwnableAccessManagerEnabled = true; + uint64 public constant OwnableAccessManager_VERSION = 1; /** * @notice Error thrown when a non-owner address attempts to call a restricted function diff --git a/src/middleware/extensions/access-managers/OzAccessManaged.sol b/src/middleware/extensions/access-managers/OzAccessManaged.sol index 7075228..a808259 100644 --- a/src/middleware/extensions/access-managers/OzAccessManaged.sol +++ b/src/middleware/extensions/access-managers/OzAccessManaged.sol @@ -11,7 +11,7 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Implements BaseMiddleware with OpenZeppelin's AccessManagedUpgradeable functionality */ abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { - bool public constant OzAccessManagedEnabled = true; + uint64 public constant OzAccessManaged_VERSION = 1; /** * @notice Initializes the contract with an authority address * @param authority The address to set as the access manager authority diff --git a/src/middleware/extensions/capture-timestamps/EpochCapture.sol b/src/middleware/extensions/capture-timestamps/EpochCapture.sol index 801e440..a84103b 100644 --- a/src/middleware/extensions/capture-timestamps/EpochCapture.sol +++ b/src/middleware/extensions/capture-timestamps/EpochCapture.sol @@ -5,7 +5,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {BaseMiddleware} from "../../BaseMiddleware.sol"; abstract contract EpochCapture is BaseMiddleware { - bool public constant EpochCaptureEnabled = true; + uint64 public constant EpochCapture_VERSION = 1; struct EpochCaptureStorage { uint48 startTimestamp; diff --git a/src/middleware/extensions/capture-timestamps/TimestampCapture.sol b/src/middleware/extensions/capture-timestamps/TimestampCapture.sol index 54d37f1..f772703 100644 --- a/src/middleware/extensions/capture-timestamps/TimestampCapture.sol +++ b/src/middleware/extensions/capture-timestamps/TimestampCapture.sol @@ -5,7 +5,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {BaseMiddleware} from "../../BaseMiddleware.sol"; abstract contract TimestampCapture is BaseMiddleware { - bool public constant TimestampCaptureEnabled = true; + uint64 public constant TimestampCapture_VERSION = 1; /* * @notice Returns the current timestamp minus 1 second. diff --git a/src/middleware/extensions/key-storages/KeyStorage256.sol b/src/middleware/extensions/key-storages/KeyStorage256.sol index 281033b..41e6391 100644 --- a/src/middleware/extensions/key-storages/KeyStorage256.sol +++ b/src/middleware/extensions/key-storages/KeyStorage256.sol @@ -12,9 +12,10 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; * @dev Extends BaseMiddleware to provide key management functionality */ abstract contract KeyStorage256 is BaseMiddleware { - using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; - using PauseableEnumerableSet for PauseableEnumerableSet.Status; + uint64 public constant KeyStorage256_VERSION = 1; + using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; + error DuplicateKey(); error KeyAlreadyEnabled(); error MaxDisabledKeysReached(); diff --git a/src/middleware/extensions/key-storages/KeyStorageBytes.sol b/src/middleware/extensions/key-storages/KeyStorageBytes.sol index 62ef739..269aaa1 100644 --- a/src/middleware/extensions/key-storages/KeyStorageBytes.sol +++ b/src/middleware/extensions/key-storages/KeyStorageBytes.sol @@ -12,8 +12,9 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; * @dev Extends BaseManager to provide key management functionality */ abstract contract KeyStorageBytes is BaseMiddleware { + uint64 public constant KeyStorageBytes_VERSION = 1; + using PauseableEnumerableSet for PauseableEnumerableSet.BytesSet; - using PauseableEnumerableSet for PauseableEnumerableSet.Status; error DuplicateKey(); error MaxDisabledKeysReached(); diff --git a/src/middleware/extensions/key-storages/NoKeyStorage.sol b/src/middleware/extensions/key-storages/NoKeyStorage.sol index 62d2781..5da6d34 100644 --- a/src/middleware/extensions/key-storages/NoKeyStorage.sol +++ b/src/middleware/extensions/key-storages/NoKeyStorage.sol @@ -9,7 +9,7 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Implements BaseMiddleware and always reverts on key operations */ abstract contract NoKeyStorage is BaseMiddleware { - bool public constant NoKeyStorageEnabled = true; + uint64 public constant NoKeyStorage_VERSION = 1; error KeyStorageDisabled(); diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index 3cdd5ae..c3000ea 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -9,7 +9,7 @@ import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; * @dev Implements force pause functionality and prevents unpausing of force-paused operators */ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { - bool public constant ForcePauseSelfRegisterOperatorsEnabled = true; + uint64 public constant ForcePauseSelfRegisterOperators_VERSION = 1; struct ForcePauseSelfRegisterOperatorsStorage { mapping(address => bool) forcePaused; diff --git a/src/middleware/extensions/operators/Operators.sol b/src/middleware/extensions/operators/Operators.sol index 330ea59..3a500a4 100644 --- a/src/middleware/extensions/operators/Operators.sol +++ b/src/middleware/extensions/operators/Operators.sol @@ -9,7 +9,7 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; * @dev Provides core operator management functionality with hooks for customization */ abstract contract Operators is BaseMiddleware { - bool public constant OperatorsEnabled = true; + uint64 public constant Operators_VERSION = 1; /** * @notice Registers a new operator with an optional vault association diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index 10f4868..58ee3c8 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -12,7 +12,7 @@ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/Signa * @dev Extends BaseMiddleware, BaseSig, and EIP712Upgradeable to provide signature-based operator management */ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgradeable { - bool public constant SelfRegisterOperatorsEnabled = true; + uint64 public constant SelfRegisterOperators_VERSION = 1; error InvalidSignature(); diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/middleware/extensions/sigs/ECDSASig.sol index 8629d7f..63092a3 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/middleware/extensions/sigs/ECDSASig.sol @@ -10,7 +10,7 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; * @dev Implements BaseSig interface using OpenZeppelin's ECDSA library */ abstract contract ECDSASig is BaseSig { - bool public constant ECDSASigEnabled = true; + uint64 public constant ECDSASig_VERSION = 1; using ECDSA for bytes32; diff --git a/src/middleware/extensions/sigs/EdDSASig.sol b/src/middleware/extensions/sigs/EdDSASig.sol index 35a8435..09b558d 100644 --- a/src/middleware/extensions/sigs/EdDSASig.sol +++ b/src/middleware/extensions/sigs/EdDSASig.sol @@ -10,7 +10,7 @@ import {BaseSig} from "./BaseSig.sol"; * @dev Implements BaseSig interface using EdDSA signature verification */ abstract contract EdDSASig is BaseSig { - bool public constant EdDSASigEnabled = true; + uint64 public constant EdDSASig_VERSION = 1; /** * @notice Verifies that a signature was created by the owner of a key From 40b04501d77303261e712d923a96812c367097c1 Mon Sep 17 00:00:00 2001 From: alrxy Date: Tue, 26 Nov 2024 14:53:43 +0700 Subject: [PATCH 070/115] fix: non-revert noaccessmanager and init owner in sqrttaskmiddleware --- .../sqrt-task-network/SqrtTaskMiddleware.sol | 17 +++++++++++++++-- .../access-managers/NoAccessManager.sol | 13 ++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index e968c1f..3ba3c0b 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -44,9 +44,22 @@ contract SqrtTaskMiddleware is SharedVaults, Operators, NoKeyStorage, EIP712, Ow address operatorRegistry, address vaultRegistry, address operatorNetOptin, - uint48 slashingWindow + uint48 slashingWindow, + address owner ) EIP712("SqrtTaskMiddleware", "1") { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, owner); + } + + function initialize( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptin, + address owner + ) public override initializer { + super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + __OwnableAccessManaged_init(owner); } function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/middleware/extensions/access-managers/NoAccessManager.sol index 8d0b3eb..b49195e 100644 --- a/src/middleware/extensions/access-managers/NoAccessManager.sol +++ b/src/middleware/extensions/access-managers/NoAccessManager.sol @@ -10,17 +10,12 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; */ abstract contract NoAccessManager is BaseMiddleware { uint64 public constant NoAccessManager_VERSION = 1; - - /** - * @notice Error thrown when access is denied - */ - error NoAccess(); - + /** - * @notice Checks access and always reverts - * @dev This function is called internally to enforce access control + * @notice Checks access and always allows access + * @dev This function is called internally to enforce access control and will always allow access */ function _checkAccess() internal pure override { - revert NoAccess(); + // Allow all access by default } } From 0b16b204b7993b597ef9f8549a3e214664f456ea Mon Sep 17 00:00:00 2001 From: alrxy Date: Tue, 26 Nov 2024 15:23:02 +0700 Subject: [PATCH 071/115] fix: init not override in sqrttaskmiddleware --- src/examples/sqrt-task-network/SqrtTaskMiddleware.sol | 2 +- src/middleware/extensions/access-managers/NoAccessManager.sol | 2 +- src/middleware/extensions/key-storages/KeyStorage256.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 3ba3c0b..eb472fb 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -57,7 +57,7 @@ contract SqrtTaskMiddleware is SharedVaults, Operators, NoKeyStorage, EIP712, Ow address operatorRegistry, address operatorNetOptin, address owner - ) public override initializer { + ) public initializer { super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); __OwnableAccessManaged_init(owner); } diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/middleware/extensions/access-managers/NoAccessManager.sol index b49195e..9383584 100644 --- a/src/middleware/extensions/access-managers/NoAccessManager.sol +++ b/src/middleware/extensions/access-managers/NoAccessManager.sol @@ -10,7 +10,7 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; */ abstract contract NoAccessManager is BaseMiddleware { uint64 public constant NoAccessManager_VERSION = 1; - + /** * @notice Checks access and always allows access * @dev This function is called internally to enforce access control and will always allow access diff --git a/src/middleware/extensions/key-storages/KeyStorage256.sol b/src/middleware/extensions/key-storages/KeyStorage256.sol index 41e6391..dcf0e28 100644 --- a/src/middleware/extensions/key-storages/KeyStorage256.sol +++ b/src/middleware/extensions/key-storages/KeyStorage256.sol @@ -15,7 +15,7 @@ abstract contract KeyStorage256 is BaseMiddleware { uint64 public constant KeyStorage256_VERSION = 1; using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; - + error DuplicateKey(); error KeyAlreadyEnabled(); error MaxDisabledKeysReached(); From fe7276a3d49dfa0f77512b06d5bc40ed781496d0 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 27 Nov 2024 14:50:43 +0700 Subject: [PATCH 072/115] chore: readme --- README.md | 458 +++++++++----------------------------------------- imgs/arch.jpg | Bin 141050 -> 0 bytes 2 files changed, 78 insertions(+), 380 deletions(-) delete mode 100644 imgs/arch.jpg diff --git a/README.md b/README.md index 254da94..b243619 100644 --- a/README.md +++ b/README.md @@ -1,428 +1,126 @@ -# Middleware SDK +# Middleware Development Guide -The Symbiotic Middleware SDK is a collection of foundational contracts and libraries designed to simplify the development of network middleware on the Symbiotic platform. It provides essential functionalities such as operator management, key handling, vault interactions, subnetwork operations, and slashing mechanisms. This SDK enables developers to create custom middleware solutions tailored to their network's requirements with ease. +This repository provides a framework for developing middleware in a modular and extensible way. It leverages various base contracts and extensions to handle key functionalities such as operator management, access control, key storage, and timestamp capturing. -## Table of Contents +## Key Components: -- [Features](#features) -- [Architecture](#architecture) -- [Usage](#usage) - - [Libraries](#libraries) - - [PauseableEnumerableSet Library](#pauseableenumerableset-library) - - [Contracts](#contracts) - - [BaseManager Contract](#basemanager-contract) - - [Operator Management Modules](#operator-management-modules) - - [Vault Management Modules](#vault-management-modules) - - [Key Management Modules](#key-management-modules) - - [Subnetwork Management](#subnetwork-management) - - [Examples](#examples) - - [SimpleMiddleware Example](#simplemiddleware-example) - - [SqrtTaskMiddleware Example](#sqrttaskmiddleware-example) - - [Writing Your Own Network Middleware](#writing-your-own-network-middleware) - - [Custom Modules](#custom-modules) - - [Example: Self-Register Operator Manager](#example-self-register-operator-manager) -- [Important Notes](#important-notes) -- [License](#license) +- **BaseMiddleware**: The foundational contract that combines core manager functionalities from `VaultManager`, `OperatorManager`, `AccessManager`, and `KeyManager`. -## Features +- **Extensions**: Modular contracts that provide additional functionalities. Key extensions include: -- **Operator Management**: Register, pause, unpause, and manage operators within the network. -- **Vault Management**: Interact with vaults for staking, power calculation, and slashing mechanisms. -- **Key Management**: Handle operator keys (both general and BLS keys), including updates and activity checks. -- **Subnetwork Support**: Manage multiple subnetworks within the main network, including registration and pausing. -- **Epoch Management**: Utilities for handling epochs, including start times and durations. -- **Slashing Mechanisms**: Implement instant and veto-based slashing for misbehaving operators. -- **Extensibility**: Ability to write custom modules to extend or modify the SDK's functionalities. -- **Example Implementations**: Includes example contracts demonstrating how to extend and use the SDK. + - **Operators**: Manages operator registration and vault relationships. + + - **KeyStorage**: Manages operator keys. Variants include `KeyStorage256`, `KeyStorageBytes`, and `NoKeyStorage`. + + - **AccessManager**: Controls access to restricted functions. Implementations include `OwnableAccessManager`, `OzAccessManaged`, and `NoAccessManager`. + + - **CaptureTimestamp**: Captures the active state at specific timestamps. Options are `EpochCapture` and `TimestampCapture`. + + - **Signature Verification**: Verifies operator signatures. Implementations include `ECDSASig` and `EdDSASig`. -## Architecture +## Middleware Examples -The SDK is organized into modular components, each responsible for managing a specific aspect of the middleware: - -- **Libraries**: Reusable code segments such as `PauseableEnumerableSet` for managing enumerable sets with pause functionality. -- **Contracts**: Core contracts like `BaseManager`, `OperatorManager`, `VaultManager`, and key managers that provide essential middleware functionalities. -- **Examples**: Sample implementations like `SimpleMiddleware` and `SqrtTaskMiddleware` to illustrate how to build upon the SDK. -- **Custom Modules**: The SDK allows for the creation of custom modules to extend or override default behaviors, enabling developers to tailor the middleware to their specific needs. - -Developers can inherit from these modules and extend them to implement custom logic tailored to their specific requirements. - -![arch](imgs/arch.jpg) - -## Usage - -### Libraries - -#### PauseableEnumerableSet Library - -The `PauseableEnumerableSet` library extends the functionality of enumerable sets by adding pause and unpause capabilities to individual elements within the set. - -**Features:** - -- Manage sets of `address` or `uint160` values. -- Pause and unpause individual elements. -- Track enabled and disabled epochs for each element. -- Prevent operations on paused elements until they are unpaused. -- Ensure data consistency by enforcing immutable periods before certain actions can be reversed. - -### Contracts - -#### BaseManager Contract - -The `BaseManager` contract is an abstract base contract that provides foundational middleware functionalities, including epoch management, subnetwork handling, and immutable period configurations. - -**Key Features:** - -- **Epoch Management**: Calculate current epoch, epoch start times, and manage epoch durations. -- **Subnetwork Management**: Register, pause, unpause, and unregister subnetworks. -- **Immutable Epochs**: Enforce immutable periods before certain actions (like unpausing) can be performed. -- **Slashing Window Configuration**: Set slashing windows relative to epoch durations to ensure timely slashing actions. - -**Key Functions:** - -- `getCurrentEpoch() → uint32`: Returns the current epoch based on the timestamp. -- `getEpochStart(uint32 epoch) → uint48`: Returns the start timestamp of a given epoch. -- `registerSubnetwork(uint96 subnetwork)`: Registers a new subnetwork. -- `pauseSubnetwork(uint96 subnetwork)`: Pauses an active subnetwork. -- `unpauseSubnetwork(uint96 subnetwork)`: Unpauses a subnetwork after the immutable period. -- `unregisterSubnetwork(uint96 subnetwork)`: Unregisters a subnetwork. -- `subnetworksLength() → uint256`: Returns the number of registered subnetworks. -- `activeSubnetworks() → uint160[]`: Returns a list of active subnetworks. - -#### Operator Management Modules - -The SDK provides modules for managing operators within the network. - -- **`BaseOperatorManager` Contract**: Provides internal functions for operator management, including registration, pausing, unpausing, and unregistration. -- **`DefaultOperatorManager` Contract**: Inherits from `BaseOperatorManager` and exposes public functions callable by the contract owner. - -**Key Functions:** - -- `registerOperator(address operator)`: Registers a new operator. -- `pauseOperator(address operator)`: Pauses an operator, making them inactive. -- `unpauseOperator(address operator)`: Unpauses a paused operator after the immutable period. -- `unregisterOperator(address operator)`: Unregisters an operator from the network. -- `operatorsLength() → uint256`: Returns the total number of registered operators. -- `operatorWithTimesAt(uint256 pos) → (address, uint32, uint32, uint32)`: Retrieves operator details at a specific index. -- `activeOperators() → address[]`: Returns a list of active operators. - -#### Vault Management Modules - -The SDK includes modules for managing vaults, which are critical for staking and slashing operations. - -- **`BaseVaultManager` Contract**: Provides core functionalities for vault management, including stake calculation and slashing mechanisms. -- **`DefaultVaultManager` Contract**: Extends `BaseVaultManager` to include public methods for managing vaults. - -**Key Functions:** - -- `registerSharedVault(address vault)`: Registers a new shared vault accessible by all operators. -- `registerOperatorVault(address operator, address vault)`: Registers a new operator-specific vault. -- `pauseSharedVault(address vault)`: Pauses a shared vault. -- `unpauseSharedVault(address vault)`: Unpauses a shared vault after the immutable period. -- `pauseOperatorVault(address operator, address vault)`: Pauses an operator-specific vault. -- `unpauseOperatorVault(address operator, address vault)`: Unpauses an operator vault after the immutable period. -- `unregisterSharedVault(address vault)`: Unregisters a shared vault. -- `unregisterOperatorVault(address operator, address vault)`: Unregisters an operator-specific vault. -- `sharedVaultsLength() → uint256`: Returns the number of shared vaults. -- `sharedVaultWithEpochsAt(uint256 pos) → (address, uint32, uint32, uint32)`: Retrieves shared vault details at a specific index. -- `operatorVaultsLength(address operator) → uint256`: Returns the number of vaults for a specific operator. -- `operatorVaultWithEpochsAt(address operator, uint256 pos) → (address, uint32, uint32, uint32)`: Retrieves operator vault details at a specific index. -- `getOperatorStake(address operator) → uint256`: Returns the stake of an operator in the current epoch. -- `getOperatorPower(address operator) → uint256`: Returns the power of an operator based on their stake. This function can be overridden to implement custom stake-to-power logic. - -**Implementing Custom `stakeToPower` Logic:** +Below are examples of middleware implementations using different combinations of the extensions. +#### SimplePosMiddleware ```solidity -function stakeToPower(address vault, uint256 stake) public view virtual override returns (uint256) { - // Implement custom logic to convert stake to power - // For example, use an oracle to price different vault assets or weight vaults differently - uint256 assetPrice = getAssetPriceFromOracle(vault); - return stake * assetPrice; +contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableAccessManager, EpochCapture { + // Implementation details... } ``` -**Explanation:** - -The `stakeToPower` function is critical for converting an operator's stake into power, which can influence their weight in consensus mechanisms or voting. By overriding this function, you can implement custom logic to: - -- **Price Different Vault Assets Using an Oracle**: If your operators stake assets with varying market values, you can integrate with an oracle to fetch real-time prices. This ensures that the power assigned to an operator accurately reflects the current value of their staked assets. - -- **Weight Vaults Differently**: You may assign different weights to vaults based on criteria such as asset volatility, liquidity, or strategic importance. By adjusting the power calculation, you can incentivize operators to stake in preferred vaults or balance the network according to your needs. - -This flexibility allows you to create a more nuanced and fair system that reflects the true value of stakes from different vaults. - -#### Key Management Modules - -Key management is essential for operator authentication and validation. The SDK provides modules for managing both general keys and BLS keys. - -- **`BaseKeyManager` Contract**: Manages general keys associated with operators. -- **`DefaultKeyManager` Contract**: Extends `BaseKeyManager` to expose public methods for key management. -- **`BaseBLSKeyManager` Contract**: Specifically manages BLS (Boneh–Lynn–Shacham) cryptographic keys. -- **`DefaultBLSKeyManager` Contract**: Extends `BaseBLSKeyManager` to expose public methods. - -**Key Functions (General Key Management):** - -- `updateKey(address operator, bytes32 key)`: Updates the key associated with an operator. -- `operatorKey(address operator) → bytes32`: Retrieves the current key of an operator. -- `operatorByKey(bytes32 key) → address`: Returns the operator associated with a specific key. -- `keyWasActiveAt(uint32 epoch, bytes32 key) → bool`: Checks if a key was active during a specific epoch. - -**Key Functions (BLS Key Management):** - -- `updateBLSKey(address operator, bytes memory key)`: Updates the BLS key associated with an operator. -- `operatorBLSKey(address operator) → bytes memory`: Retrieves the current BLS key of an operator. -- `operatorByBLSKey(bytes memory key) → address`: Returns the operator associated with a specific BLS key. -- `blsKeyWasActiveAt(uint32 epoch, bytes memory key) → bool`: Checks if a BLS key was active during a specific epoch. - -#### Subnetwork Management - -Subnetworks allow for segmented management within the main network. - -**Key Functions:** - -- `registerSubnetwork(uint96 subnetwork)`: Registers a new subnetwork. -- `pauseSubnetwork(uint96 subnetwork)`: Pauses an active subnetwork. -- `unpauseSubnetwork(uint96 subnetwork)`: Unpauses a subnetwork after the immutable period. -- `unregisterSubnetwork(uint96 subnetwork)`: Unregisters a subnetwork. -- `subnetworksLength() → uint256`: Returns the number of subnetworks. -- `subnetworkWithTimesAt(uint256 pos) → (uint160, uint32, uint32, uint32)`: Retrieves subnetwork details at a specific index. -- `activeSubnetworks() → uint160[]`: Returns a list of active subnetworks. - -### Examples - -#### SimpleMiddleware Example - -The `SimpleMiddleware` contract demonstrates how to combine various modules to build a middleware that manages validators and handles slashing. - -**Features:** - -- **Validator Set Construction**: Compiles a list of active validators based on the current epoch. -- **Power Calculation**: Calculates the power of each validator, which can be used for consensus mechanisms. -- **Slashing Mechanism**: Implements a `slash` function to penalize misbehaving operators proportionally to their stake. +Features: -**Key Structures and Functions:** +- Manages operator keys and stakes. +- Retrieves validator sets and total stakes. +- Implements slashing logic based on epochs. -- **ValidatorData Struct**: - - ```solidity - struct ValidatorData { - uint256 power; - bytes32 key; - } - ``` - -- `getTotalStake() → uint256`: Returns the total stake of all active operators. -- `getTotalPower() → uint256`: Returns the total power of all active operators. -- `getValidatorSet() → ValidatorData[]`: Retrieves the current validator set. -- `slash(uint32 epoch, bytes32 key, uint256 amount, bytes[][] memory stakeHints, bytes[] memory slashHints) → SlashResponse[]`: Executes slashing on a misbehaving operator. - -**Note on `stakeToPower` in SimpleMiddleware:** - -In the `SimpleMiddleware` example, you can override the `stakeToPower` function to use an oracle for pricing different vault assets or weighting vaults differently. This allows the middleware to accurately reflect the value of stakes from various vaults, ensuring a fair and balanced validator set. - -For instance: +#### SqrtTaskMiddleware ```solidity -function stakeToPower(address vault, uint256 stake) public view virtual override returns (uint256) { - // Use an oracle to get the price of the asset staked in the vault - uint256 assetPrice = oracle.getPrice(vault); - // Convert stake to power based on asset price - return stake * assetPrice; +contract SqrtTaskMiddleware is SharedVaults, Operators, NoKeyStorage, EIP712, OwnableAccessManager, TimestampCapture { + // Implementation details... } ``` -#### SqrtTaskMiddleware Example - -The `SqrtTaskMiddleware` contract is an advanced example showing how to implement custom logic in the middleware. It extends the default modules and introduces a task-based system where operators solve computational tasks. - -**Features:** - -- **Task Creation**: Users can create tasks by specifying a value and an operator. -- **Task Completion**: Operators submit solutions along with signatures. The middleware verifies the solution and signature. -- **Custom Slashing**: Operators are slashed if they provide incorrect solutions. - -**Key Structures and Functions:** - -- **Task Struct**: - - ```solidity - struct Task { - uint48 captureTimestamp; - uint256 value; - address operator; - bool completed; - } - ``` - -- `createTask(uint256 value, address operator) → uint256`: Creates a new task for an operator. -- `completeTask(uint256 taskIndex, uint256 answer, bytes calldata signature, bytes[] calldata stakeHints, bytes[] calldata slashHints) → bool`: Allows an operator to complete a task and checks the validity of the answer. -- `_verify(uint256 taskIndex, uint256 answer, bytes calldata signature) → bool`: Verifies the operator's answer and signature. -- `_slash(uint256 taskIndex, bytes[] calldata stakeHints, bytes[] calldata slashHints)`: Executes slashing if the operator's answer is incorrect. +Features: -**Note:** +- Allows creation of computational tasks. +- Verifies task completion using signatures. +- Implements slashing for incorrect task completion. -In `SqrtTaskMiddleware`, subnetworks are not used, and attempts to manage subnetworks are disabled by overriding the functions and reverting: +#### SelfRegisterMiddleware ```solidity -function registerSubnetwork(uint96 subnetwork) public pure override { - revert(); +contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyStorage256, ECDSASig, NoAccessManager, TimestampCapture { + // Implementation details... } - -// Similar overrides for other subnetwork functions... ``` -### Writing Your Own Network Middleware - -The Middleware SDK is designed to be flexible and extensible, allowing you to create custom network middleware tailored to your specific requirements. This section guides you through the process of writing your own network middleware using the SDK modules. - -#### Steps to Build Your Custom Middleware - -1. **Define Your Middleware's Purpose and Requirements** - - - **Identify Functionalities**: Determine what functionalities your middleware needs to provide, such as custom slashing conditions, validator set management, or unique staking mechanisms. - - **Select Modules**: Decide which SDK modules are necessary for your middleware (e.g., operator management, vault management, key management). - - **Custom Logic**: Consider any custom logic or features you need to implement beyond the provided modules. - -2. **Select and Import the Necessary Modules** - - - **Import Modules**: Import the base or default modules that provide the required functionalities. - - **Example Imports**: - - ```solidity - import {DefaultOperatorManager} from "./OperatorManagers/DefaultOperatorManager.sol"; - import {DefaultVaultManager} from "./VaultManagers/DefaultVaultManager.sol"; - import {DefaultKeyManager} from "./KeyManagers/DefaultKeyManager.sol"; - ``` - -3. **Create Your Middleware Contract** - - - **Inherit Modules**: Inherit from the selected modules to compose your middleware contract. - - **Example Contract Structure**: - - ```solidity - contract CustomMiddleware is DefaultOperatorManager, DefaultVaultManager, DefaultKeyManager { - // Custom logic here - } - ``` +Features: -4. **Implement Custom Logic** +- Operators can self-register using ECDSA signatures. +- Manages operator keys and vault associations. +- No access restrictions on functions. - - **Override Functions**: Override base functions to introduce custom behavior. - - **Example: Custom `stakeToPower` Function**: +#### SelfRegisterEd25519Middleware - ```solidity - function stakeToPower(address vault, uint256 stake) public view virtual override returns (uint256) { - // Implement custom logic to convert stake to power - uint256 assetPrice = getAssetPriceFromOracle(vault); - return stake * assetPrice; - } - ``` - - - **Add New Functions**: Implement new functions for additional processes, such as specialized slashing conditions or custom validator selection criteria. - - **Example: Custom Slashing Logic**: - - ```solidity - function customSlash(address operator, uint256 penalty) public onlyOwner { - // Custom slashing logic - // For example, slash a fixed penalty amount from the operator's stake - // Implement the slashing mechanism according to your network's rules - } - ``` - -5. **Handle Epoch and State Management** - - - **Epoch Utilities**: Use the epoch management utilities provided by `BaseManager` to handle time-based state changes. - - **State Transitions**: Ensure that your middleware correctly manages state transitions between epochs, especially if your custom logic depends on epoch-specific data. - -### Custom Modules - -The Middleware SDK not only provides default modules but also allows developers to create custom modules to extend or modify the SDK's functionalities. This enables you to tailor the middleware to fit unique requirements that may not be covered by the default modules. - -#### Example: Self-Register Operator Manager - -In some networks, it might be desirable for operators to register themselves without the intervention of the contract owner or an administrator. Below is an example of how you can create a custom operator manager module that allows for self-registration, using the internal functions from `BaseOperatorManager`, similar to `DefaultOperatorManager`. - -##### Creating a Self-Register Operator Manager - -1. **Create a New Contract** - - ```solidity - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.25; - - import {BaseOperatorManager} from "./BaseOperatorManager.sol"; - - contract SelfRegisterOperatorManager is BaseOperatorManager { - error AlreadyRegistered(); - error NotOptedIn(); - error NotOperator(); - - constructor() { - // Initialization if needed - } - - function registerOperator() public { - address operator = msg.sender; - _registerOperator(operator); - } +```solidity +contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, KeyStorage256, EdDSASig, NoAccessManager, TimestampCapture { + // Implementation details... +} +``` - function unregisterOperator() public { - address operator = msg.sender; - _unregisterOperator(operator); - } +Features: - // Optionally, you can allow operators to pause and unpause themselves - function pauseOperator() public { - address operator = msg.sender; - _pauseOperator(operator); - } +- Similar to `SelfRegisterMiddleware` but uses Ed25519 keys and signatures. - function unpauseOperator() public { - address operator = msg.sender; - _unpauseOperator(operator); - } - } - ``` +## Getting Started -2. **Using Internal Functions** +To develop your middleware: - In this example, the `SelfRegisterOperatorManager` contract extends `BaseOperatorManager` and exposes public functions that internally call the protected functions provided by `BaseOperatorManager`. This approach mirrors how `DefaultOperatorManager` operates but allows operators to register themselves. +1. **Inherit from `BaseMiddleware`**: This provides access to core functionalities. -3. **Self-Registration Logic** +2. **Choose Extensions**: Based on your requirements, include extensions for operator management, key storage, access control, and timestamp capturing. - The internal functions `_registerOperator`, `_unregisterOperator`, `_pauseOperator`, and `_unpauseOperator` handle validation and state changes. By using these functions, you ensure consistency and reuse the existing logic. +3. **Initialize Properly**: Ensure all inherited contracts are properly initialized. For upgradeable contracts, use the `initializer` modifier and call `_disableInitializers` in the constructor to prevent double initialization. -4. **Error Handling and Validation** +4. **Implement Required Functions**: Override functions as needed to implement your middleware's logic. - The internal functions already include necessary validations, such as checking if the operator is a valid entity and has opted in. Therefore, you can rely on these internal functions for error handling. +## Example: Creating a Custom Middleware -5. **Integrate the Custom Module** +```solidity +contract MyCustomMiddleware is BaseMiddleware, Operators, KeyStorage256, OwnableAccessManager, TimestampCapture { + uint64 public constant MyCustomMiddleware_VERSION = 1; + + function initialize( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptIn, + address owner + ) public initializer { + super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + __OwnableAccessManaged_init(owner); + } + + // Additional implementation... +} +``` - In your middleware contract, inherit from `SelfRegisterOperatorManager` instead of `DefaultOperatorManager`: +## Notes - ```solidity - contract CustomMiddleware is SelfRegisterOperatorManager, DefaultVaultManager, DefaultKeyManager { - // Custom logic here - } - ``` +- **Storage Slots**: When creating extensions, ensure you follow the ERC-7201 standard for storage slot allocation to prevent conflicts. -6. **Benefits** +- **Versioning**: Include a public constant variable for versioning in your contracts (e.g., `uint64 public constant MyExtension_VERSION = 1;`). - - **Reusability**: By using the internal functions from `BaseOperatorManager`, you ensure consistency with the SDK's architecture. - - **Encapsulation**: Internal functions handle validation and state changes, keeping your public functions clean and focused. - - **Flexibility**: Operators can manage their participation autonomously. +- **Access Control**: Choose an appropriate `AccessManager` based on your needs. For unrestricted access, use `NoAccessManager`. For owner-based access, use `OwnableAccessManager`. -## Important Notes +- **Key Storage**: Select a `KeyStorage` implementation that fits your key requirements. Use `KeyStorage256` for 256-bit keys, `KeyStorageBytes` for arbitrary-length keys, or `NoKeyStorage` if keys are not needed. -- **Epoch-Based Data**: Most data is relevant only for the current epoch. Ensure that data is fetched at the finalized block moment using calls at specific blocks. -- **Immutable Periods**: Certain actions, like unpausing operators or vaults, require waiting for an immutable period (specified in epochs) before they can be reversed. -- **Slashing Windows**: The slashing window is the duration during which a slashing action can be executed after misbehavior is detected. It should be set appropriately relative to the epoch duration. -- **Finalized Blocks**: Networks typically operate on finalized blocks. Use finalized blocks to retrieve data to avoid discrepancies due to chain reorganizations or forks. -- **Data Consistency**: Be mindful that most state changes are epoch-bound, and historical data may not reflect the current state. Design your middleware to account for this. +This framework provides flexibility in building middleware by allowing you to mix and match various extensions based on your requirements. By following the modular approach and best practices outlined, you can develop robust middleware solutions that integrate seamlessly with the network. ## License -This project is licensed under the [MIT License](LICENSE). - ---- - -For any questions, issues, or contributions, please open an issue or submit a pull request on the repository. \ No newline at end of file +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. diff --git a/imgs/arch.jpg b/imgs/arch.jpg deleted file mode 100644 index b893a26785855de55ddf435e300c7bebdea58755..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141050 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

1IkU{pCcv8=xxy|n&cGd**2i8w^c`-Amz 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 @WXfy&sRkN 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 Date: Wed, 27 Nov 2024 14:53:34 +0700 Subject: [PATCH 073/115] fix: basemiddleware docstrings --- src/middleware/BaseMiddleware.sol | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 4bbcdb7..114943a 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -8,9 +8,14 @@ import {KeyManager} from "../managers/KeyManager.sol"; /** * @title BaseMiddleware - * @notice Abstract base contract that combines core manager functionality - * @dev Inherits from VaultManager, OperatorManager, AccessManager and KeyManager to provide - * comprehensive middleware capabilities for vault and operator management, access control, - * and key management + * @notice Abstract base contract that combines core manager functionality for building middleware + * @dev Inherits from VaultManager, OperatorManager, AccessManager and KeyManager to provide: + * - Vault management and registration + * - Operator management and registration + * - Access control and permissions + * - Key storage and management + * + * This contract serves as a foundation for building custom middleware by providing essential + * management capabilities that can be extended with additional functionality. */ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager {} From e875a73eb6fb7a9729ee35b0428bce2c35231b70 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 27 Nov 2024 16:42:15 +0700 Subject: [PATCH 074/115] feat: stake to power extensions --- README.md | 4 +++- .../SelfRegisterEd25519Middleware.sol | 5 +++-- .../SelfRegisterMiddleware.sol | 5 +++-- .../SimplePosMiddleware.sol | 12 +++++++++-- .../sqrt-task-network/SqrtTaskMiddleware.sol | 13 ++++++++++-- src/managers/BaseManager.sol | 4 +--- src/middleware/BaseMiddleware.sol | 2 +- .../stake-powers/EqualStakePower.sol | 21 +++++++++++++++++++ 8 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 src/middleware/extensions/stake-powers/EqualStakePower.sol diff --git a/README.md b/README.md index b243619..08b9b44 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Middleware Development Guide -This repository provides a framework for developing middleware in a modular and extensible way. It leverages various base contracts and extensions to handle key functionalities such as operator management, access control, key storage, and timestamp capturing. +This repository provides a framework for developing middleware in a modular and extensible way. It leverages various base contracts and extensions to handle key functionalities such as operator management, access control, key storage, timestamp capturing and stake to power calculation. ## Key Components: @@ -18,6 +18,8 @@ This repository provides a framework for developing middleware in a modular and - **Signature Verification**: Verifies operator signatures. Implementations include `ECDSASig` and `EdDSASig`. + - **StakePower**: Calculates operator power based on stake. Implementations include `EqualStakePower` for 1:1 stake-to-power ratio, and can be extended for custom power calculations. + ## Middleware Examples Below are examples of middleware implementations using different combinations of the extensions. diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index aa4067f..209cab6 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -14,7 +14,7 @@ import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; - +import {EqualStakePower} from "../../middleware/extensions/stake-powers/EqualStakePower.sol"; import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; import {EdDSASig} from "../../middleware/extensions/sigs/EdDSASig.sol"; @@ -24,7 +24,8 @@ contract SelfRegisterEd25519Middleware is KeyStorage256, EdDSASig, NoAccessManager, - TimestampCapture + TimestampCapture, + EqualStakePower { /* * @notice Constructor for initializing the SelfRegisterEd25519Middleware contract. diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index a063e35..01042a5 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -15,7 +15,7 @@ import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfR import {ECDSASig} from "../../middleware/extensions/sigs/ECDSASig.sol"; import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; - +import {EqualStakePower} from "../../middleware/extensions/stake-powers/EqualStakePower.sol"; import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; contract SelfRegisterMiddleware is @@ -24,7 +24,8 @@ contract SelfRegisterMiddleware is KeyStorage256, ECDSASig, NoAccessManager, - TimestampCapture + TimestampCapture, + EqualStakePower { /* * @notice Constructor for initializing the SelfRegisterMiddleware contract. diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 5a4b05b..a109cf8 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -15,8 +15,16 @@ import {Operators} from "../../middleware/extensions/operators/Operators.sol"; import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; import {EpochCapture} from "../../middleware/extensions/capture-timestamps/EpochCapture.sol"; import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; - -contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableAccessManager, EpochCapture { +import {EqualStakePower} from "../../middleware/extensions/stake-powers/EqualStakePower.sol"; + +contract SimplePosMiddleware is + SharedVaults, + Operators, + KeyStorage256, + OwnableAccessManager, + EpochCapture, + EqualStakePower +{ using Subnetwork for address; error InactiveKeySlash(); // Error thrown when trying to slash an inactive key diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index eb472fb..1aa5e74 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -16,8 +16,17 @@ import {Operators} from "../../middleware/extensions/operators/Operators.sol"; import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; import {NoKeyStorage} from "../../middleware/extensions/key-storages/NoKeyStorage.sol"; import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; - -contract SqrtTaskMiddleware is SharedVaults, Operators, NoKeyStorage, EIP712, OwnableAccessManager, TimestampCapture { +import {EqualStakePower} from "../../middleware/extensions/stake-powers/EqualStakePower.sol"; + +contract SqrtTaskMiddleware is + SharedVaults, + Operators, + NoKeyStorage, + EIP712, + OwnableAccessManager, + TimestampCapture, + EqualStakePower +{ using Subnetwork for address; using Math for uint256; diff --git a/src/managers/BaseManager.sol b/src/managers/BaseManager.sol index c45486d..9007067 100644 --- a/src/managers/BaseManager.sol +++ b/src/managers/BaseManager.sol @@ -87,7 +87,5 @@ abstract contract BaseManager is Initializable { * @param stake The stake amount * @return power The calculated voting power */ - function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256 power) { - return stake; - } + function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256 power); } diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 114943a..ff7f430 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -11,7 +11,7 @@ import {KeyManager} from "../managers/KeyManager.sol"; * @notice Abstract base contract that combines core manager functionality for building middleware * @dev Inherits from VaultManager, OperatorManager, AccessManager and KeyManager to provide: * - Vault management and registration - * - Operator management and registration + * - Operator management and registration * - Access control and permissions * - Key storage and management * diff --git a/src/middleware/extensions/stake-powers/EqualStakePower.sol b/src/middleware/extensions/stake-powers/EqualStakePower.sol new file mode 100644 index 0000000..bc50f5d --- /dev/null +++ b/src/middleware/extensions/stake-powers/EqualStakePower.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseManager} from "../../../managers/BaseManager.sol"; + +/** + * @title EqualStakePower + * @notice Implementation of a 1:1 stake to power conversion + * @dev Simply returns the stake amount as the power amount without any modifications + */ +abstract contract EqualStakePower is BaseManager { + /** + * @notice Converts stake amount to voting power using a 1:1 ratio + * @param vault The vault address (unused in this implementation) + * @param stake The stake amount + * @return power The calculated voting power (equal to stake) + */ + function stakeToPower(address vault, uint256 stake) public pure override returns (uint256 power) { + return stake; + } +} From 70f9ef74ee1b0d7a2ab07cf36423ea257caef6b1 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 27 Nov 2024 16:47:15 +0700 Subject: [PATCH 075/115] refactor: remove operator stake public function --- .../SimplePosMiddleware.sol | 15 +---- src/managers/VaultManager.sol | 57 ------------------- 2 files changed, 3 insertions(+), 69 deletions(-) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index a109cf8..08c8a70 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -40,7 +40,7 @@ contract SimplePosMiddleware is struct SlashParams { uint48 epochStart; address operator; - uint256 totalStake; + uint256 totalPower; address[] vaults; uint160[] subnetworks; } @@ -81,15 +81,6 @@ contract SimplePosMiddleware is __EpochCapture_init(epochDuration); } - /* - * @notice Returns the total stake for the active operators in the current epoch. - * @return The total stake amount. - */ - function getTotalStake() public view returns (uint256) { - address[] memory operators = activeOperators(); // Get the list of active operators - return _totalStake(operators); // Return the total stake for the current epoch - } - /* * @notice Returns the total power for the active operators in the current epoch. * @return The total power amount. @@ -149,7 +140,7 @@ contract SimplePosMiddleware is _checkCanSlash(epoch, key); - params.totalStake = getOperatorStakeAt(params.operator, params.epochStart); + params.totalPower = getOperatorPowerAt(params.operator, params.epochStart); params.vaults = activeVaultsAt(params.epochStart, params.operator); params.subnetworks = activeSubnetworksAt(params.epochStart); @@ -170,7 +161,7 @@ contract SimplePosMiddleware is subnetwork, params.operator, params.epochStart, stakeHints[i][j] ); - uint256 slashAmount = Math.mulDiv(amount, stake, params.totalStake); + uint256 slashAmount = Math.mulDiv(amount, stakeToPower(vault, stake), params.totalPower); if (slashAmount == 0) { continue; } diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index cbaac9b..f7e9eb5 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -400,47 +400,6 @@ abstract contract VaultManager is BaseManager { return stakeToPower(vault, stake); } - /** - * @notice Gets the total stake amount for an operator across all vaults and subnetworks - * @param operator The operator address - * @return stake The total stake amount - */ - function getOperatorStake( - address operator - ) public view virtual returns (uint256 stake) { - address[] memory vaults = activeVaults(operator); - uint160[] memory subnetworks = activeSubnetworks(); - - for (uint256 i; i < vaults.length; ++i) { - address vault = vaults[i]; - for (uint256 j; j < subnetworks.length; ++j) { - stake += getOperatorStake(operator, vault, uint96(subnetworks[j])); - } - } - - return stake; - } - - /** - * @notice Gets the total stake amount for an operator across all vaults and subnetworks at a specific timestamp - * @param operator The operator address - * @param timestamp The timestamp to check - * @return stake The total stake amount at the timestamp - */ - function getOperatorStakeAt(address operator, uint48 timestamp) public view virtual returns (uint256 stake) { - address[] memory vaults = activeVaultsAt(timestamp, operator); - uint160[] memory subnetworks = activeSubnetworksAt(timestamp); - - for (uint256 i; i < vaults.length; ++i) { - address vault = vaults[i]; - for (uint256 j; j < subnetworks.length; ++j) { - stake += getOperatorStakeAt(operator, vault, uint96(subnetworks[j]), timestamp); - } - } - - return stake; - } - /** * @notice Gets the total power amount for an operator across all vaults and subnetworks * @param operator The operator address @@ -482,22 +441,6 @@ abstract contract VaultManager is BaseManager { return power; } - /** - * @notice Calculates the total stake for a list of operators - * @param operators Array of operator addresses - * @return stake The total stake amount - */ - function _totalStake( - address[] memory operators - ) internal view returns (uint256 stake) { - for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = getOperatorStake(operators[i]); - stake += operatorStake; - } - - return stake; - } - /** * @notice Calculates the total power for a list of operators * @param operators Array of operator addresses From b24cbc06730913b4a94d711c9f0c0f71bff1f5e4 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 27 Nov 2024 17:23:42 +0700 Subject: [PATCH 076/115] feat: OzAccessControl --- README.md | 92 ++++++++- .../access-managers/NoAccessManager.sol | 2 +- .../access-managers/OwnableAccessManager.sol | 2 +- .../access-managers/OzAccessControl.sol | 192 ++++++++++++++++++ .../access-managers/OzAccessManaged.sol | 6 +- 5 files changed, 280 insertions(+), 14 deletions(-) create mode 100644 src/middleware/extensions/access-managers/OzAccessControl.sol diff --git a/README.md b/README.md index 08b9b44..c0ff681 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ This repository provides a framework for developing middleware in a modular and - **Extensions**: Modular contracts that provide additional functionalities. Key extensions include: - - **Operators**: Manages operator registration and vault relationships. + - **Operators**: Manages operator registration and operator's vault. - **KeyStorage**: Manages operator keys. Variants include `KeyStorage256`, `KeyStorageBytes`, and `NoKeyStorage`. - - **AccessManager**: Controls access to restricted functions. Implementations include `OwnableAccessManager`, `OzAccessManaged`, and `NoAccessManager`. + - **AccessManager**: Controls access to restricted functions. Implementations include `OwnableAccessManager`, `OzAccessControl`, `OzAccessManaged`, and `NoAccessManager`. - **CaptureTimestamp**: Captures the active state at specific timestamps. Options are `EpochCapture` and `TimestampCapture`. @@ -20,13 +20,18 @@ This repository provides a framework for developing middleware in a modular and - **StakePower**: Calculates operator power based on stake. Implementations include `EqualStakePower` for 1:1 stake-to-power ratio, and can be extended for custom power calculations. + - **SharedVaults**: Manages vaults shared between all operators. + + - **Subnetworks**: Manages subnetworks. + + ## Middleware Examples Below are examples of middleware implementations using different combinations of the extensions. #### SimplePosMiddleware ```solidity -contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableAccessManager, EpochCapture { +contract SimplePosMiddleware is SharedVaults, Operators, KeyStorage256, OwnableAccessManager, EpochCapture, EqualStakePower { // Implementation details... } ``` @@ -40,7 +45,7 @@ Features: #### SqrtTaskMiddleware ```solidity -contract SqrtTaskMiddleware is SharedVaults, Operators, NoKeyStorage, EIP712, OwnableAccessManager, TimestampCapture { +contract SqrtTaskMiddleware is SharedVaults, Operators, NoKeyStorage, EIP712, OwnableAccessManager, TimestampCapture, EqualStakePower { // Implementation details... } ``` @@ -54,7 +59,7 @@ Features: #### SelfRegisterMiddleware ```solidity -contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyStorage256, ECDSASig, NoAccessManager, TimestampCapture { +contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, KeyStorage256, ECDSASig, NoAccessManager, TimestampCapture, EqualStakePower { // Implementation details... } ``` @@ -75,7 +80,7 @@ contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, K Features: -- Similar to `SelfRegisterMiddleware` but uses Ed25519 keys and signatures. +- Similar to `SelfRegisterMiddleware` but uses Ed25519 keys and EdDSA signatures. ## Getting Started @@ -85,7 +90,41 @@ To develop your middleware: 2. **Choose Extensions**: Based on your requirements, include extensions for operator management, key storage, access control, and timestamp capturing. -3. **Initialize Properly**: Ensure all inherited contracts are properly initialized. For upgradeable contracts, use the `initializer` modifier and call `_disableInitializers` in the constructor to prevent double initialization. +3. **Initialize Properly**: Ensure all inherited contracts are properly initialized: + + - Use the `initializer` modifier on your initialization function + - Call `_disableInitializers()` in the constructor for upgradeable contracts + - Initialize all inherited contracts in the correct order + - Pass required parameters to each contract's initialization function + - Follow initialization order from most base to most derived contract + - Note: If your contract is not upgradeable, initialization can be done directly in the constructor: + ```solidity + constructor( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptIn, + address admin + ) { + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, admin); + } + ``` + - Example initialization pattern: + ```solidity + function initialize( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptIn, + address admin + ) public initializer { + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + __OzAccessManaged_init(admin); + __AdditionalExtension_init(); + } + ``` 4. **Implement Required Functions**: Override functions as needed to implement your middleware's logic. @@ -111,14 +150,49 @@ contract MyCustomMiddleware is BaseMiddleware, Operators, KeyStorage256, Ownable } ``` +5. **Configure OzAccessControl Roles**: When using OzAccessControl, set up roles and permissions: + + ```solidity + // Define role identifiers as constants + bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); + bytes32 public constant VAULT_ROLE = keccak256("VAULT_ROLE"); + bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); + + function initialize(...) public initializer { + // Initialize base contracts + __BaseMiddleware_init(...); + __OzAccessControl_init(admin); + + // Set up role hierarchy + _setRoleAdmin(OPERATOR_ROLE, MANAGER_ROLE); // Manager role can grant/revoke operator role + _setRoleAdmin(VAULT_ROLE, MANAGER_ROLE); // Manager role can grant/revoke vault role + _setRoleAdmin(MANAGER_ROLE, DEFAULT_ADMIN_ROLE); // Default admin can grant/revoke manager role + + // Assign roles to function selectors + _setSelectorRole(this.registerOperator.selector, OPERATOR_ROLE); + _setSelectorRole(this.registerVault.selector, VAULT_ROLE); + _setSelectorRole(this.updateParameters.selector, MANAGER_ROLE); + + // Grant initial roles + _grantRole(MANAGER_ROLE, admin); + } + ``` + ## Notes - **Storage Slots**: When creating extensions, ensure you follow the ERC-7201 standard for storage slot allocation to prevent conflicts. - **Versioning**: Include a public constant variable for versioning in your contracts (e.g., `uint64 public constant MyExtension_VERSION = 1;`). -- **Access Control**: Choose an appropriate `AccessManager` based on your needs. For unrestricted access, use `NoAccessManager`. For owner-based access, use `OwnableAccessManager`. - +- **Access Control**: Choose an appropriate `AccessManager` based on your needs: + - `NoAccessManager`: Allows unrestricted access to all functions + - `OwnableAccessManager`: Restricts access to a single owner address + - `OzAccessControl`: Implements OpenZeppelin-style role-based access control where different roles can be assigned to specific function selectors. Roles can be granted and revoked by role admins, with a default admin role that can manage all other roles. Roles can be set up by: + 1. Granting roles to addresses using `grantRole(bytes32 role, address account)` + 2. Setting role admins with `_setRoleAdmin(bytes32 role, bytes32 adminRole)` + 3. Assigning roles to function selectors via `_setSelectorRole(bytes4 selector, bytes32 role)` + - `OzAccessManaged`: Wraps OpenZeppelin's AccessManaged contract to integrate with external access control systems + - **Key Storage**: Select a `KeyStorage` implementation that fits your key requirements. Use `KeyStorage256` for 256-bit keys, `KeyStorageBytes` for arbitrary-length keys, or `NoKeyStorage` if keys are not needed. This framework provides flexibility in building middleware by allowing you to mix and match various extensions based on your requirements. By following the modular approach and best practices outlined, you can develop robust middleware solutions that integrate seamlessly with the network. diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/middleware/extensions/access-managers/NoAccessManager.sol index 9383584..c9ec786 100644 --- a/src/middleware/extensions/access-managers/NoAccessManager.sol +++ b/src/middleware/extensions/access-managers/NoAccessManager.sol @@ -15,7 +15,7 @@ abstract contract NoAccessManager is BaseMiddleware { * @notice Checks access and always allows access * @dev This function is called internally to enforce access control and will always allow access */ - function _checkAccess() internal pure override { + function _checkAccess() internal pure virtual override { // Allow all access by default } } diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/middleware/extensions/access-managers/OwnableAccessManager.sol index 4b270fa..009fd8c 100644 --- a/src/middleware/extensions/access-managers/OwnableAccessManager.sol +++ b/src/middleware/extensions/access-managers/OwnableAccessManager.sol @@ -65,7 +65,7 @@ abstract contract OwnableAccessManager is BaseMiddleware { * @notice Checks if the caller has access (is the owner) * @dev Reverts if the caller is not the owner */ - function _checkAccess() internal view override { + function _checkAccess() internal view virtual override { if (msg.sender != _owner()) { revert OnlyOwnerCanCall(msg.sender); } diff --git a/src/middleware/extensions/access-managers/OzAccessControl.sol b/src/middleware/extensions/access-managers/OzAccessControl.sol new file mode 100644 index 0000000..c6faad3 --- /dev/null +++ b/src/middleware/extensions/access-managers/OzAccessControl.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseMiddleware} from "../../BaseMiddleware.sol"; + +/** + * @title OzAccessControl + * @notice A middleware extension that implements role-based access control + * @dev Implements BaseMiddleware with role-based access control functionality + */ +abstract contract OzAccessControl is BaseMiddleware { + uint64 public constant OzAccessControl_VERSION = 1; + + struct RoleData { + mapping(address account => bool) hasRole; + bytes32 adminRole; + } + + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + /// @custom:storage-location erc7201:symbiotic.storage.OzAccessControl + struct OzAccessControlStorage { + mapping(bytes32 role => RoleData) _roles; + mapping(bytes4 selector => bytes32 role) _selectorRoles; + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.OzAccessControl")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant OzAccessControlStorageLocation = + 0xbe09a78a256419d2b885312b60a13e8082d8ab3c36c463fff4fbb086f1e96f00; + + function _getOzAccessControlStorage() private pure returns (OzAccessControlStorage storage $) { + assembly { + $.slot := OzAccessControlStorageLocation + } + } + + error AccessControlUnauthorizedAccount(address account, bytes32 role); + error AccessControlBadConfirmation(); + + event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); + event SelectorRoleSet(bytes4 indexed selector, bytes32 indexed role); + + /** + * @notice Initializes the contract with a default admin + * @param defaultAdmin The address to set as the default admin + */ + function __OzAccessControl_init( + address defaultAdmin + ) internal onlyInitializing { + _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); + } + + /** + * @notice Returns true if account has been granted role + * @param role The role to check + * @param account The account to check + * @return bool True if account has role + */ + function hasRole(bytes32 role, address account) public view virtual returns (bool) { + OzAccessControlStorage storage $ = _getOzAccessControlStorage(); + return $._roles[role].hasRole[account]; + } + + /** + * @notice Returns the admin role that controls the specified role + * @param role The role to get the admin for + * @return bytes32 The admin role + */ + function getRoleAdmin( + bytes32 role + ) public view virtual returns (bytes32) { + OzAccessControlStorage storage $ = _getOzAccessControlStorage(); + return $._roles[role].adminRole; + } + + /** + * @notice Returns the role required for a function selector + * @param selector The function selector + * @return bytes32 The required role + */ + function getRole( + bytes4 selector + ) public view virtual returns (bytes32) { + OzAccessControlStorage storage $ = _getOzAccessControlStorage(); + return $._selectorRoles[selector]; + } + + /** + * @notice Grants role to account if caller has admin role + * @param role The role to grant + * @param account The account to grant the role to + */ + function grantRole(bytes32 role, address account) public virtual { + bytes32 adminRole = getRoleAdmin(role); + if (!hasRole(adminRole, msg.sender)) { + revert AccessControlUnauthorizedAccount(msg.sender, adminRole); + } + _grantRole(role, account); + } + + /** + * @notice Revokes role from account if caller has admin role + * @param role The role to revoke + * @param account The account to revoke the role from + */ + function revokeRole(bytes32 role, address account) public virtual { + bytes32 adminRole = getRoleAdmin(role); + if (!hasRole(adminRole, msg.sender)) { + revert AccessControlUnauthorizedAccount(msg.sender, adminRole); + } + _revokeRole(role, account); + } + + /** + * @notice Allows an account to renounce a role they have + * @param role The role to renounce + * @param callerConfirmation Address of the caller for confirmation + */ + function renounceRole(bytes32 role, address callerConfirmation) public virtual { + if (callerConfirmation != msg.sender) { + revert AccessControlBadConfirmation(); + } + _revokeRole(role, callerConfirmation); + } + + /** + * @notice Sets the role required for a function selector + * @param selector The function selector + * @param role The required role + */ + function _setSelectorRole(bytes4 selector, bytes32 role) internal virtual { + OzAccessControlStorage storage $ = _getOzAccessControlStorage(); + $._selectorRoles[selector] = role; + emit SelectorRoleSet(selector, role); + } + + /** + * @notice Sets the admin role for a role + * @param role The role to set admin for + * @param adminRole The new admin role + */ + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { + OzAccessControlStorage storage $ = _getOzAccessControlStorage(); + bytes32 previousAdminRole = getRoleAdmin(role); + $._roles[role].adminRole = adminRole; + emit RoleAdminChanged(role, previousAdminRole, adminRole); + } + + /** + * @notice Internal function to grant a role + * @param role The role to grant + * @param account The account to grant the role to + * @return bool True if role was granted + */ + function _grantRole(bytes32 role, address account) internal virtual returns (bool) { + if (!hasRole(role, account)) { + OzAccessControlStorage storage $ = _getOzAccessControlStorage(); + $._roles[role].hasRole[account] = true; + emit RoleGranted(role, account, msg.sender); + return true; + } + return false; + } + + /** + * @notice Internal function to revoke a role + * @param role The role to revoke + * @param account The account to revoke the role from + * @return bool True if role was revoked + */ + function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { + if (hasRole(role, account)) { + OzAccessControlStorage storage $ = _getOzAccessControlStorage(); + $._roles[role].hasRole[account] = false; + emit RoleRevoked(role, account, msg.sender); + return true; + } + return false; + } + + /** + * @notice Checks access based on role required for the function selector + * @dev Implements BaseMiddleware's _checkAccess function + */ + function _checkAccess() internal view virtual override { + if (!hasRole(getRole(msg.sig), msg.sender)) { + revert AccessControlUnauthorizedAccount(msg.sender, getRole(msg.sig)); + } + } +} diff --git a/src/middleware/extensions/access-managers/OzAccessManaged.sol b/src/middleware/extensions/access-managers/OzAccessManaged.sol index a808259..f2a2cff 100644 --- a/src/middleware/extensions/access-managers/OzAccessManaged.sol +++ b/src/middleware/extensions/access-managers/OzAccessManaged.sol @@ -7,7 +7,7 @@ import {BaseMiddleware} from "../../BaseMiddleware.sol"; /** * @title OzAccessManaged - * @notice A middleware extension that integrates OpenZeppelin's AccessManager for access control + * @notice A middleware extension that integrates OpenZeppelin's AccessManaged for access control * @dev Implements BaseMiddleware with OpenZeppelin's AccessManagedUpgradeable functionality */ abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { @@ -25,10 +25,10 @@ abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { } /** - * @notice Checks if the caller has access through the OpenZeppelin AccessManager + * @notice Checks if the caller has access through the OpenZeppelin AccessManaged * @dev Delegates access check to OpenZeppelin's _checkCanCall function */ - function _checkAccess() internal override { + function _checkAccess() internal virtual override { _checkCanCall(msg.sender, msg.data); } } From c81f6f8a3659db5f8cf6e11e9d82b139a5a98dbf Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 13:55:23 +0400 Subject: [PATCH 077/115] feat: validate operator vault --- src/managers/BaseManager.sol | 4 ++-- src/managers/VaultManager.sol | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/managers/BaseManager.sol b/src/managers/BaseManager.sol index 9007067..76ce404 100644 --- a/src/managers/BaseManager.sol +++ b/src/managers/BaseManager.sol @@ -5,8 +5,8 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; abstract contract BaseManager is Initializable { - uint64 public constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type - uint64 public constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type + uint64 internal constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type + uint64 internal constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type /// @custom:storage-location erc7201:symbiotic.storage.BaseManager struct BaseManagerStorage { diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index f7e9eb5..b5d1077 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -9,6 +9,7 @@ import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; +import {IOperatorSpecificDelegator} from "@symbiotic/interfaces/delegator/IOperatorSpecificDelegator.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; @@ -38,6 +39,7 @@ abstract contract VaultManager is BaseManager { error UnknownSlasherType(); error NonVetoSlasher(); error TooOldTimestampSlash(); + error NotOperatorSpecificVault(); /// @custom:storage-location erc7201:symbiotic.storage.VaultManager struct VaultManagerStorage { @@ -57,6 +59,8 @@ abstract contract VaultManager is BaseManager { } } + uint64 internal constant OPERATOR_SPECIFIC_DELEGATOR_TYPE = 2; + /** * @dev Struct containing information about a slash response * @param vault The address of the vault being slashed @@ -521,6 +525,7 @@ abstract contract VaultManager is BaseManager { function _registerOperatorVault(address operator, address vault) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); _validateVault(vault); + _validateOperatorVault(vault, operator); if ($._sharedVaults.contains(vault)) { revert VaultAlreadyRegistred(); } @@ -689,4 +694,14 @@ abstract contract VaultManager is BaseManager { revert VaultEpochTooShort(); } } + + function _validateOperatorVault(address operator, address vault) internal view { + address delegator = IVault(vault).delegator(); + if ( + delegator == address(0) || IEntity(delegator).TYPE() != OPERATOR_SPECIFIC_DELEGATOR_TYPE + || IOperatorSpecificDelegator(delegator).operator() != operator + ) { + revert NotOperatorSpecificVault(); + } + } } From 019433a0ed16435334e71844063c43efb0103d9e Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 15:51:07 +0400 Subject: [PATCH 078/115] fix: add operator vault validation --- src/managers/VaultManager.sol | 2 +- test/SigTests.t.sol | 79 ++++++++++++++++++++++++++++++----- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index b5d1077..0506e33 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -525,7 +525,7 @@ abstract contract VaultManager is BaseManager { function _registerOperatorVault(address operator, address vault) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); _validateVault(vault); - _validateOperatorVault(vault, operator); + _validateOperatorVault(operator, vault); if ($._sharedVaults.contains(vault)) { revert VaultAlreadyRegistred(); } diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol index afa7373..5ea3423 100644 --- a/test/SigTests.t.sol +++ b/test/SigTests.t.sol @@ -3,9 +3,15 @@ pragma solidity ^0.8.25; import {Test, Vm, console} from "forge-std/Test.sol"; import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; +import {IOperatorSpecificDelegator} from "@symbiotic/interfaces/delegator/IOperatorSpecificDelegator.sol"; +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IVaultConfigurator} from "@symbiotic/interfaces/IVaultConfigurator.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; + import {SelfRegisterMiddleware} from "../src/examples/self-register-network/SelfRegisterMiddleware.sol"; import {SelfRegisterEd25519Middleware} from "../src/examples/self-register-network/SelfRegisterEd25519Middleware.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {EdDSA} from "../src/libraries/EdDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; contract SigTests is POCBaseTest { @@ -19,6 +25,8 @@ contract SigTests is POCBaseTest { bytes32 internal operatorPublicKey; string internal constant ED25519_TEST_DATA = "test/helpers/ed25519TestData.json"; address internal ed25519Operator; + address internal vault; + address internal vaultEd; function setUp() public override { SYMBIOTIC_CORE_PROJECT_ROOT = "lib/core/"; @@ -52,10 +60,14 @@ contract SigTests is POCBaseTest { _registerNetwork(address(0x456), address(ed25519Middleware)); _registerOperator(operator); _registerOperator(ed25519Operator); - _optInOperatorVault(vault1, operator); - _optInOperatorVault(vault1, ed25519Operator); _optInOperatorNetwork(operator, address(0x123)); _optInOperatorNetwork(ed25519Operator, address(0x456)); + + vault = address(_getOperatorVault(operator)); + _optInOperatorVault(IVault(vault), operator); + + vaultEd = address(_getOperatorVault(ed25519Operator)); + _optInOperatorVault(IVault(vaultEd), ed25519Operator); } function testEd25519RegisterOperator() public { @@ -65,7 +77,7 @@ contract SigTests is POCBaseTest { // Register operator using Ed25519 signature vm.prank(ed25519Operator); - ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + ed25519Middleware.registerOperator(abi.encode(key), address(vaultEd), signature); // Verify operator is registered correctly assertTrue(ed25519Middleware.isOperatorRegistered(ed25519Operator)); @@ -87,7 +99,7 @@ contract SigTests is POCBaseTest { // Attempt to register with invalid signature should fail vm.prank(ed25519Operator); vm.expectRevert(); - ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + ed25519Middleware.registerOperator(abi.encode(key), address(vaultEd), signature); } function testEd25519RegisterOperatorWrongSender() public { @@ -104,7 +116,7 @@ contract SigTests is POCBaseTest { // Attempt to register from different address should fail vm.prank(alice); vm.expectRevert(); - ed25519Middleware.registerOperator(abi.encode(key), address(vault1), abi.encodePacked(r, s)); + ed25519Middleware.registerOperator(abi.encode(key), address(vaultEd), abi.encodePacked(r, s)); } function testSelfRegisterOperator() public { @@ -115,7 +127,7 @@ contract SigTests is POCBaseTest { bytes memory signature = abi.encodePacked(r, s, v); // Register operator using their own signature vm.prank(operator); - middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault), signature); // Verify operator is registered correctly assertTrue(middleware.isOperatorRegistered(operator)); @@ -136,7 +148,7 @@ contract SigTests is POCBaseTest { // Attempt to register with mismatched key should fail vm.prank(operator); vm.expectRevert(); - middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault), signature); } function testSelfxRegisterOperatorWrongSender() public { @@ -149,7 +161,7 @@ contract SigTests is POCBaseTest { // Attempt to register from different address should fail vm.prank(alice); vm.expectRevert(); - middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault), signature); } function testSelxfRegisterOperatorAlreadyRegistered() public { @@ -160,12 +172,12 @@ contract SigTests is POCBaseTest { bytes memory signature = abi.encodePacked(r, s, v); // Register operator first time vm.prank(operator); - middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault), signature); // Attempt to register again should fail vm.prank(operator); vm.expectRevert(); - middleware.registerOperator(abi.encode(operatorPublicKey), address(vault1), signature); + middleware.registerOperator(abi.encode(operatorPublicKey), address(vault), signature); } function testEd25519RegisterOperatorMismatchedKeyAndSignature() public { @@ -176,6 +188,51 @@ contract SigTests is POCBaseTest { // Attempt to register with mismatched key and signature should fail vm.prank(ed25519Operator); vm.expectRevert(); - ed25519Middleware.registerOperator(abi.encode(key), address(vault1), signature); + ed25519Middleware.registerOperator(abi.encode(key), address(vault), signature); + } + + function _getOperatorVault(address operator) internal returns (IVault) { + address[] memory networkLimitSetRoleHolders = new address[](1); + networkLimitSetRoleHolders[0] = alice; + address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); + operatorNetworkSharesSetRoleHolders[0] = alice; + (address vault_,,) = vaultConfigurator.create( + IVaultConfigurator.InitParams({ + version: 1, + owner: alice, + vaultParams: abi.encode( + IVault.InitParams({ + collateral: address(collateral), + burner: address(0xdEaD), + epochDuration: 7 days, + depositWhitelist: false, + isDepositLimit: false, + depositLimit: 0, + defaultAdminRoleHolder: alice, + depositWhitelistSetRoleHolder: alice, + depositorWhitelistRoleHolder: alice, + isDepositLimitSetRoleHolder: alice, + depositLimitSetRoleHolder: alice + }) + ), + delegatorIndex: 2, + delegatorParams: abi.encode( + IOperatorSpecificDelegator.InitParams({ + baseParams: IBaseDelegator.BaseParams({ + defaultAdminRoleHolder: alice, + hook: address(0), + hookSetRoleHolder: alice + }), + networkLimitSetRoleHolders: networkLimitSetRoleHolders, + operator: operator + }) + ), + withSlasher: false, + slasherIndex: 0, + slasherParams: "" + }) + ); + + return IVault(vault_); } } From 52ae4ff70be94dc036889c12d92d74d1168b17f2 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 16:09:00 +0400 Subject: [PATCH 079/115] fix: statemind-Double import of IVetoSlasher in VaultManager.sol --- src/managers/VaultManager.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 0506e33..6e02e45 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -8,7 +8,6 @@ import {IEntity} from "@symbiotic/interfaces/common/IEntity.sol"; import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; -import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; import {IOperatorSpecificDelegator} from "@symbiotic/interfaces/delegator/IOperatorSpecificDelegator.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; @@ -698,7 +697,7 @@ abstract contract VaultManager is BaseManager { function _validateOperatorVault(address operator, address vault) internal view { address delegator = IVault(vault).delegator(); if ( - delegator == address(0) || IEntity(delegator).TYPE() != OPERATOR_SPECIFIC_DELEGATOR_TYPE + IEntity(delegator).TYPE() != OPERATOR_SPECIFIC_DELEGATOR_TYPE || IOperatorSpecificDelegator(delegator).operator() != operator ) { revert NotOperatorSpecificVault(); From 96b79492ac5c844717ac1f3839b90b473aa38664 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 16:10:20 +0400 Subject: [PATCH 080/115] fix: lint tests --- test/SigTests.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol index 5ea3423..d587bbb 100644 --- a/test/SigTests.t.sol +++ b/test/SigTests.t.sol @@ -191,7 +191,9 @@ contract SigTests is POCBaseTest { ed25519Middleware.registerOperator(abi.encode(key), address(vault), signature); } - function _getOperatorVault(address operator) internal returns (IVault) { + function _getOperatorVault( + address operator + ) internal returns (IVault) { address[] memory networkLimitSetRoleHolders = new address[](1); networkLimitSetRoleHolders[0] = alice; address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); From 702a09dbbffb72aacf0782132fde2a09693a1424 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 16:23:57 +0400 Subject: [PATCH 081/115] fix: statemind-Extra SLOAD and MLOAD calls --- src/libraries/PauseableEnumerableSet.sol | 15 ++++---- src/managers/VaultManager.sol | 45 ++++-------------------- 2 files changed, 16 insertions(+), 44 deletions(-) diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 372e997..9b05215 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -319,9 +319,10 @@ library PauseableEnumerableSet { * @return array Array of active uint160s */ function getActive(Uint160Set storage self, uint48 timestamp) internal view returns (uint160[] memory array) { - array = new uint160[](self.array.length); + uint256 arrayLen = self.array.length; + array = new uint160[](arrayLen); uint256 len; - for (uint256 i; i < self.array.length; ++i) { + for (uint256 i; i < arrayLen; ++i) { if (self.array[i].status.wasActiveAt(timestamp)) { array[len++] = self.array[i].value; } @@ -450,9 +451,10 @@ library PauseableEnumerableSet { * @return array Array of active bytes32s */ function getActive(Bytes32Set storage self, uint48 timestamp) internal view returns (bytes32[] memory array) { - array = new bytes32[](self.array.length); + uint256 arrayLen = self.array.length; + array = new bytes32[](arrayLen); uint256 len; - for (uint256 i; i < self.array.length; ++i) { + for (uint256 i; i < arrayLen; ++i) { if (self.array[i].status.wasActiveAt(timestamp)) { array[len++] = self.array[i].value; } @@ -601,9 +603,10 @@ library PauseableEnumerableSet { * @return array Array of active bytes values */ function getActive(BytesSet storage self, uint48 timestamp) internal view returns (bytes[] memory array) { - array = new bytes[](self.array.length); + uint256 arrayLen = self.array.length; + array = new bytes[](arrayLen); uint256 len; - for (uint256 i; i < self.array.length; ++i) { + for (uint256 i; i < arrayLen; ++i) { if (self.array[i].status.wasActiveAt(timestamp)) { array[len++] = self.array[i].value; } diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 6e02e45..6440e23 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -205,27 +205,7 @@ abstract contract VaultManager is BaseManager { function activeVaults() public view virtual returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); uint48 timestamp = getCaptureTimestamp(); - address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); - uint256 len = activeSharedVaults_.length; - address[] memory vaults = new address[](len + $._vaultOperator.length()); - - for (uint256 i; i < len; ++i) { - vaults[i] = activeSharedVaults_[i]; - } - - uint256 operatorVaultsLen = $._vaultOperator.length(); - for (uint256 i; i < operatorVaultsLen; ++i) { - (address vault, address operator) = $._vaultOperator.at(i); - if ($._operatorVaults[operator].wasActiveAt(timestamp, vault)) { - vaults[len++] = vault; - } - } - - assembly { - mstore(vaults, len) - } - - return vaults; + return activeVaultsAt(timestamp); } /** @@ -239,13 +219,13 @@ abstract contract VaultManager is BaseManager { VaultManagerStorage storage $ = _getVaultManagerStorage(); address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); uint256 len = activeSharedVaults_.length; - address[] memory vaults = new address[](len + $._vaultOperator.length()); + uint256 operatorVaultsLen = $._vaultOperator.length(); + address[] memory vaults = new address[](len + operatorVaultsLen); for (uint256 i; i < len; ++i) { vaults[i] = activeSharedVaults_[i]; } - uint256 operatorVaultsLen = $._vaultOperator.length(); for (uint256 i; i < operatorVaultsLen; ++i) { (address vault, address operator) = $._vaultOperator.at(i); if ($._operatorVaults[operator].wasActiveAt(timestamp, vault)) { @@ -270,19 +250,7 @@ abstract contract VaultManager is BaseManager { ) public view virtual returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); uint48 timestamp = getCaptureTimestamp(); - address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); - address[] memory activeOperatorVaults_ = $._operatorVaults[operator].getActive(timestamp); - - uint256 activeSharedVaultsLen = activeSharedVaults_.length; - address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults_.length); - for (uint256 i; i < activeSharedVaultsLen; ++i) { - vaults[i] = activeSharedVaults_[i]; - } - for (uint256 i; i < activeOperatorVaults_.length; ++i) { - vaults[activeSharedVaultsLen + i] = activeOperatorVaults_[i]; - } - - return vaults; + return activeVaultsAt(timestamp, operator); } /** @@ -297,11 +265,12 @@ abstract contract VaultManager is BaseManager { address[] memory activeOperatorVaults_ = $._operatorVaults[operator].getActive(timestamp); uint256 activeSharedVaultsLen = activeSharedVaults_.length; - address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaults_.length); + uint256 activeOperatorVaultsLen = activeOperatorVaults_.length; + address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaultsLen); for (uint256 i; i < activeSharedVaultsLen; ++i) { vaults[i] = activeSharedVaults_[i]; } - for (uint256 i; i < activeOperatorVaults_.length; ++i) { + for (uint256 i; i < activeOperatorVaultsLen; ++i) { vaults[activeSharedVaultsLen + i] = activeOperatorVaults_[i]; } From e772f1ab92206922c6c1ae1644a80e0271865267 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 16:25:57 +0400 Subject: [PATCH 082/115] fix: statemind-Unused errors in the OperatorManager contract --- src/managers/OperatorManager.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index 6166807..27e7643 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -16,8 +16,6 @@ abstract contract OperatorManager is BaseManager { error NotOperator(); error OperatorNotOptedIn(); - error OperatorNotRegistered(); - error OperatorAlreadyRegistred(); /// @custom:storage-location erc7201:symbiotic.storage.OperatorManager struct OperatorManagerStorage { From 1f5ca0f69b34812e3018c85aabcbb03b1b4fa47f Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 16:34:46 +0400 Subject: [PATCH 083/115] fix: statemind-Typos --- src/managers/VaultManager.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 6440e23..8c13797 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -32,7 +32,7 @@ abstract contract VaultManager is BaseManager { error NotVault(); error NotOperatorVault(); error VaultNotInitialized(); - error VaultAlreadyRegistred(); + error VaultAlreadyRegistered(); error VaultEpochTooShort(); error InactiveVaultSlash(); error UnknownSlasherType(); @@ -422,8 +422,7 @@ abstract contract VaultManager is BaseManager { address[] memory operators ) internal view returns (uint256 power) { for (uint256 i; i < operators.length; ++i) { - uint256 operatorStake = getOperatorPower(operators[i]); - power += operatorStake; + power += getOperatorPower(operators[i]); } return power; @@ -495,7 +494,7 @@ abstract contract VaultManager is BaseManager { _validateVault(vault); _validateOperatorVault(operator, vault); if ($._sharedVaults.contains(vault)) { - revert VaultAlreadyRegistred(); + revert VaultAlreadyRegistered(); } $._operatorVaults[operator].register(Time.timestamp(), vault); $._vaultOperator.set(vault, operator); @@ -648,7 +647,7 @@ abstract contract VaultManager is BaseManager { } if ($._vaultOperator.contains(vault)) { - revert VaultAlreadyRegistred(); + revert VaultAlreadyRegistered(); } uint48 vaultEpoch = IVault(vault).epochDuration(); From 99922305454a1fd53b2c19fc78a32dc75852d381 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 16:37:45 +0400 Subject: [PATCH 084/115] fix: Vault Check is outside VaultManager._validateVault() --- src/managers/VaultManager.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 8c13797..6c384e4 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -493,9 +493,7 @@ abstract contract VaultManager is BaseManager { VaultManagerStorage storage $ = _getVaultManagerStorage(); _validateVault(vault); _validateOperatorVault(operator, vault); - if ($._sharedVaults.contains(vault)) { - revert VaultAlreadyRegistered(); - } + $._operatorVaults[operator].register(Time.timestamp(), vault); $._vaultOperator.set(vault, operator); } @@ -646,7 +644,7 @@ abstract contract VaultManager is BaseManager { revert VaultNotInitialized(); } - if ($._vaultOperator.contains(vault)) { + if ($._vaultOperator.contains(vault) || $._sharedVaults.contains(vault)) { revert VaultAlreadyRegistered(); } From 3101f0f4ef03f2e5898d12ee0aa52a8720329c97 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 16:40:15 +0400 Subject: [PATCH 085/115] fix: statemind-Missing slasher check --- src/managers/VaultManager.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 6c384e4..918df30 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -37,6 +37,7 @@ abstract contract VaultManager is BaseManager { error InactiveVaultSlash(); error UnknownSlasherType(); error NonVetoSlasher(); + error NoSlasher(); error TooOldTimestampSlash(); error NotOperatorSpecificVault(); @@ -594,6 +595,10 @@ abstract contract VaultManager is BaseManager { } address slasher = IVault(vault).slasher(); + if (slasher == address(0)) { + revert NoSlasher(); + } + uint64 slasherType = IEntity(slasher).TYPE(); resp.vault = vault; resp.slasherType = slasherType; From da222005327d345c14c72aecf61189cfb95ea677 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 2 Dec 2024 16:53:37 +0400 Subject: [PATCH 086/115] fix: optimize vault manager code reusing --- src/managers/VaultManager.sol | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 918df30..82839d2 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -162,6 +162,16 @@ abstract contract VaultManager is BaseManager { return $._sharedVaults.getActive(getCaptureTimestamp()); } + /** + * @notice Gets all shared vaults that were active at a specific timestamp + * @param timestamp The timestamp to check + * @return address[] Array of shared vault addresses that were active at the timestamp + */ + function activeSharedVaultsAt(uint48 timestamp) public view returns (address[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.getActive(timestamp); + } + /** * @notice Gets the number of vaults associated with an operator * @param operator The operator address to query @@ -321,8 +331,7 @@ abstract contract VaultManager is BaseManager { */ function getOperatorStake(address operator, address vault, uint96 subnetwork) public view returns (uint256) { uint48 timestamp = getCaptureTimestamp(); - bytes32 subnetworkId = NETWORK().subnetwork(subnetwork); - return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); + return getOperatorStakeAt(operator, vault, subnetwork, timestamp); } /** @@ -351,8 +360,7 @@ abstract contract VaultManager is BaseManager { * @return uint256 The power amount */ function getOperatorPower(address operator, address vault, uint96 subnetwork) public view returns (uint256) { - uint256 stake = getOperatorStake(operator, vault, subnetwork); - return stakeToPower(vault, stake); + return getOperatorPowerAt(operator, vault, subnetwork, getCaptureTimestamp()); } /** @@ -381,17 +389,7 @@ abstract contract VaultManager is BaseManager { function getOperatorPower( address operator ) public view virtual returns (uint256 power) { - address[] memory vaults = activeVaults(operator); - uint160[] memory subnetworks = activeSubnetworks(); - - for (uint256 i; i < vaults.length; ++i) { - address vault = vaults[i]; - for (uint256 j; j < subnetworks.length; ++j) { - power += getOperatorPower(operator, vault, uint96(subnetworks[j])); - } - } - - return power; + return getOperatorPowerAt(operator, getCaptureTimestamp()); } /** From 95e4fd86d8edcc6a149bb53f35f8d1915afcca15 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 4 Dec 2024 14:59:41 +0400 Subject: [PATCH 087/115] chore: make stake functions private --- src/managers/VaultManager.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 82839d2..e43916a 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -329,7 +329,7 @@ abstract contract VaultManager is BaseManager { * @param subnetwork The subnetwork identifier * @return uint256 The stake amount */ - function getOperatorStake(address operator, address vault, uint96 subnetwork) public view returns (uint256) { + function getOperatorStake(address operator, address vault, uint96 subnetwork) private view returns (uint256) { uint48 timestamp = getCaptureTimestamp(); return getOperatorStakeAt(operator, vault, subnetwork, timestamp); } @@ -347,7 +347,7 @@ abstract contract VaultManager is BaseManager { address vault, uint96 subnetwork, uint48 timestamp - ) public view returns (uint256) { + ) private view returns (uint256) { bytes32 subnetworkId = NETWORK().subnetwork(subnetwork); return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); } From c7a044dd13612d67c2d33b62c7970c96cb7e9513 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 4 Dec 2024 15:01:50 +0400 Subject: [PATCH 088/115] chore: rm getOperatorStake function --- src/managers/VaultManager.sol | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index e43916a..44ce214 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -322,18 +322,6 @@ abstract contract VaultManager is BaseManager { return $._operatorVaults[operator].wasActiveAt(timestamp, vault); } - /** - * @notice Gets the stake amount for an operator in a vault and subnetwork - * @param operator The operator address - * @param vault The vault address - * @param subnetwork The subnetwork identifier - * @return uint256 The stake amount - */ - function getOperatorStake(address operator, address vault, uint96 subnetwork) private view returns (uint256) { - uint48 timestamp = getCaptureTimestamp(); - return getOperatorStakeAt(operator, vault, subnetwork, timestamp); - } - /** * @notice Gets the stake amount for an operator in a vault and subnetwork at a specific timestamp * @param operator The operator address From 07e71717936c10cdfeb5ab2c83c16e98b42654bf Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 5 Dec 2024 17:11:56 +0400 Subject: [PATCH 089/115] WIP: restructure --- .../SelfRegisterEd25519Middleware.sol | 17 ++-- .../SelfRegisterMiddleware.sol | 17 ++-- .../SimplePosMiddleware.sol | 19 +++-- .../sqrt-task-network/SqrtTaskMiddleware.sol | 17 ++-- src/managers/{ => base}/AccessManager.sol | 4 +- src/managers/{ => base}/BaseManager.sol | 35 +++----- src/managers/base/CaptureTimestampManager.sol | 24 ++++++ src/managers/{ => base}/KeyManager.sol | 0 src/managers/{ => base}/OperatorManager.sol | 42 +++------- .../base/SigManager.sol} | 6 +- src/managers/base/StakePowerManager.sol | 18 +++++ src/managers/{ => base}/VaultManager.sol | 81 +++++++++---------- .../extensions/access}/NoAccessManager.sol | 8 +- .../access}/OwnableAccessManager.sol | 26 +++--- .../extensions/access}/OzAccessControl.sol | 20 ++--- .../extensions/access}/OzAccessManaged.sol | 7 +- .../capture-timestamps/EpochCapture.sol | 24 +++--- .../capture-timestamps/TimestampCapture.sol | 22 +++++ .../extensions/keys/KeyManager256.sol} | 40 +++++---- .../extensions/keys/KeyManagerBytes.sol} | 40 +++++---- .../extensions/keys/NoKeyManager.sol} | 20 ++--- .../extensions/sigs/ECDSASig.sol | 10 +-- .../extensions/sigs/EdDSASig.sol | 10 +-- .../stake-powers/EqualStakePower.sol | 6 +- src/middleware/BaseMiddleware.sol | 14 ++-- src/middleware/extensions/SharedVaults.sol | 8 +- src/middleware/extensions/Subnetworks.sol | 10 +-- .../capture-timestamps/TimestampCapture.sol | 17 ---- .../ForcePauseSelfRegisterOperators.sol | 10 +-- .../extensions/operators/Operators.sol | 18 ++--- .../operators/SelfRegisterOperators.sol | 30 +++---- 31 files changed, 316 insertions(+), 304 deletions(-) rename src/managers/{ => base}/AccessManager.sol (81%) rename src/managers/{ => base}/BaseManager.sol (67%) create mode 100644 src/managers/base/CaptureTimestampManager.sol rename src/managers/{ => base}/KeyManager.sol (100%) rename src/managers/{ => base}/OperatorManager.sol (74%) rename src/{middleware/extensions/sigs/BaseSig.sol => managers/base/SigManager.sol} (74%) create mode 100644 src/managers/base/StakePowerManager.sol rename src/managers/{ => base}/VaultManager.sol (89%) rename src/{middleware/extensions/access-managers => managers/extensions/access}/NoAccessManager.sol (58%) rename src/{middleware/extensions/access-managers => managers/extensions/access}/OwnableAccessManager.sol (90%) rename src/{middleware/extensions/access-managers => managers/extensions/access}/OzAccessControl.sol (89%) rename src/{middleware/extensions/access-managers => managers/extensions/access}/OzAccessManaged.sol (81%) rename src/{middleware => managers}/extensions/capture-timestamps/EpochCapture.sol (67%) create mode 100644 src/managers/extensions/capture-timestamps/TimestampCapture.sol rename src/{middleware/extensions/key-storages/KeyStorage256.sol => managers/extensions/keys/KeyManager256.sol} (74%) rename src/{middleware/extensions/key-storages/KeyStorageBytes.sol => managers/extensions/keys/KeyManagerBytes.sol} (72%) rename src/{middleware/extensions/key-storages/NoKeyStorage.sol => managers/extensions/keys/NoKeyManager.sol} (75%) rename src/{middleware => managers}/extensions/sigs/ECDSASig.sol (83%) rename src/{middleware => managers}/extensions/sigs/EdDSASig.sol (86%) rename src/{middleware => managers}/extensions/stake-powers/EqualStakePower.sol (69%) delete mode 100644 src/middleware/extensions/capture-timestamps/TimestampCapture.sol diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index 209cab6..3eb9711 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -12,16 +12,17 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; -import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; -import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; -import {EqualStakePower} from "../../middleware/extensions/stake-powers/EqualStakePower.sol"; -import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; -import {EdDSASig} from "../../middleware/extensions/sigs/EdDSASig.sol"; + +import {NoAccessManager} from "../../managers/extensions/access/NoAccessManager.sol"; +import {TimestampCapture} from "../../managers/extensions/capture-timestamps/TimestampCapture.sol"; +import {EqualStakePower} from "../../managers/extensions/stake-powers/EqualStakePower.sol"; +import {KeyManager256} from "../../managers/extensions/keys/KeyManager256.sol"; +import {EdDSASig} from "../../managers/extensions/sigs/EdDSASig.sol"; contract SelfRegisterEd25519Middleware is SharedVaults, SelfRegisterOperators, - KeyStorage256, + KeyManager256, EdDSASig, NoAccessManager, TimestampCapture, @@ -52,8 +53,8 @@ contract SelfRegisterEd25519Middleware is address vaultRegistry, address operatorRegistry, address operatorNetOptIn - ) public override initializer { - super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + ) internal initializer { + __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); __SelfRegisterOperators_init("SelfRegisterEd25519Middleware"); } } diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index 01042a5..675c80c 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -12,16 +12,17 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; -import {ECDSASig} from "../../middleware/extensions/sigs/ECDSASig.sol"; -import {NoAccessManager} from "../../middleware/extensions/access-managers/NoAccessManager.sol"; -import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; -import {EqualStakePower} from "../../middleware/extensions/stake-powers/EqualStakePower.sol"; -import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; + +import {ECDSASig} from "../../managers/extensions/sigs/ECDSASig.sol"; +import {NoAccessManager} from "../../managers/extensions/access/NoAccessManager.sol"; +import {TimestampCapture} from "../../managers/extensions/capture-timestamps/TimestampCapture.sol"; +import {EqualStakePower} from "../../managers/extensions/stake-powers/EqualStakePower.sol"; +import {KeyManager256} from "../../managers/extensions/keys/KeyManager256.sol"; contract SelfRegisterMiddleware is SharedVaults, SelfRegisterOperators, - KeyStorage256, + KeyManager256, ECDSASig, NoAccessManager, TimestampCapture, @@ -52,8 +53,8 @@ contract SelfRegisterMiddleware is address vaultRegistry, address operatorRegistry, address operatorNetOptIn - ) public override initializer { - super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + ) internal initializer { + __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); __SelfRegisterOperators_init("SelfRegisterMiddleware"); } } diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 08c8a70..ab76d3b 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -5,22 +5,21 @@ import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/operators/Operators.sol"; -import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; -import {EpochCapture} from "../../middleware/extensions/capture-timestamps/EpochCapture.sol"; -import {KeyStorage256} from "../../middleware/extensions/key-storages/KeyStorage256.sol"; -import {EqualStakePower} from "../../middleware/extensions/stake-powers/EqualStakePower.sol"; + +import {OwnableAccessManager} from "../../managers/extensions/access/OwnableAccessManager.sol"; +import {EpochCapture} from "../../managers/extensions/capture-timestamps/EpochCapture.sol"; +import {KeyManager256} from "../../managers/extensions/keys/KeyManager256.sol"; +import {EqualStakePower} from "../../managers/extensions/stake-powers/EqualStakePower.sol"; contract SimplePosMiddleware is SharedVaults, Operators, - KeyStorage256, + KeyManager256, OwnableAccessManager, EpochCapture, EqualStakePower @@ -75,9 +74,9 @@ contract SimplePosMiddleware is address operatorNetOptin, address owner, uint48 epochDuration - ) public initializer { - super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); - __OwnableAccessManaged_init(owner); + ) internal initializer { + __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + __OwnableAccessManager_init(owner); __EpochCapture_init(epochDuration); } diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 1aa5e74..7a1a80b 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -13,15 +13,16 @@ import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/Signa import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; import {Operators} from "../../middleware/extensions/operators/Operators.sol"; -import {OwnableAccessManager} from "../../middleware/extensions/access-managers/OwnableAccessManager.sol"; -import {NoKeyStorage} from "../../middleware/extensions/key-storages/NoKeyStorage.sol"; -import {TimestampCapture} from "../../middleware/extensions/capture-timestamps/TimestampCapture.sol"; -import {EqualStakePower} from "../../middleware/extensions/stake-powers/EqualStakePower.sol"; + +import {OwnableAccessManager} from "../../managers/extensions/access/OwnableAccessManager.sol"; +import {NoKeyManager} from "../../managers/extensions/keys/NoKeyManager.sol"; +import {TimestampCapture} from "../../managers/extensions/capture-timestamps/TimestampCapture.sol"; +import {EqualStakePower} from "../../managers/extensions/stake-powers/EqualStakePower.sol"; contract SqrtTaskMiddleware is SharedVaults, Operators, - NoKeyStorage, + NoKeyManager, EIP712, OwnableAccessManager, TimestampCapture, @@ -66,9 +67,9 @@ contract SqrtTaskMiddleware is address operatorRegistry, address operatorNetOptin, address owner - ) public initializer { - super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); - __OwnableAccessManaged_init(owner); + ) internal initializer { + __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + __OwnableAccessManager_init(owner); } function createTask(uint256 value, address operator) external returns (uint256 taskIndex) { diff --git a/src/managers/AccessManager.sol b/src/managers/base/AccessManager.sol similarity index 81% rename from src/managers/AccessManager.sol rename to src/managers/base/AccessManager.sol index 70b0872..96a66ef 100644 --- a/src/managers/AccessManager.sol +++ b/src/managers/base/AccessManager.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + /** * @title AccessManager * @notice Abstract contract for managing access control * @dev Provides a modifier and internal function for checking access permissions */ -abstract contract AccessManager { +abstract contract AccessManager is Initializable { /** * @notice Modifier that checks access before executing a function * @dev Calls internal _checkAccess function and continues if allowed diff --git a/src/managers/BaseManager.sol b/src/managers/base/BaseManager.sol similarity index 67% rename from src/managers/BaseManager.sol rename to src/managers/base/BaseManager.sol index 76ce404..0a6366d 100644 --- a/src/managers/BaseManager.sol +++ b/src/managers/base/BaseManager.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {CaptureTimestampManager} from "./CaptureTimestampManager.sol"; -abstract contract BaseManager is Initializable { +abstract contract BaseManager is CaptureTimestampManager { uint64 internal constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type uint64 internal constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type @@ -21,7 +20,7 @@ abstract contract BaseManager is Initializable { bytes32 private constant BaseManagerStorageLocation = 0xb3503c3f5ee7753561129bea19627692ca916ecb48491bfcd223db17a12b8e00; - function _getBaseManagerStorage() private pure returns (BaseManagerStorage storage $) { + function _getBaseManagerStorage() internal pure returns (BaseManagerStorage storage $) { assembly { $.slot := BaseManagerStorageLocation } @@ -35,13 +34,13 @@ abstract contract BaseManager is Initializable { * @param operatorRegistry The address of the operator registry * @param operatorNetOptIn The address of the operator network opt-in service */ - function initialize( + function __BaseManager_init( address network, uint48 slashingWindow, address vaultRegistry, address operatorRegistry, address operatorNetOptIn - ) public virtual initializer { + ) internal onlyInitializing() { BaseManagerStorage storage $ = _getBaseManagerStorage(); $._network = network; $._slashingWindow = slashingWindow; @@ -50,42 +49,28 @@ abstract contract BaseManager is Initializable { $._operatorNetOptin = operatorNetOptIn; } - function NETWORK() public view returns (address) { + function NETWORK() internal view returns (address) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._network; } - function SLASHING_WINDOW() public view returns (uint48) { + function SLASHING_WINDOW() internal view returns (uint48) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._slashingWindow; } - function VAULT_REGISTRY() public view returns (address) { + function VAULT_REGISTRY() internal view returns (address) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._vaultRegistry; } - function OPERATOR_REGISTRY() public view returns (address) { + function OPERATOR_REGISTRY() internal view returns (address) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._operatorRegistry; } - function OPERATOR_NET_OPTIN() public view returns (address) { + function OPERATOR_NET_OPTIN() internal view returns (address) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._operatorNetOptin; } - - /** - * @notice Returns the current capture timestamp - * @return timestamp The current capture timestamp - */ - function getCaptureTimestamp() public view virtual returns (uint48 timestamp); - - /** - * @notice Converts stake amount to voting power - * @param vault The vault address - * @param stake The stake amount - * @return power The calculated voting power - */ - function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256 power); } diff --git a/src/managers/base/CaptureTimestampManager.sol b/src/managers/base/CaptureTimestampManager.sol new file mode 100644 index 0000000..711a10d --- /dev/null +++ b/src/managers/base/CaptureTimestampManager.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +/** + * @title CaptureTimestampManager + * @notice Abstract contract for managing capture timestamps + */ +abstract contract CaptureTimestampManager is Initializable { + /** + * @notice Returns the current capture timestamp + * @return timestamp The current capture timestamp + */ + function getCaptureTimestamp() internal view virtual returns (uint48 timestamp); + + /** + * @notice Returns the current timestamp + * @return timestamp The current timestamp + */ + function now() internal view returns (uint48) { + return Time.timestamp(); + } +} diff --git a/src/managers/KeyManager.sol b/src/managers/base/KeyManager.sol similarity index 100% rename from src/managers/KeyManager.sol rename to src/managers/base/KeyManager.sol diff --git a/src/managers/OperatorManager.sol b/src/managers/base/OperatorManager.sol similarity index 74% rename from src/managers/OperatorManager.sol rename to src/managers/base/OperatorManager.sol index 27e7643..6fcdecd 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/base/OperatorManager.sol @@ -4,12 +4,11 @@ pragma solidity ^0.8.25; import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseManager} from "./BaseManager.sol"; -import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; +import {PauseableEnumerableSet} from "../../libraries/PauseableEnumerableSet.sol"; abstract contract OperatorManager is BaseManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; @@ -32,34 +31,11 @@ abstract contract OperatorManager is BaseManager { } } - /** - * @notice Returns the total number of registered operators, including both active and inactive - * @return The number of registered operators - */ - function operatorsLength() public view returns (uint256) { - OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - return $._operators.length(); - } - - /** - * @notice Returns the operator and their associated enabled and disabled times at a specific position - * @param pos The index position in the operators array - * @return The operator address - * @return The enabled timestamp - * @return The disabled timestamp - */ - function operatorWithTimesAt( - uint256 pos - ) public view returns (address, uint48, uint48) { - OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - return $._operators.at(pos); - } - /** * @notice Returns a list of active operators * @return Array of addresses representing the active operators */ - function activeOperators() public view returns (address[] memory) { + function activeOperators() internal view returns (address[] memory) { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); return $._operators.getActive(getCaptureTimestamp()); } @@ -71,7 +47,7 @@ abstract contract OperatorManager is BaseManager { */ function activeOperatorsAt( uint48 timestamp - ) public view returns (address[] memory) { + ) internal view returns (address[] memory) { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); return $._operators.getActive(timestamp); } @@ -82,7 +58,7 @@ abstract contract OperatorManager is BaseManager { * @param operator The operator address to check * @return True if the operator was active at the timestamp, false otherwise */ - function operatorWasActiveAt(uint48 timestamp, address operator) public view returns (bool) { + function operatorWasActiveAt(uint48 timestamp, address operator) internal view returns (bool) { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); return $._operators.wasActiveAt(timestamp, operator); } @@ -94,7 +70,7 @@ abstract contract OperatorManager is BaseManager { */ function isOperatorRegistered( address operator - ) public view returns (bool) { + ) internal view returns (bool) { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); return $._operators.contains(operator); } @@ -117,7 +93,7 @@ abstract contract OperatorManager is BaseManager { } OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - $._operators.register(Time.timestamp(), operator); + $._operators.register(now(), operator); } /** @@ -128,7 +104,7 @@ abstract contract OperatorManager is BaseManager { address operator ) internal { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - $._operators.pause(Time.timestamp(), operator); + $._operators.pause(now(), operator); } /** @@ -139,7 +115,7 @@ abstract contract OperatorManager is BaseManager { address operator ) internal { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - $._operators.unpause(Time.timestamp(), SLASHING_WINDOW(), operator); + $._operators.unpause(now(), SLASHING_WINDOW(), operator); } /** @@ -150,6 +126,6 @@ abstract contract OperatorManager is BaseManager { address operator ) internal { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - $._operators.unregister(Time.timestamp(), SLASHING_WINDOW(), operator); + $._operators.unregister(now(), SLASHING_WINDOW(), operator); } } diff --git a/src/middleware/extensions/sigs/BaseSig.sol b/src/managers/base/SigManager.sol similarity index 74% rename from src/middleware/extensions/sigs/BaseSig.sol rename to src/managers/base/SigManager.sol index 9276721..79a270f 100644 --- a/src/middleware/extensions/sigs/BaseSig.sol +++ b/src/managers/base/SigManager.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -abstract contract BaseSig { +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +abstract contract SigManager is Initializable { /** * @notice Verifies that a signature was created by the owner of a key * @param operator The address of the operator that owns the key @@ -13,5 +15,5 @@ abstract contract BaseSig { address operator, bytes memory key_, bytes memory signature - ) internal virtual returns (bool); + ) public virtual returns (bool); } diff --git a/src/managers/base/StakePowerManager.sol b/src/managers/base/StakePowerManager.sol new file mode 100644 index 0000000..2a06cd9 --- /dev/null +++ b/src/managers/base/StakePowerManager.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @title StakePowerManager + * @notice Abstract contract for managing stake power conversion + */ + abstract contract StakePowerManager is Initializable { + /** + * @notice Converts stake amount to voting power + * @param vault The vault address + * @param stake The stake amount + * @return power The calculated voting power + */ + function stakeToPower(address vault, uint256 stake) internal view virtual returns (uint256 power); + } \ No newline at end of file diff --git a/src/managers/VaultManager.sol b/src/managers/base/VaultManager.sol similarity index 89% rename from src/managers/VaultManager.sol rename to src/managers/base/VaultManager.sol index 44ce214..30ba9cb 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/base/VaultManager.sol @@ -10,19 +10,18 @@ import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; import {IOperatorSpecificDelegator} from "@symbiotic/interfaces/delegator/IOperatorSpecificDelegator.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import {BaseManager} from "./BaseManager.sol"; -import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; +import {StakePowerManager} from "./StakePowerManager.sol"; +import {PauseableEnumerableSet} from "../../libraries/PauseableEnumerableSet.sol"; /** * @title VaultManager * @notice Abstract contract for managing vaults and their relationships with operators and subnetworks * @dev Extends BaseManager and provides functionality for registering, pausing, and managing vaults */ -abstract contract VaultManager is BaseManager { +abstract contract VaultManager is BaseManager, StakePowerManager { using EnumerableMap for EnumerableMap.AddressToUintMap; using EnumerableMap for EnumerableMap.AddressToAddressMap; using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; @@ -53,7 +52,7 @@ abstract contract VaultManager is BaseManager { bytes32 private constant VaultManagerStorageLocation = 0x485f0695561726d087d0cb5cf546efed37ef61dfced21455f1ba7eb5e5b3db00; - function _getVaultManagerStorage() private pure returns (VaultManagerStorage storage $) { + function _getVaultManagerStorage() internal pure returns (VaultManagerStorage storage $) { assembly { $.slot := VaultManagerStorageLocation } @@ -79,7 +78,7 @@ abstract contract VaultManager is BaseManager { * @notice Gets the total number of registered subnetworks * @return uint256 The count of registered subnetworks */ - function subnetworksLength() public view returns (uint256) { + function subnetworksLength() internal view returns (uint256) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._subnetworks.length(); } @@ -93,7 +92,7 @@ abstract contract VaultManager is BaseManager { */ function subnetworkWithTimesAt( uint256 pos - ) public view returns (uint160, uint48, uint48) { + ) internal view returns (uint160, uint48, uint48) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._subnetworks.at(pos); } @@ -102,7 +101,7 @@ abstract contract VaultManager is BaseManager { * @notice Gets all currently active subnetworks * @return uint160[] Array of active subnetwork addresses */ - function activeSubnetworks() public view returns (uint160[] memory) { + function activeSubnetworks() internal view returns (uint160[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._subnetworks.getActive(getCaptureTimestamp()); } @@ -114,7 +113,7 @@ abstract contract VaultManager is BaseManager { */ function activeSubnetworksAt( uint48 timestamp - ) public view returns (uint160[] memory) { + ) internal view returns (uint160[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._subnetworks.getActive(timestamp); } @@ -125,7 +124,7 @@ abstract contract VaultManager is BaseManager { * @param subnetwork The subnetwork identifier * @return bool True if the subnetwork was active at the timestamp */ - function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) public view returns (bool) { + function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) internal view returns (bool) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); } @@ -134,7 +133,7 @@ abstract contract VaultManager is BaseManager { * @notice Gets the total number of shared vaults * @return uint256 The count of shared vaults */ - function sharedVaultsLength() public view returns (uint256) { + function sharedVaultsLength() internal view returns (uint256) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.length(); } @@ -148,7 +147,7 @@ abstract contract VaultManager is BaseManager { */ function sharedVaultWithTimesAt( uint256 pos - ) public view returns (address, uint48, uint48) { + ) internal view returns (address, uint48, uint48) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.at(pos); } @@ -157,7 +156,7 @@ abstract contract VaultManager is BaseManager { * @notice Gets all currently active shared vaults * @return address[] Array of active shared vault addresses */ - function activeSharedVaults() public view returns (address[] memory) { + function activeSharedVaults() internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.getActive(getCaptureTimestamp()); } @@ -167,7 +166,7 @@ abstract contract VaultManager is BaseManager { * @param timestamp The timestamp to check * @return address[] Array of shared vault addresses that were active at the timestamp */ - function activeSharedVaultsAt(uint48 timestamp) public view returns (address[] memory) { + function activeSharedVaultsAt(uint48 timestamp) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.getActive(timestamp); } @@ -179,7 +178,7 @@ abstract contract VaultManager is BaseManager { */ function operatorVaultsLength( address operator - ) public view returns (uint256) { + ) internal view returns (uint256) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._operatorVaults[operator].length(); } @@ -192,7 +191,7 @@ abstract contract VaultManager is BaseManager { * @return uint48 The time when the vault was enabled * @return uint48 The time when the vault was disabled */ - function operatorVaultWithTimesAt(address operator, uint256 pos) public view returns (address, uint48, uint48) { + function operatorVaultWithTimesAt(address operator, uint256 pos) internal view returns (address, uint48, uint48) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._operatorVaults[operator].at(pos); } @@ -204,7 +203,7 @@ abstract contract VaultManager is BaseManager { */ function activeOperatorVaults( address operator - ) public view returns (address[] memory) { + ) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._operatorVaults[operator].getActive(getCaptureTimestamp()); } @@ -213,7 +212,7 @@ abstract contract VaultManager is BaseManager { * @notice Gets all currently active vaults across all operators * @return address[] Array of all active vault addresses */ - function activeVaults() public view virtual returns (address[] memory) { + function activeVaults() internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); uint48 timestamp = getCaptureTimestamp(); return activeVaultsAt(timestamp); @@ -226,7 +225,7 @@ abstract contract VaultManager is BaseManager { */ function activeVaultsAt( uint48 timestamp - ) public view virtual returns (address[] memory) { + ) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); uint256 len = activeSharedVaults_.length; @@ -258,7 +257,7 @@ abstract contract VaultManager is BaseManager { */ function activeVaults( address operator - ) public view virtual returns (address[] memory) { + ) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); uint48 timestamp = getCaptureTimestamp(); return activeVaultsAt(timestamp, operator); @@ -270,7 +269,7 @@ abstract contract VaultManager is BaseManager { * @param operator The operator address * @return address[] Array of vault addresses that were active at the timestamp */ - function activeVaultsAt(uint48 timestamp, address operator) public view virtual returns (address[] memory) { + function activeVaultsAt(uint48 timestamp, address operator) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); address[] memory activeOperatorVaults_ = $._operatorVaults[operator].getActive(timestamp); @@ -295,7 +294,7 @@ abstract contract VaultManager is BaseManager { * @param vault The vault address * @return bool True if the vault was active at the timestamp */ - function vaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { + function vaultWasActiveAt(uint48 timestamp, address operator, address vault) internal view returns (bool) { return sharedVaultWasActiveAt(timestamp, vault) || operatorVaultWasActiveAt(timestamp, operator, vault); } @@ -305,7 +304,7 @@ abstract contract VaultManager is BaseManager { * @param vault The vault address * @return bool True if the shared vault was active at the timestamp */ - function sharedVaultWasActiveAt(uint48 timestamp, address vault) public view returns (bool) { + function sharedVaultWasActiveAt(uint48 timestamp, address vault) internal view returns (bool) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.wasActiveAt(timestamp, vault); } @@ -317,7 +316,7 @@ abstract contract VaultManager is BaseManager { * @param vault The vault address * @return bool True if the operator vault was active at the timestamp */ - function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) public view returns (bool) { + function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) internal view returns (bool) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._operatorVaults[operator].wasActiveAt(timestamp, vault); } @@ -347,7 +346,7 @@ abstract contract VaultManager is BaseManager { * @param subnetwork The subnetwork identifier * @return uint256 The power amount */ - function getOperatorPower(address operator, address vault, uint96 subnetwork) public view returns (uint256) { + function getOperatorPower(address operator, address vault, uint96 subnetwork) internal view returns (uint256) { return getOperatorPowerAt(operator, vault, subnetwork, getCaptureTimestamp()); } @@ -364,7 +363,7 @@ abstract contract VaultManager is BaseManager { address vault, uint96 subnetwork, uint48 timestamp - ) public view returns (uint256) { + ) internal view returns (uint256) { uint256 stake = getOperatorStakeAt(operator, vault, subnetwork, timestamp); return stakeToPower(vault, stake); } @@ -376,7 +375,7 @@ abstract contract VaultManager is BaseManager { */ function getOperatorPower( address operator - ) public view virtual returns (uint256 power) { + ) internal view returns (uint256 power) { return getOperatorPowerAt(operator, getCaptureTimestamp()); } @@ -386,7 +385,7 @@ abstract contract VaultManager is BaseManager { * @param timestamp The timestamp to check * @return power The total power amount at the timestamp */ - function getOperatorPowerAt(address operator, uint48 timestamp) public view virtual returns (uint256 power) { + function getOperatorPowerAt(address operator, uint48 timestamp) internal view returns (uint256 power) { address[] memory vaults = activeVaultsAt(timestamp, operator); uint160[] memory subnetworks = activeSubnetworksAt(timestamp); @@ -423,7 +422,7 @@ abstract contract VaultManager is BaseManager { uint96 subnetwork ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.register(Time.timestamp(), uint160(subnetwork)); + $._subnetworks.register(now(), uint160(subnetwork)); } /** @@ -434,7 +433,7 @@ abstract contract VaultManager is BaseManager { uint96 subnetwork ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.pause(Time.timestamp(), uint160(subnetwork)); + $._subnetworks.pause(now(), uint160(subnetwork)); } /** @@ -445,7 +444,7 @@ abstract contract VaultManager is BaseManager { uint96 subnetwork ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.unpause(Time.timestamp(), SLASHING_WINDOW(), uint160(subnetwork)); + $._subnetworks.unpause(now(), SLASHING_WINDOW(), uint160(subnetwork)); } /** @@ -456,7 +455,7 @@ abstract contract VaultManager is BaseManager { uint96 subnetwork ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.unregister(Time.timestamp(), SLASHING_WINDOW(), uint160(subnetwork)); + $._subnetworks.unregister(now(), SLASHING_WINDOW(), uint160(subnetwork)); } /** @@ -468,7 +467,7 @@ abstract contract VaultManager is BaseManager { ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); _validateVault(vault); - $._sharedVaults.register(Time.timestamp(), vault); + $._sharedVaults.register(now(), vault); } /** @@ -481,7 +480,7 @@ abstract contract VaultManager is BaseManager { _validateVault(vault); _validateOperatorVault(operator, vault); - $._operatorVaults[operator].register(Time.timestamp(), vault); + $._operatorVaults[operator].register(now(), vault); $._vaultOperator.set(vault, operator); } @@ -493,7 +492,7 @@ abstract contract VaultManager is BaseManager { address vault ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.pause(Time.timestamp(), vault); + $._sharedVaults.pause(now(), vault); } /** @@ -504,7 +503,7 @@ abstract contract VaultManager is BaseManager { address vault ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.unpause(Time.timestamp(), SLASHING_WINDOW(), vault); + $._sharedVaults.unpause(now(), SLASHING_WINDOW(), vault); } /** @@ -514,7 +513,7 @@ abstract contract VaultManager is BaseManager { */ function _pauseOperatorVault(address operator, address vault) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].pause(Time.timestamp(), vault); + $._operatorVaults[operator].pause(now(), vault); } /** @@ -524,7 +523,7 @@ abstract contract VaultManager is BaseManager { */ function _unpauseOperatorVault(address operator, address vault) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].unpause(Time.timestamp(), SLASHING_WINDOW(), vault); + $._operatorVaults[operator].unpause(now(), SLASHING_WINDOW(), vault); } /** @@ -535,7 +534,7 @@ abstract contract VaultManager is BaseManager { address vault ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.unregister(Time.timestamp(), SLASHING_WINDOW(), vault); + $._sharedVaults.unregister(now(), SLASHING_WINDOW(), vault); } /** @@ -545,7 +544,7 @@ abstract contract VaultManager is BaseManager { */ function _unregisterOperatorVault(address operator, address vault) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].unregister(Time.timestamp(), SLASHING_WINDOW(), vault); + $._operatorVaults[operator].unregister(now(), SLASHING_WINDOW(), vault); $._vaultOperator.remove(vault); } @@ -576,7 +575,7 @@ abstract contract VaultManager is BaseManager { revert InactiveVaultSlash(); } - if (timestamp + SLASHING_WINDOW() < Time.timestamp()) { + if (timestamp + SLASHING_WINDOW() < now()) { revert TooOldTimestampSlash(); } diff --git a/src/middleware/extensions/access-managers/NoAccessManager.sol b/src/managers/extensions/access/NoAccessManager.sol similarity index 58% rename from src/middleware/extensions/access-managers/NoAccessManager.sol rename to src/managers/extensions/access/NoAccessManager.sol index c9ec786..4cff60f 100644 --- a/src/middleware/extensions/access-managers/NoAccessManager.sol +++ b/src/managers/extensions/access/NoAccessManager.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {AccessManager} from "../../base/AccessManager.sol"; /** * @title NoAccessManager * @notice A middleware extension that denies all access by default - * @dev Implements BaseMiddleware and always reverts on access checks + * @dev Implements AccessManager and always reverts on access checks */ -abstract contract NoAccessManager is BaseMiddleware { +abstract contract NoAccessManager is AccessManager { uint64 public constant NoAccessManager_VERSION = 1; /** * @notice Checks access and always allows access - * @dev This function is called internally to enforce access control and will always allow access + * @dev This function is called publicly to enforce access control and will always allow access */ function _checkAccess() internal pure virtual override { // Allow all access by default diff --git a/src/middleware/extensions/access-managers/OwnableAccessManager.sol b/src/managers/extensions/access/OwnableAccessManager.sol similarity index 90% rename from src/middleware/extensions/access-managers/OwnableAccessManager.sol rename to src/managers/extensions/access/OwnableAccessManager.sol index 009fd8c..6111d01 100644 --- a/src/middleware/extensions/access-managers/OwnableAccessManager.sol +++ b/src/managers/extensions/access/OwnableAccessManager.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {AccessManager} from "../../base/AccessManager.sol"; /** * @title OwnableAccessManager * @notice A middleware extension that restricts access to a single owner address - * @dev Implements BaseMiddleware with owner-based access control + * @dev Implements AccessManager with owner-based access control */ -abstract contract OwnableAccessManager is BaseMiddleware { +abstract contract OwnableAccessManager is AccessManager { uint64 public constant OwnableAccessManager_VERSION = 1; /** @@ -34,6 +34,16 @@ abstract contract OwnableAccessManager is BaseMiddleware { } } + /** + * @notice Initializes the contract with an owner address + * @param owner_ The address to set as the owner + */ + function __OwnableAccessManager_init( + address owner_ + ) internal onlyInitializing { + _setOwner(owner_); + } + function _setOwner( address owner_ ) private { @@ -51,16 +61,6 @@ abstract contract OwnableAccessManager is BaseMiddleware { return _owner(); } - /** - * @notice Initializes the contract with an owner address - * @param owner_ The address to set as the owner - */ - function __OwnableAccessManaged_init( - address owner_ - ) internal onlyInitializing { - _setOwner(owner_); - } - /** * @notice Checks if the caller has access (is the owner) * @dev Reverts if the caller is not the owner diff --git a/src/middleware/extensions/access-managers/OzAccessControl.sol b/src/managers/extensions/access/OzAccessControl.sol similarity index 89% rename from src/middleware/extensions/access-managers/OzAccessControl.sol rename to src/managers/extensions/access/OzAccessControl.sol index c6faad3..666356f 100644 --- a/src/middleware/extensions/access-managers/OzAccessControl.sol +++ b/src/managers/extensions/access/OzAccessControl.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {AccessManager} from "../../base/AccessManager.sol"; /** * @title OzAccessControl * @notice A middleware extension that implements role-based access control - * @dev Implements BaseMiddleware with role-based access control functionality + * @dev Implements AccessManager with role-based access control functionality */ -abstract contract OzAccessControl is BaseMiddleware { +abstract contract OzAccessControl is AccessManager { uint64 public constant OzAccessControl_VERSION = 1; struct RoleData { @@ -28,7 +28,7 @@ abstract contract OzAccessControl is BaseMiddleware { bytes32 private constant OzAccessControlStorageLocation = 0xbe09a78a256419d2b885312b60a13e8082d8ab3c36c463fff4fbb086f1e96f00; - function _getOzAccessControlStorage() private pure returns (OzAccessControlStorage storage $) { + function _getOzAccessControlStorage() internal pure returns (OzAccessControlStorage storage $) { assembly { $.slot := OzAccessControlStorageLocation } @@ -130,7 +130,7 @@ abstract contract OzAccessControl is BaseMiddleware { * @param selector The function selector * @param role The required role */ - function _setSelectorRole(bytes4 selector, bytes32 role) internal virtual { + function _setSelectorRole(bytes4 selector, bytes32 role) public virtual { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); $._selectorRoles[selector] = role; emit SelectorRoleSet(selector, role); @@ -141,7 +141,7 @@ abstract contract OzAccessControl is BaseMiddleware { * @param role The role to set admin for * @param adminRole The new admin role */ - function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { + function _setRoleAdmin(bytes32 role, bytes32 adminRole) public virtual { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); bytes32 previousAdminRole = getRoleAdmin(role); $._roles[role].adminRole = adminRole; @@ -149,12 +149,12 @@ abstract contract OzAccessControl is BaseMiddleware { } /** - * @notice Internal function to grant a role + * @notice public function to grant a role * @param role The role to grant * @param account The account to grant the role to * @return bool True if role was granted */ - function _grantRole(bytes32 role, address account) internal virtual returns (bool) { + function _grantRole(bytes32 role, address account) public virtual returns (bool) { if (!hasRole(role, account)) { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); $._roles[role].hasRole[account] = true; @@ -165,12 +165,12 @@ abstract contract OzAccessControl is BaseMiddleware { } /** - * @notice Internal function to revoke a role + * @notice public function to revoke a role * @param role The role to revoke * @param account The account to revoke the role from * @return bool True if role was revoked */ - function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { + function _revokeRole(bytes32 role, address account) public virtual returns (bool) { if (hasRole(role, account)) { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); $._roles[role].hasRole[account] = false; diff --git a/src/middleware/extensions/access-managers/OzAccessManaged.sol b/src/managers/extensions/access/OzAccessManaged.sol similarity index 81% rename from src/middleware/extensions/access-managers/OzAccessManaged.sol rename to src/managers/extensions/access/OzAccessManaged.sol index f2a2cff..6aed159 100644 --- a/src/middleware/extensions/access-managers/OzAccessManaged.sol +++ b/src/managers/extensions/access/OzAccessManaged.sol @@ -3,21 +3,20 @@ pragma solidity ^0.8.25; import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {AccessManager} from "../../base/AccessManager.sol"; /** * @title OzAccessManaged * @notice A middleware extension that integrates OpenZeppelin's AccessManaged for access control - * @dev Implements BaseMiddleware with OpenZeppelin's AccessManagedUpgradeable functionality + * @dev Implements AccessManager with OpenZeppelin's AccessManagedUpgradeable functionality */ -abstract contract OzAccessManaged is BaseMiddleware, AccessManagedUpgradeable { +abstract contract OzAccessManaged is AccessManager, AccessManagedUpgradeable { uint64 public constant OzAccessManaged_VERSION = 1; /** * @notice Initializes the contract with an authority address * @param authority The address to set as the access manager authority * @dev Can only be called during initialization */ - function __OzAccessManaged_init( address authority ) internal onlyInitializing { diff --git a/src/middleware/extensions/capture-timestamps/EpochCapture.sol b/src/managers/extensions/capture-timestamps/EpochCapture.sol similarity index 67% rename from src/middleware/extensions/capture-timestamps/EpochCapture.sol rename to src/managers/extensions/capture-timestamps/EpochCapture.sol index a84103b..cde9433 100644 --- a/src/middleware/extensions/capture-timestamps/EpochCapture.sol +++ b/src/managers/extensions/capture-timestamps/EpochCapture.sol @@ -1,10 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {CaptureTimestampManager} from "../../base/CaptureTimestampManager.sol"; -abstract contract EpochCapture is BaseMiddleware { +/** + * @title EpochCapture + * @notice A middleware extension that captures timestamps based on epochs + * @dev Implements CaptureTimestampManager with epoch-based timestamp capture + * @dev Epochs are fixed time periods starting from a base timestamp + */ + +abstract contract EpochCapture is CaptureTimestampManager { uint64 public constant EpochCapture_VERSION = 1; struct EpochCaptureStorage { @@ -16,7 +22,7 @@ abstract contract EpochCapture is BaseMiddleware { bytes32 private constant EpochCaptureStorageLocation = 0x4e241e104e7ef4df0fc8eb6aad7b0f201c6126c722652f1bd1305b6b75c86d00; - function _getEpochCaptureStorage() private pure returns (EpochCaptureStorage storage $) { + function _getEpochCaptureStorage() internal pure returns (EpochCaptureStorage storage $) { bytes32 location = EpochCaptureStorageLocation; assembly { $.slot := location @@ -32,7 +38,7 @@ abstract contract EpochCapture is BaseMiddleware { ) internal onlyInitializing { EpochCaptureStorage storage $ = _getEpochCaptureStorage(); $.epochDuration = epochDuration; - $.startTimestamp = Time.timestamp(); + $.startTimestamp = now(); } /* @@ -42,7 +48,7 @@ abstract contract EpochCapture is BaseMiddleware { */ function getEpochStart( uint48 epoch - ) public view returns (uint48) { + ) internal view returns (uint48) { EpochCaptureStorage storage $ = _getEpochCaptureStorage(); return $.startTimestamp + epoch * $.epochDuration; } @@ -51,16 +57,16 @@ abstract contract EpochCapture is BaseMiddleware { * @notice Returns the current epoch. * @return The current epoch. */ - function getCurrentEpoch() public view returns (uint48) { + function getCurrentEpoch() internal view returns (uint48) { EpochCaptureStorage storage $ = _getEpochCaptureStorage(); - return (Time.timestamp() - $.startTimestamp) / $.epochDuration; + return (now() - $.startTimestamp) / $.epochDuration; } /* * @notice Returns the capture timestamp for the current epoch. * @return The capture timestamp. */ - function getCaptureTimestamp() public view override returns (uint48 timestamp) { + function getCaptureTimestamp() internal view override returns (uint48 timestamp) { return getEpochStart(getCurrentEpoch()); } } diff --git a/src/managers/extensions/capture-timestamps/TimestampCapture.sol b/src/managers/extensions/capture-timestamps/TimestampCapture.sol new file mode 100644 index 0000000..fe130c3 --- /dev/null +++ b/src/managers/extensions/capture-timestamps/TimestampCapture.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {CaptureTimestampManager} from "../../base/CaptureTimestampManager.sol"; + +/** + * @title TimestampCapture + * @notice A middleware extension that captures timestamps by subtracting 1 second from current time + * @dev Implements CaptureTimestampManager with a simple timestamp capture mechanism + */ + +abstract contract TimestampCapture is CaptureTimestampManager { + uint64 public constant TimestampCapture_VERSION = 1; + + /* + * @notice Returns the current timestamp minus 1 second. + * @return timestamp The current timestamp minus 1 second. + */ + function getCaptureTimestamp() internal view override returns (uint48 timestamp) { + return now() - 1; + } +} diff --git a/src/middleware/extensions/key-storages/KeyStorage256.sol b/src/managers/extensions/keys/KeyManager256.sol similarity index 74% rename from src/middleware/extensions/key-storages/KeyStorage256.sol rename to src/managers/extensions/keys/KeyManager256.sol index dcf0e28..89c3782 100644 --- a/src/middleware/extensions/key-storages/KeyStorage256.sol +++ b/src/managers/extensions/keys/KeyManager256.sol @@ -1,18 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {KeyManager} from "../../base/KeyManager.sol"; import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; - /** - * @title KeyStorage256 + * @title KeyManager256 * @notice Manages storage and validation of operator keys using bytes32 values - * @dev Extends BaseMiddleware to provide key management functionality + * @dev Extends KeyManager to provide key management functionality */ -abstract contract KeyStorage256 is BaseMiddleware { - uint64 public constant KeyStorage256_VERSION = 1; +abstract contract KeyManager256 is KeyManager { + uint64 public constant KeyManager256_VERSION = 1; using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; @@ -23,19 +21,19 @@ abstract contract KeyStorage256 is BaseMiddleware { bytes32 private constant ZERO_BYTES32 = bytes32(0); uint256 private constant MAX_DISABLED_KEYS = 1; - struct KeyStorage256Storage { + struct KeyManager256Storage { /// @notice Mapping from operator addresses to their keys mapping(address => PauseableEnumerableSet.Bytes32Set) keys; /// @notice Mapping from keys to operator addresses mapping(bytes32 => address) keyToOperator; } - // keccak256(abi.encode(uint256(keccak256("symbioticfi.storage.KeyStorage256")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant KeyStorage256StorageLocation = + // keccak256(abi.encode(uint256(keccak256("symbioticfi.storage.KeyManager256")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant KeyManager256StorageLocation = 0x00c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c800; - function _getStorage() private pure returns (KeyStorage256Storage storage s) { - bytes32 location = KeyStorage256StorageLocation; + function _getKeyManager256Storage() internal pure returns (KeyManager256Storage storage s) { + bytes32 location = KeyManager256StorageLocation; assembly { s.slot := location } @@ -49,7 +47,7 @@ abstract contract KeyStorage256 is BaseMiddleware { function operatorByKey( bytes memory key ) public view override returns (address) { - KeyStorage256Storage storage s = _getStorage(); + KeyManager256Storage storage s = _getKeyManager256Storage(); return s.keyToOperator[abi.decode(key, (bytes32))]; } @@ -61,7 +59,7 @@ abstract contract KeyStorage256 is BaseMiddleware { function operatorKey( address operator ) public view override returns (bytes memory) { - KeyStorage256Storage storage s = _getStorage(); + KeyManager256Storage storage s = _getKeyManager256Storage(); bytes32[] memory active = s.keys[operator].getActive(getCaptureTimestamp()); if (active.length == 0) { return abi.encode(ZERO_BYTES32); @@ -76,7 +74,7 @@ abstract contract KeyStorage256 is BaseMiddleware { * @return True if the key was active at the timestamp, false otherwise */ function keyWasActiveAt(uint48 timestamp, bytes memory key_) public view override returns (bool) { - KeyStorage256Storage storage s = _getStorage(); + KeyManager256Storage storage s = _getKeyManager256Storage(); bytes32 key = abi.decode(key_, (bytes32)); return s.keys[s.keyToOperator[key]].wasActiveAt(timestamp, key); } @@ -90,7 +88,7 @@ abstract contract KeyStorage256 is BaseMiddleware { * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key_) internal override { - KeyStorage256Storage storage s = _getStorage(); + KeyManager256Storage storage s = _getKeyManager256Storage(); bytes32 key = abi.decode(key_, (bytes32)); if (s.keyToOperator[key] != address(0)) { @@ -106,17 +104,17 @@ abstract contract KeyStorage256 is BaseMiddleware { if (s.keys[operator].length() > 0) { // try to remove disabled keys bytes32 prevKey = s.keys[operator].array[0].value; - if (s.keys[operator].checkUnregister(Time.timestamp(), SLASHING_WINDOW(), prevKey)) { - s.keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW(), prevKey); + if (s.keys[operator].checkUnregister(now(), SLASHING_WINDOW(), prevKey)) { + s.keys[operator].unregister(now(), SLASHING_WINDOW(), prevKey); delete s.keyToOperator[prevKey]; - } else if (s.keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { - s.keys[operator].pause(Time.timestamp(), prevKey); + } else if (s.keys[operator].wasActiveAt(now(), prevKey)) { + s.keys[operator].pause(now(), prevKey); } } if (key != ZERO_BYTES32) { // register the new key - s.keys[operator].register(Time.timestamp(), key); + s.keys[operator].register(now(), key); s.keyToOperator[key] = operator; } } diff --git a/src/middleware/extensions/key-storages/KeyStorageBytes.sol b/src/managers/extensions/keys/KeyManagerBytes.sol similarity index 72% rename from src/middleware/extensions/key-storages/KeyStorageBytes.sol rename to src/managers/extensions/keys/KeyManagerBytes.sol index 269aaa1..7c0acec 100644 --- a/src/middleware/extensions/key-storages/KeyStorageBytes.sol +++ b/src/managers/extensions/keys/KeyManagerBytes.sol @@ -1,18 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {KeyManager} from "../../base/KeyManager.sol"; import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; - /** - * @title KeyStorageBytes + * @title KeyManagerBytes * @notice Manages storage and validation of operator keys - * @dev Extends BaseManager to provide key management functionality + * @dev Extends KeyManager to provide key management functionality */ -abstract contract KeyStorageBytes is BaseMiddleware { - uint64 public constant KeyStorageBytes_VERSION = 1; +abstract contract KeyManagerBytes is KeyManager { + uint64 public constant KeyManagerBytes_VERSION = 1; using PauseableEnumerableSet for PauseableEnumerableSet.BytesSet; @@ -23,17 +21,17 @@ abstract contract KeyStorageBytes is BaseMiddleware { bytes private constant ZERO_BYTES = ""; bytes32 private constant ZERO_BYTES_HASH = keccak256(""); - struct KeyStorageBytesStorage { + struct KeyManagerBytesStorage { mapping(address => PauseableEnumerableSet.BytesSet) _keys; mapping(bytes => address) _keyToOperator; } - // keccak256(abi.encode(uint256(keccak256("symbioticfi.storage.KeyStorageBytes")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant KeyStorageBytesStorageLocation = + // keccak256(abi.encode(uint256(keccak256("symbioticfi.storage.KeyManagerBytes")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant KeyManagerBytesStorageLocation = 0x00c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c800; - function _getStorage() private pure returns (KeyStorageBytesStorage storage s) { - bytes32 location = KeyStorageBytesStorageLocation; + function _getKeyManagerBytesStorage() internal pure returns (KeyManagerBytesStorage storage s) { + bytes32 location = KeyManagerBytesStorageLocation; assembly { s.slot := location } @@ -47,7 +45,7 @@ abstract contract KeyStorageBytes is BaseMiddleware { function operatorByKey( bytes memory key ) public view override returns (address) { - KeyStorageBytesStorage storage $ = _getStorage(); + KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); return $._keyToOperator[key]; } @@ -59,7 +57,7 @@ abstract contract KeyStorageBytes is BaseMiddleware { function operatorKey( address operator ) public view override returns (bytes memory) { - KeyStorageBytesStorage storage $ = _getStorage(); + KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); bytes[] memory active = $._keys[operator].getActive(getCaptureTimestamp()); if (active.length == 0) { return ZERO_BYTES; @@ -74,7 +72,7 @@ abstract contract KeyStorageBytes is BaseMiddleware { * @return True if the key was active at the timestamp, false otherwise */ function keyWasActiveAt(uint48 timestamp, bytes memory key) public view override returns (bool) { - KeyStorageBytesStorage storage $ = _getStorage(); + KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); return $._keys[$._keyToOperator[key]].wasActiveAt(timestamp, key); } @@ -87,7 +85,7 @@ abstract contract KeyStorageBytes is BaseMiddleware { * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key) internal override { - KeyStorageBytesStorage storage $ = _getStorage(); + KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); bytes32 keyHash = keccak256(key); if ($._keyToOperator[key] != address(0)) { @@ -103,17 +101,17 @@ abstract contract KeyStorageBytes is BaseMiddleware { if ($._keys[operator].length() > 0) { // try to remove disabled keys bytes memory prevKey = $._keys[operator].array[0].value; - if ($._keys[operator].checkUnregister(Time.timestamp(), SLASHING_WINDOW(), prevKey)) { - $._keys[operator].unregister(Time.timestamp(), SLASHING_WINDOW(), prevKey); + if ($._keys[operator].checkUnregister(now(), SLASHING_WINDOW(), prevKey)) { + $._keys[operator].unregister(now(), SLASHING_WINDOW(), prevKey); delete $._keyToOperator[prevKey]; - } else if ($._keys[operator].wasActiveAt(getCaptureTimestamp(), prevKey)) { - $._keys[operator].pause(Time.timestamp(), prevKey); + } else if ($._keys[operator].wasActiveAt(now(), prevKey)) { + $._keys[operator].pause(now(), prevKey); } } if (keyHash != ZERO_BYTES_HASH) { // register the new key - $._keys[operator].register(Time.timestamp(), key); + $._keys[operator].register(now(), key); $._keyToOperator[key] = operator; } } diff --git a/src/middleware/extensions/key-storages/NoKeyStorage.sol b/src/managers/extensions/keys/NoKeyManager.sol similarity index 75% rename from src/middleware/extensions/key-storages/NoKeyStorage.sol rename to src/managers/extensions/keys/NoKeyManager.sol index 5da6d34..99e41a8 100644 --- a/src/middleware/extensions/key-storages/NoKeyStorage.sol +++ b/src/managers/extensions/keys/NoKeyManager.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {KeyManager} from "../../base/KeyManager.sol"; /** - * @title NoKeyStorage + * @title NoKeyManager * @notice A middleware extension that provides no key storage functionality - * @dev Implements BaseMiddleware and always reverts on key operations + * @dev Implements KeyManager and always reverts on key operations */ -abstract contract NoKeyStorage is BaseMiddleware { - uint64 public constant NoKeyStorage_VERSION = 1; +abstract contract NoKeyManager is KeyManager { + uint64 public constant NoKeyManager_VERSION = 1; - error KeyStorageDisabled(); + error KeyManagerDisabled(); /** * @notice Gets the operator address associated with a key @@ -21,7 +21,7 @@ abstract contract NoKeyStorage is BaseMiddleware { function operatorByKey( bytes memory key ) public pure override returns (address) { - revert KeyStorageDisabled(); + revert KeyManagerDisabled(); } /** @@ -32,7 +32,7 @@ abstract contract NoKeyStorage is BaseMiddleware { function operatorKey( address operator ) public pure override returns (bytes memory) { - revert KeyStorageDisabled(); + revert KeyManagerDisabled(); } /** @@ -42,7 +42,7 @@ abstract contract NoKeyStorage is BaseMiddleware { * @return Whether key was active (always reverts) */ function keyWasActiveAt(uint48 timestamp, bytes memory key) public pure override returns (bool) { - revert KeyStorageDisabled(); + revert KeyManagerDisabled(); } /** @@ -51,6 +51,6 @@ abstract contract NoKeyStorage is BaseMiddleware { * @param key The new key (unused) */ function _updateKey(address operator, bytes memory key) internal virtual override { - revert KeyStorageDisabled(); + revert KeyManagerDisabled(); } } diff --git a/src/middleware/extensions/sigs/ECDSASig.sol b/src/managers/extensions/sigs/ECDSASig.sol similarity index 83% rename from src/middleware/extensions/sigs/ECDSASig.sol rename to src/managers/extensions/sigs/ECDSASig.sol index 63092a3..29812c5 100644 --- a/src/middleware/extensions/sigs/ECDSASig.sol +++ b/src/managers/extensions/sigs/ECDSASig.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseSig} from "./BaseSig.sol"; +import {SigManager} from "../../base/SigManager.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; /** * @title ECDSASig * @notice Contract for verifying ECDSA signatures against operator keys - * @dev Implements BaseSig interface using OpenZeppelin's ECDSA library + * @dev Implements SigManager interface using OpenZeppelin's ECDSA library */ -abstract contract ECDSASig is BaseSig { +abstract contract ECDSASig is SigManager { uint64 public constant ECDSASig_VERSION = 1; using ECDSA for bytes32; @@ -26,7 +26,7 @@ abstract contract ECDSASig is BaseSig { address operator, bytes memory key_, bytes memory signature - ) internal pure override returns (bool) { + ) public pure override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); bytes32 hash = keccak256(abi.encodePacked(operator, key)); address signer = recover(hash, signature); @@ -41,7 +41,7 @@ abstract contract ECDSASig is BaseSig { * @return The address that created the signature * @dev Wrapper around OpenZeppelin's ECDSA.recover */ - function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { + function recover(bytes32 hash, bytes memory signature) public pure returns (address) { return hash.recover(signature); } } diff --git a/src/middleware/extensions/sigs/EdDSASig.sol b/src/managers/extensions/sigs/EdDSASig.sol similarity index 86% rename from src/middleware/extensions/sigs/EdDSASig.sol rename to src/managers/extensions/sigs/EdDSASig.sol index 09b558d..644d63c 100644 --- a/src/middleware/extensions/sigs/EdDSASig.sol +++ b/src/managers/extensions/sigs/EdDSASig.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.25; import {EdDSA} from "../../../libraries/EdDSA.sol"; -import {BaseSig} from "./BaseSig.sol"; +import {SigManager} from "../../base/SigManager.sol"; /** * @title EdDSASig * @notice Contract for verifying EdDSA signatures over Ed25519 against operator keys - * @dev Implements BaseSig interface using EdDSA signature verification + * @dev Implements SigManager interface using EdDSA signature verification */ -abstract contract EdDSASig is BaseSig { +abstract contract EdDSASig is SigManager { uint64 public constant EdDSASig_VERSION = 1; /** @@ -25,7 +25,7 @@ abstract contract EdDSASig is BaseSig { address operator, bytes memory key_, bytes memory signature - ) internal override returns (bool) { + ) public override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); bytes memory message = abi.encode(keccak256(abi.encodePacked(operator, key))); return verify(message, signature, key); @@ -39,7 +39,7 @@ abstract contract EdDSASig is BaseSig { * @return True if the signature is valid, false otherwise * @dev Wrapper around Ed25519.verify which handles decompression and curve operations */ - function verify(bytes memory message, bytes memory signature, bytes32 key) internal returns (bool) { + function verify(bytes memory message, bytes memory signature, bytes32 key) public returns (bool) { return EdDSA.verify(message, signature, key); } } diff --git a/src/middleware/extensions/stake-powers/EqualStakePower.sol b/src/managers/extensions/stake-powers/EqualStakePower.sol similarity index 69% rename from src/middleware/extensions/stake-powers/EqualStakePower.sol rename to src/managers/extensions/stake-powers/EqualStakePower.sol index bc50f5d..81f483e 100644 --- a/src/middleware/extensions/stake-powers/EqualStakePower.sol +++ b/src/managers/extensions/stake-powers/EqualStakePower.sol @@ -1,21 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseManager} from "../../../managers/BaseManager.sol"; +import {StakePowerManager} from "../../base/StakePowerManager.sol"; /** * @title EqualStakePower * @notice Implementation of a 1:1 stake to power conversion * @dev Simply returns the stake amount as the power amount without any modifications */ -abstract contract EqualStakePower is BaseManager { +abstract contract EqualStakePower is StakePowerManager { /** * @notice Converts stake amount to voting power using a 1:1 ratio * @param vault The vault address (unused in this implementation) * @param stake The stake amount * @return power The calculated voting power (equal to stake) */ - function stakeToPower(address vault, uint256 stake) public pure override returns (uint256 power) { + function stakeToPower(address vault, uint256 stake) internal pure override returns (uint256 power) { return stake; } } diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index ff7f430..61fb8ba 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {VaultManager} from "../managers/VaultManager.sol"; -import {OperatorManager} from "../managers/OperatorManager.sol"; -import {AccessManager} from "../managers/AccessManager.sol"; -import {KeyManager} from "../managers/KeyManager.sol"; +import {VaultManager} from "../managers/base/VaultManager.sol"; +import {OperatorManager} from "../managers/base/OperatorManager.sol"; +import {AccessManager} from "../managers/base/AccessManager.sol"; +import {KeyManager} from "../managers/base/KeyManager.sol"; /** * @title BaseMiddleware * @notice Abstract base contract that combines core manager functionality for building middleware - * @dev Inherits from VaultManager, OperatorManager, AccessManager and KeyManager to provide: + * @dev Inherits from VaultManager, OperatorManager, AccessManager, and KeyManager to provide: * - Vault management and registration * - Operator management and registration - * - Access control and permissions - * - Key storage and management + * - Access management + * - Key management * * This contract serves as a foundation for building custom middleware by providing essential * management capabilities that can be extended with additional functionality. diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 9afffcc..96f38f5 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -61,7 +61,7 @@ abstract contract SharedVaults is BaseMiddleware { */ function _beforeRegisterSharedVault( address sharedVault - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before pausing a shared vault @@ -69,7 +69,7 @@ abstract contract SharedVaults is BaseMiddleware { */ function _beforePauseSharedVault( address sharedVault - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before unpausing a shared vault @@ -77,7 +77,7 @@ abstract contract SharedVaults is BaseMiddleware { */ function _beforeUnpauseSharedVault( address sharedVault - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before unregistering a shared vault @@ -85,5 +85,5 @@ abstract contract SharedVaults is BaseMiddleware { */ function _beforeUnregisterSharedVault( address sharedVault - ) internal virtual {} + ) public virtual {} } diff --git a/src/middleware/extensions/Subnetworks.sol b/src/middleware/extensions/Subnetworks.sol index cec458e..e610a5d 100644 --- a/src/middleware/extensions/Subnetworks.sol +++ b/src/middleware/extensions/Subnetworks.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - import {BaseMiddleware} from "../BaseMiddleware.sol"; /** @@ -63,7 +61,7 @@ abstract contract Subnetworks is BaseMiddleware { */ function _beforeRegisterSubnetwork( uint96 subnetwork - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before pausing a subnetwork @@ -71,7 +69,7 @@ abstract contract Subnetworks is BaseMiddleware { */ function _beforePauseSubnetwork( uint96 subnetwork - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before unpausing a subnetwork @@ -79,7 +77,7 @@ abstract contract Subnetworks is BaseMiddleware { */ function _beforeUnpauseSubnetwork( uint96 subnetwork - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before unregistering a subnetwork @@ -87,5 +85,5 @@ abstract contract Subnetworks is BaseMiddleware { */ function _beforeUnregisterSubnetwork( uint96 subnetwork - ) internal virtual {} + ) public virtual {} } diff --git a/src/middleware/extensions/capture-timestamps/TimestampCapture.sol b/src/middleware/extensions/capture-timestamps/TimestampCapture.sol deleted file mode 100644 index f772703..0000000 --- a/src/middleware/extensions/capture-timestamps/TimestampCapture.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; - -abstract contract TimestampCapture is BaseMiddleware { - uint64 public constant TimestampCapture_VERSION = 1; - - /* - * @notice Returns the current timestamp minus 1 second. - * @return timestamp The current timestamp minus 1 second. - */ - function getCaptureTimestamp() public view override returns (uint48 timestamp) { - return Time.timestamp() - 1; - } -} diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index c3000ea..41bd2f6 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -20,7 +20,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { bytes32 private constant ForcePauseSelfRegisterOperatorsStorageLocation = 0xf3871d05fd4da42686c3c56dfd4be98b1d278da4bf1fd61b1d6e7a6e37722600; - function _getForcePauseStorage() private pure returns (ForcePauseSelfRegisterOperatorsStorage storage $) { + function _getForcePauseStorage() internal pure returns (ForcePauseSelfRegisterOperatorsStorage storage $) { bytes32 location = ForcePauseSelfRegisterOperatorsStorageLocation; assembly { $.slot := location @@ -90,7 +90,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { */ function _beforeUnpauseOperator( address operator - ) internal virtual override { + ) public virtual override { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); if ($.forcePaused[operator]) revert OperatorForcePaused(); super._beforeUnpauseOperator(operator); @@ -102,7 +102,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { */ function _beforeUnregisterOperator( address operator - ) internal virtual override { + ) public virtual override { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); if ($.forcePaused[operator]) revert OperatorForcePaused(); super._beforeUnregisterOperator(operator); @@ -113,7 +113,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @param operator The operator address * @param vault The vault address */ - function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual override { + function _beforeUnpauseOperatorVault(address operator, address vault) public virtual override { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); if ($.forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); super._beforeUnpauseOperatorVault(operator, vault); @@ -124,7 +124,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @param operator The operator address * @param vault The vault address */ - function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual override { + function _beforeUnregisterOperatorVault(address operator, address vault) public virtual override { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); if ($.forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); super._beforeUnregisterOperatorVault(operator, vault); diff --git a/src/middleware/extensions/operators/Operators.sol b/src/middleware/extensions/operators/Operators.sol index 3a500a4..67c7a99 100644 --- a/src/middleware/extensions/operators/Operators.sol +++ b/src/middleware/extensions/operators/Operators.sol @@ -116,7 +116,7 @@ abstract contract Operators is BaseMiddleware { * @param operator The operator address * @param key The new key */ - function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} + function _beforeUpdateOperatorKey(address operator, bytes memory key) public virtual {} /** * @notice Hook called before registering an operator @@ -124,7 +124,7 @@ abstract contract Operators is BaseMiddleware { * @param key The operator's key * @param vault Optional vault address */ - function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} + function _beforeRegisterOperator(address operator, bytes memory key, address vault) public virtual {} /** * @notice Hook called before unregistering an operator @@ -132,7 +132,7 @@ abstract contract Operators is BaseMiddleware { */ function _beforeUnregisterOperator( address operator - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before pausing an operator @@ -140,7 +140,7 @@ abstract contract Operators is BaseMiddleware { */ function _beforePauseOperator( address operator - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before unpausing an operator @@ -148,33 +148,33 @@ abstract contract Operators is BaseMiddleware { */ function _beforeUnpauseOperator( address operator - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before registering an operator-vault pair * @param operator The operator address * @param vault The vault address */ - function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} + function _beforeRegisterOperatorVault(address operator, address vault) public virtual {} /** * @notice Hook called before unregistering an operator-vault pair * @param operator The operator address * @param vault The vault address */ - function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual {} + function _beforeUnregisterOperatorVault(address operator, address vault) public virtual {} /** * @notice Hook called before pausing an operator-vault pair * @param operator The operator address * @param vault The vault address */ - function _beforePauseOperatorVault(address operator, address vault) internal virtual {} + function _beforePauseOperatorVault(address operator, address vault) public virtual {} /** * @notice Hook called before unpausing an operator-vault pair * @param operator The operator address * @param vault The vault address */ - function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual {} + function _beforeUnpauseOperatorVault(address operator, address vault) public virtual {} } diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index 58ee3c8..41a160c 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -2,16 +2,16 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../../BaseMiddleware.sol"; -import {BaseSig} from "../sigs/BaseSig.sol"; +import {SigManager} from "../../../managers/base/SigManager.sol"; import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; /** * @title SelfRegisterOperators * @notice Contract for self-registration and management of operators with signature verification - * @dev Extends BaseMiddleware, BaseSig, and EIP712Upgradeable to provide signature-based operator management + * @dev Extends BaseMiddleware, SigManager, and EIP712Upgradeable to provide signature-based operator management */ -abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgradeable { +abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upgradeable { uint64 public constant SelfRegisterOperators_VERSION = 1; error InvalidSignature(); @@ -61,7 +61,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function __SelfRegisterOperators_init( string memory name - ) internal onlyInitializing { + ) public onlyInitializing { __EIP712_init(name, "1.0"); } @@ -336,7 +336,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad * @param key The public key to verify * @param signature The signature to verify */ - function _verifyKey(address operator, bytes memory key, bytes memory signature) internal { + function _verifyKey(address operator, bytes memory key, bytes memory signature) public { if (key.length != 0 && !_verifyKeySignature(operator, key, signature)) { revert InvalidSignature(); } @@ -348,7 +348,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad * @param structHash The hash of the EIP712 struct * @param signature The signature to verify */ - function _verifyEIP712(address operator, bytes32 structHash, bytes memory signature) internal view { + function _verifyEIP712(address operator, bytes32 structHash, bytes memory signature) public view { if (!SignatureChecker.isValidSignatureNow(operator, _hashTypedDataV4(structHash), signature)) { revert InvalidSignature(); } @@ -359,7 +359,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad * @param operator The operator address * @param key The new key */ - function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} + function _beforeUpdateOperatorKey(address operator, bytes memory key) public virtual {} /** * @notice Hook called before registering an operator @@ -367,7 +367,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad * @param key The operator's key * @param vault Optional vault address */ - function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} + function _beforeRegisterOperator(address operator, bytes memory key, address vault) public virtual {} /** * @notice Hook called before unregistering an operator @@ -375,7 +375,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function _beforeUnregisterOperator( address operator - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before pausing an operator @@ -383,7 +383,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function _beforePauseOperator( address operator - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before unpausing an operator @@ -391,33 +391,33 @@ abstract contract SelfRegisterOperators is BaseMiddleware, BaseSig, EIP712Upgrad */ function _beforeUnpauseOperator( address operator - ) internal virtual {} + ) public virtual {} /** * @notice Hook called before registering an operator vault * @param operator The operator address * @param vault The vault address */ - function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} + function _beforeRegisterOperatorVault(address operator, address vault) public virtual {} /** * @notice Hook called before unregistering an operator vault * @param operator The operator address * @param vault The vault address */ - function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual {} + function _beforeUnregisterOperatorVault(address operator, address vault) public virtual {} /** * @notice Hook called before pausing an operator vault * @param operator The operator address * @param vault The vault address */ - function _beforePauseOperatorVault(address operator, address vault) internal virtual {} + function _beforePauseOperatorVault(address operator, address vault) public virtual {} /** * @notice Hook called before unpausing an operator vault * @param operator The operator address * @param vault The vault address */ - function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual {} + function _beforeUnpauseOperatorVault(address operator, address vault) public virtual {} } From 639a351e5de48c897912c7cc962733e68fededc4 Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 5 Dec 2024 18:57:08 +0400 Subject: [PATCH 090/115] feat: external read helper --- .../SimplePosMiddleware.sol | 16 +- .../sqrt-task-network/SqrtTaskMiddleware.sol | 4 +- src/managers/base/BaseManager.sol | 12 +- src/managers/base/CaptureTimestampManager.sol | 5 +- src/managers/base/OperatorManager.sol | 45 +++- src/managers/base/StakePowerManager.sol | 18 +- src/managers/base/VaultManager.sol | 138 ++++++----- .../access/OwnableAccessManager.sol | 2 +- .../extensions/access/OzAccessManaged.sol | 1 + .../capture-timestamps/EpochCapture.sol | 11 +- .../capture-timestamps/TimestampCapture.sol | 5 +- .../extensions/keys/KeyManager256.sol | 10 +- .../extensions/keys/KeyManagerBytes.sol | 10 +- .../stake-powers/EqualStakePower.sol | 2 +- src/middleware/BaseMiddleware.sol | 17 +- .../extensions/operators/Operators.sol | 2 +- .../operators/SelfRegisterOperators.sol | 4 +- .../extensions/read-helper/ReadHelper.sol | 34 +++ .../extensions/read-helper/ReadHelperImpl.sol | 226 ++++++++++++++++++ 19 files changed, 436 insertions(+), 126 deletions(-) create mode 100644 src/middleware/extensions/read-helper/ReadHelper.sol create mode 100644 src/middleware/extensions/read-helper/ReadHelperImpl.sol diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index ab76d3b..5e51248 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -85,7 +85,7 @@ contract SimplePosMiddleware is * @return The total power amount. */ function getTotalPower() public view returns (uint256) { - address[] memory operators = activeOperators(); // Get the list of active operators + address[] memory operators = _activeOperators(); // Get the list of active operators return _totalPower(operators); // Return the total power for the current epoch } @@ -94,7 +94,7 @@ contract SimplePosMiddleware is * @return An array of ValidatorData containing the power and key of each validator. */ function getValidatorSet() public view returns (ValidatorData[] memory validatorSet) { - address[] memory operators = activeOperators(); // Get the list of active operators + address[] memory operators = _activeOperators(); // Get the list of active operators validatorSet = new ValidatorData[](operators.length); // Initialize the validator set uint256 len = 0; // Length counter @@ -106,7 +106,7 @@ contract SimplePosMiddleware is continue; // Skip if the key is inactive } - uint256 power = getOperatorPower(operator); // Get the operator's power + uint256 power = _getOperatorPower(operator); // Get the operator's power validatorSet[len++] = ValidatorData(power, key); // Store the validator data } @@ -139,9 +139,9 @@ contract SimplePosMiddleware is _checkCanSlash(epoch, key); - params.totalPower = getOperatorPowerAt(params.operator, params.epochStart); - params.vaults = activeVaultsAt(params.epochStart, params.operator); - params.subnetworks = activeSubnetworksAt(params.epochStart); + params.totalPower = _getOperatorPowerAt(params.epochStart, params.operator); + params.vaults = _activeVaultsAt(params.epochStart, params.operator); + params.subnetworks = _activeSubnetworksAt(params.epochStart); // Validate hints lengths upfront if (stakeHints.length != slashHints.length || stakeHints.length != params.vaults.length) { @@ -155,7 +155,7 @@ contract SimplePosMiddleware is address vault = params.vaults[i]; for (uint256 j; j < params.subnetworks.length; ++j) { - bytes32 subnetwork = NETWORK().subnetwork(uint96(params.subnetworks[j])); + bytes32 subnetwork = _NETWORK().subnetwork(uint96(params.subnetworks[j])); uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( subnetwork, params.operator, params.epochStart, stakeHints[i][j] ); @@ -182,7 +182,7 @@ contract SimplePosMiddleware is revert InactiveKeySlash(); // Revert if the key is inactive } - if (!operatorWasActiveAt(epochStart, operator)) { + if (!_operatorWasActiveAt(epochStart, operator)) { revert InactiveOperatorSlash(); // Revert if the operator wasn't active } } diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 7a1a80b..8f42184 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -143,13 +143,13 @@ contract SqrtTaskMiddleware is function _slash(uint256 taskIndex, bytes[] calldata stakeHints, bytes[] calldata slashHints) private { Task storage task = tasks[taskIndex]; - address[] memory vaults = activeVaultsAt(task.captureTimestamp, task.operator); + address[] memory vaults = _activeVaultsAt(task.captureTimestamp, task.operator); if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { revert InvalidHints(); } - bytes32 subnetwork = NETWORK().subnetwork(0); + bytes32 subnetwork = _NETWORK().subnetwork(0); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; uint256 slashAmount = IBaseDelegator(IVault(vault).delegator()).stakeAt( diff --git a/src/managers/base/BaseManager.sol b/src/managers/base/BaseManager.sol index 0a6366d..ab7b827 100644 --- a/src/managers/base/BaseManager.sol +++ b/src/managers/base/BaseManager.sol @@ -40,7 +40,7 @@ abstract contract BaseManager is CaptureTimestampManager { address vaultRegistry, address operatorRegistry, address operatorNetOptIn - ) internal onlyInitializing() { + ) internal onlyInitializing { BaseManagerStorage storage $ = _getBaseManagerStorage(); $._network = network; $._slashingWindow = slashingWindow; @@ -49,27 +49,27 @@ abstract contract BaseManager is CaptureTimestampManager { $._operatorNetOptin = operatorNetOptIn; } - function NETWORK() internal view returns (address) { + function _NETWORK() internal view returns (address) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._network; } - function SLASHING_WINDOW() internal view returns (uint48) { + function _SLASHING_WINDOW() internal view returns (uint48) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._slashingWindow; } - function VAULT_REGISTRY() internal view returns (address) { + function _VAULT_REGISTRY() internal view returns (address) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._vaultRegistry; } - function OPERATOR_REGISTRY() internal view returns (address) { + function _OPERATOR_REGISTRY() internal view returns (address) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._operatorRegistry; } - function OPERATOR_NET_OPTIN() internal view returns (address) { + function _OPERATOR_NET_OPTIN() internal view returns (address) { BaseManagerStorage storage $ = _getBaseManagerStorage(); return $._operatorNetOptin; } diff --git a/src/managers/base/CaptureTimestampManager.sol b/src/managers/base/CaptureTimestampManager.sol index 711a10d..31b1f63 100644 --- a/src/managers/base/CaptureTimestampManager.sol +++ b/src/managers/base/CaptureTimestampManager.sol @@ -7,18 +7,19 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * @title CaptureTimestampManager * @notice Abstract contract for managing capture timestamps */ + abstract contract CaptureTimestampManager is Initializable { /** * @notice Returns the current capture timestamp * @return timestamp The current capture timestamp */ - function getCaptureTimestamp() internal view virtual returns (uint48 timestamp); + function getCaptureTimestamp() public view virtual returns (uint48 timestamp); /** * @notice Returns the current timestamp * @return timestamp The current timestamp */ - function now() internal view returns (uint48) { + function _now() internal view returns (uint48) { return Time.timestamp(); } } diff --git a/src/managers/base/OperatorManager.sol b/src/managers/base/OperatorManager.sol index 6fcdecd..334670d 100644 --- a/src/managers/base/OperatorManager.sol +++ b/src/managers/base/OperatorManager.sol @@ -25,17 +25,40 @@ abstract contract OperatorManager is BaseManager { bytes32 private constant OperatorManagerStorageLocation = 0x3b2b549db680c436ebf9aa3c8eeee850852f16da5cdb5137dbc0299ebb219e00; - function _getOperatorManagerStorage() private pure returns (OperatorManagerStorage storage $) { + function _getOperatorManagerStorage() internal pure returns (OperatorManagerStorage storage $) { assembly { $.slot := OperatorManagerStorageLocation } } + /** + * @notice Returns the total number of registered operators, including both active and inactive + * @return The number of registered operators + */ + function _operatorsLength() internal view returns (uint256) { + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operators.length(); + } + /** + * @notice Returns the operator and their associated enabled and disabled times at a specific position + * @param pos The index position in the operators array + * @return The operator address + * @return The enabled timestamp + * @return The disabled timestamp + */ + + function _operatorWithTimesAt( + uint256 pos + ) internal view returns (address, uint48, uint48) { + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operators.at(pos); + } + /** * @notice Returns a list of active operators * @return Array of addresses representing the active operators */ - function activeOperators() internal view returns (address[] memory) { + function _activeOperators() internal view returns (address[] memory) { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); return $._operators.getActive(getCaptureTimestamp()); } @@ -45,7 +68,7 @@ abstract contract OperatorManager is BaseManager { * @param timestamp The timestamp to check * @return Array of addresses representing the active operators at the timestamp */ - function activeOperatorsAt( + function _activeOperatorsAt( uint48 timestamp ) internal view returns (address[] memory) { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); @@ -58,7 +81,7 @@ abstract contract OperatorManager is BaseManager { * @param operator The operator address to check * @return True if the operator was active at the timestamp, false otherwise */ - function operatorWasActiveAt(uint48 timestamp, address operator) internal view returns (bool) { + function _operatorWasActiveAt(uint48 timestamp, address operator) internal view returns (bool) { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); return $._operators.wasActiveAt(timestamp, operator); } @@ -68,7 +91,7 @@ abstract contract OperatorManager is BaseManager { * @param operator The address of the operator to check * @return True if the operator is registered, false otherwise */ - function isOperatorRegistered( + function _isOperatorRegistered( address operator ) internal view returns (bool) { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); @@ -84,16 +107,16 @@ abstract contract OperatorManager is BaseManager { function _registerOperator( address operator ) internal { - if (!IRegistry(OPERATOR_REGISTRY()).isEntity(operator)) { + if (!IRegistry(_OPERATOR_REGISTRY()).isEntity(operator)) { revert NotOperator(); } - if (!IOptInService(OPERATOR_NET_OPTIN()).isOptedIn(operator, NETWORK())) { + if (!IOptInService(_OPERATOR_NET_OPTIN()).isOptedIn(operator, _NETWORK())) { revert OperatorNotOptedIn(); } OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - $._operators.register(now(), operator); + $._operators.register(_now(), operator); } /** @@ -104,7 +127,7 @@ abstract contract OperatorManager is BaseManager { address operator ) internal { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - $._operators.pause(now(), operator); + $._operators.pause(_now(), operator); } /** @@ -115,7 +138,7 @@ abstract contract OperatorManager is BaseManager { address operator ) internal { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - $._operators.unpause(now(), SLASHING_WINDOW(), operator); + $._operators.unpause(_now(), _SLASHING_WINDOW(), operator); } /** @@ -126,6 +149,6 @@ abstract contract OperatorManager is BaseManager { address operator ) internal { OperatorManagerStorage storage $ = _getOperatorManagerStorage(); - $._operators.unregister(now(), SLASHING_WINDOW(), operator); + $._operators.unregister(_now(), _SLASHING_WINDOW(), operator); } } diff --git a/src/managers/base/StakePowerManager.sol b/src/managers/base/StakePowerManager.sol index 2a06cd9..1450cf3 100644 --- a/src/managers/base/StakePowerManager.sol +++ b/src/managers/base/StakePowerManager.sol @@ -7,12 +7,12 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * @title StakePowerManager * @notice Abstract contract for managing stake power conversion */ - abstract contract StakePowerManager is Initializable { - /** - * @notice Converts stake amount to voting power - * @param vault The vault address - * @param stake The stake amount - * @return power The calculated voting power - */ - function stakeToPower(address vault, uint256 stake) internal view virtual returns (uint256 power); - } \ No newline at end of file +abstract contract StakePowerManager is Initializable { + /** + * @notice Converts stake amount to voting power + * @param vault The vault address + * @param stake The stake amount + * @return power The calculated voting power + */ + function stakeToPower(address vault, uint256 stake) public view virtual returns (uint256 power); +} diff --git a/src/managers/base/VaultManager.sol b/src/managers/base/VaultManager.sol index 30ba9cb..897c8ab 100644 --- a/src/managers/base/VaultManager.sol +++ b/src/managers/base/VaultManager.sol @@ -78,7 +78,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @notice Gets the total number of registered subnetworks * @return uint256 The count of registered subnetworks */ - function subnetworksLength() internal view returns (uint256) { + function _subnetworksLength() internal view returns (uint256) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._subnetworks.length(); } @@ -90,7 +90,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @return uint48 The time when the subnetwork was enabled * @return uint48 The time when the subnetwork was disabled */ - function subnetworkWithTimesAt( + function _subnetworkWithTimesAt( uint256 pos ) internal view returns (uint160, uint48, uint48) { VaultManagerStorage storage $ = _getVaultManagerStorage(); @@ -101,7 +101,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @notice Gets all currently active subnetworks * @return uint160[] Array of active subnetwork addresses */ - function activeSubnetworks() internal view returns (uint160[] memory) { + function _activeSubnetworks() internal view returns (uint160[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._subnetworks.getActive(getCaptureTimestamp()); } @@ -111,7 +111,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param timestamp The timestamp to check * @return uint160[] Array of subnetwork addresses that were active at the timestamp */ - function activeSubnetworksAt( + function _activeSubnetworksAt( uint48 timestamp ) internal view returns (uint160[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); @@ -124,7 +124,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param subnetwork The subnetwork identifier * @return bool True if the subnetwork was active at the timestamp */ - function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) internal view returns (bool) { + function _subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) internal view returns (bool) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); } @@ -133,7 +133,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @notice Gets the total number of shared vaults * @return uint256 The count of shared vaults */ - function sharedVaultsLength() internal view returns (uint256) { + function _sharedVaultsLength() internal view returns (uint256) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.length(); } @@ -145,7 +145,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @return uint48 The time when the vault was enabled * @return uint48 The time when the vault was disabled */ - function sharedVaultWithTimesAt( + function _sharedVaultWithTimesAt( uint256 pos ) internal view returns (address, uint48, uint48) { VaultManagerStorage storage $ = _getVaultManagerStorage(); @@ -156,7 +156,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @notice Gets all currently active shared vaults * @return address[] Array of active shared vault addresses */ - function activeSharedVaults() internal view returns (address[] memory) { + function _activeSharedVaults() internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.getActive(getCaptureTimestamp()); } @@ -166,7 +166,9 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param timestamp The timestamp to check * @return address[] Array of shared vault addresses that were active at the timestamp */ - function activeSharedVaultsAt(uint48 timestamp) internal view returns (address[] memory) { + function _activeSharedVaultsAt( + uint48 timestamp + ) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.getActive(timestamp); } @@ -176,7 +178,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param operator The operator address to query * @return uint256 The count of vaults for the operator */ - function operatorVaultsLength( + function _operatorVaultsLength( address operator ) internal view returns (uint256) { VaultManagerStorage storage $ = _getVaultManagerStorage(); @@ -191,7 +193,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @return uint48 The time when the vault was enabled * @return uint48 The time when the vault was disabled */ - function operatorVaultWithTimesAt(address operator, uint256 pos) internal view returns (address, uint48, uint48) { + function _operatorVaultWithTimesAt(address operator, uint256 pos) internal view returns (address, uint48, uint48) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._operatorVaults[operator].at(pos); } @@ -201,21 +203,29 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param operator The operator address * @return address[] Array of active vault addresses */ - function activeOperatorVaults( + function _activeOperatorVaults( address operator ) internal view returns (address[] memory) { + return _activeOperatorVaultsAt(getCaptureTimestamp(), operator); + } + + /** + * @notice Gets all currently active vaults for a specific operator at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @return address[] Array of active vault addresses + */ + function _activeOperatorVaultsAt(uint48 timestamp, address operator) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._operatorVaults[operator].getActive(getCaptureTimestamp()); + return $._operatorVaults[operator].getActive(timestamp); } /** * @notice Gets all currently active vaults across all operators * @return address[] Array of all active vault addresses */ - function activeVaults() internal view returns (address[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - uint48 timestamp = getCaptureTimestamp(); - return activeVaultsAt(timestamp); + function _activeVaults() internal view returns (address[] memory) { + return _activeVaultsAt(getCaptureTimestamp()); } /** @@ -223,7 +233,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param timestamp The timestamp to check * @return address[] Array of vault addresses that were active at the timestamp */ - function activeVaultsAt( + function _activeVaultsAt( uint48 timestamp ) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); @@ -255,12 +265,10 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param operator The operator address * @return address[] Array of active vault addresses for the operator */ - function activeVaults( + function _activeVaults( address operator ) internal view returns (address[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - uint48 timestamp = getCaptureTimestamp(); - return activeVaultsAt(timestamp, operator); + return _activeVaultsAt(getCaptureTimestamp(), operator); } /** @@ -269,7 +277,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param operator The operator address * @return address[] Array of vault addresses that were active at the timestamp */ - function activeVaultsAt(uint48 timestamp, address operator) internal view returns (address[] memory) { + function _activeVaultsAt(uint48 timestamp, address operator) internal view returns (address[] memory) { VaultManagerStorage storage $ = _getVaultManagerStorage(); address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); address[] memory activeOperatorVaults_ = $._operatorVaults[operator].getActive(timestamp); @@ -294,8 +302,8 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param vault The vault address * @return bool True if the vault was active at the timestamp */ - function vaultWasActiveAt(uint48 timestamp, address operator, address vault) internal view returns (bool) { - return sharedVaultWasActiveAt(timestamp, vault) || operatorVaultWasActiveAt(timestamp, operator, vault); + function _vaultWasActiveAt(uint48 timestamp, address operator, address vault) internal view returns (bool) { + return _sharedVaultWasActiveAt(timestamp, vault) || _operatorVaultWasActiveAt(timestamp, operator, vault); } /** @@ -304,7 +312,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param vault The vault address * @return bool True if the shared vault was active at the timestamp */ - function sharedVaultWasActiveAt(uint48 timestamp, address vault) internal view returns (bool) { + function _sharedVaultWasActiveAt(uint48 timestamp, address vault) internal view returns (bool) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._sharedVaults.wasActiveAt(timestamp, vault); } @@ -316,26 +324,30 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param vault The vault address * @return bool True if the operator vault was active at the timestamp */ - function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) internal view returns (bool) { + function _operatorVaultWasActiveAt( + uint48 timestamp, + address operator, + address vault + ) internal view returns (bool) { VaultManagerStorage storage $ = _getVaultManagerStorage(); return $._operatorVaults[operator].wasActiveAt(timestamp, vault); } /** * @notice Gets the stake amount for an operator in a vault and subnetwork at a specific timestamp + * @param timestamp The timestamp to check * @param operator The operator address * @param vault The vault address * @param subnetwork The subnetwork identifier - * @param timestamp The timestamp to check * @return uint256 The stake amount at the timestamp */ - function getOperatorStakeAt( + function _getOperatorStakeAt( + uint48 timestamp, address operator, address vault, - uint96 subnetwork, - uint48 timestamp + uint96 subnetwork ) private view returns (uint256) { - bytes32 subnetworkId = NETWORK().subnetwork(subnetwork); + bytes32 subnetworkId = _NETWORK().subnetwork(subnetwork); return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); } @@ -346,25 +358,25 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param subnetwork The subnetwork identifier * @return uint256 The power amount */ - function getOperatorPower(address operator, address vault, uint96 subnetwork) internal view returns (uint256) { - return getOperatorPowerAt(operator, vault, subnetwork, getCaptureTimestamp()); + function _getOperatorPower(address operator, address vault, uint96 subnetwork) internal view returns (uint256) { + return _getOperatorPowerAt(getCaptureTimestamp(), operator, vault, subnetwork); } /** * @notice Gets the power amount for an operator in a vault and subnetwork at a specific timestamp + * @param timestamp The timestamp to check * @param operator The operator address * @param vault The vault address * @param subnetwork The subnetwork identifier - * @param timestamp The timestamp to check * @return uint256 The power amount at the timestamp */ - function getOperatorPowerAt( + function _getOperatorPowerAt( + uint48 timestamp, address operator, address vault, - uint96 subnetwork, - uint48 timestamp + uint96 subnetwork ) internal view returns (uint256) { - uint256 stake = getOperatorStakeAt(operator, vault, subnetwork, timestamp); + uint256 stake = _getOperatorStakeAt(timestamp, operator, vault, subnetwork); return stakeToPower(vault, stake); } @@ -373,26 +385,26 @@ abstract contract VaultManager is BaseManager, StakePowerManager { * @param operator The operator address * @return power The total power amount */ - function getOperatorPower( + function _getOperatorPower( address operator ) internal view returns (uint256 power) { - return getOperatorPowerAt(operator, getCaptureTimestamp()); + return _getOperatorPowerAt(getCaptureTimestamp(), operator); } /** * @notice Gets the total power amount for an operator across all vaults and subnetworks at a specific timestamp - * @param operator The operator address * @param timestamp The timestamp to check + * @param operator The operator address * @return power The total power amount at the timestamp */ - function getOperatorPowerAt(address operator, uint48 timestamp) internal view returns (uint256 power) { - address[] memory vaults = activeVaultsAt(timestamp, operator); - uint160[] memory subnetworks = activeSubnetworksAt(timestamp); + function _getOperatorPowerAt(uint48 timestamp, address operator) internal view returns (uint256 power) { + address[] memory vaults = _activeVaultsAt(timestamp, operator); + uint160[] memory subnetworks = _activeSubnetworksAt(timestamp); for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; for (uint256 j; j < subnetworks.length; ++j) { - power += getOperatorPowerAt(operator, vault, uint96(subnetworks[j]), timestamp); + power += _getOperatorPowerAt(timestamp, operator, vault, uint96(subnetworks[j])); } } @@ -408,7 +420,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { address[] memory operators ) internal view returns (uint256 power) { for (uint256 i; i < operators.length; ++i) { - power += getOperatorPower(operators[i]); + power += _getOperatorPower(operators[i]); } return power; @@ -422,7 +434,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { uint96 subnetwork ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.register(now(), uint160(subnetwork)); + $._subnetworks.register(_now(), uint160(subnetwork)); } /** @@ -433,7 +445,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { uint96 subnetwork ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.pause(now(), uint160(subnetwork)); + $._subnetworks.pause(_now(), uint160(subnetwork)); } /** @@ -444,7 +456,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { uint96 subnetwork ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.unpause(now(), SLASHING_WINDOW(), uint160(subnetwork)); + $._subnetworks.unpause(_now(), _SLASHING_WINDOW(), uint160(subnetwork)); } /** @@ -455,7 +467,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { uint96 subnetwork ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.unregister(now(), SLASHING_WINDOW(), uint160(subnetwork)); + $._subnetworks.unregister(_now(), _SLASHING_WINDOW(), uint160(subnetwork)); } /** @@ -467,7 +479,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); _validateVault(vault); - $._sharedVaults.register(now(), vault); + $._sharedVaults.register(_now(), vault); } /** @@ -480,7 +492,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { _validateVault(vault); _validateOperatorVault(operator, vault); - $._operatorVaults[operator].register(now(), vault); + $._operatorVaults[operator].register(_now(), vault); $._vaultOperator.set(vault, operator); } @@ -492,7 +504,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { address vault ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.pause(now(), vault); + $._sharedVaults.pause(_now(), vault); } /** @@ -503,7 +515,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { address vault ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.unpause(now(), SLASHING_WINDOW(), vault); + $._sharedVaults.unpause(_now(), _SLASHING_WINDOW(), vault); } /** @@ -513,7 +525,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { */ function _pauseOperatorVault(address operator, address vault) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].pause(now(), vault); + $._operatorVaults[operator].pause(_now(), vault); } /** @@ -523,7 +535,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { */ function _unpauseOperatorVault(address operator, address vault) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].unpause(now(), SLASHING_WINDOW(), vault); + $._operatorVaults[operator].unpause(_now(), _SLASHING_WINDOW(), vault); } /** @@ -534,7 +546,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { address vault ) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.unregister(now(), SLASHING_WINDOW(), vault); + $._sharedVaults.unregister(_now(), _SLASHING_WINDOW(), vault); } /** @@ -544,7 +556,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { */ function _unregisterOperatorVault(address operator, address vault) internal { VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].unregister(now(), SLASHING_WINDOW(), vault); + $._operatorVaults[operator].unregister(_now(), _SLASHING_WINDOW(), vault); $._vaultOperator.remove(vault); } @@ -571,11 +583,11 @@ abstract contract VaultManager is BaseManager, StakePowerManager { revert NotOperatorVault(); } - if (!vaultWasActiveAt(timestamp, operator, vault)) { + if (!_vaultWasActiveAt(timestamp, operator, vault)) { revert InactiveVaultSlash(); } - if (timestamp + SLASHING_WINDOW() < now()) { + if (timestamp + _SLASHING_WINDOW() < _now()) { revert TooOldTimestampSlash(); } @@ -626,7 +638,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { address vault ) private view { VaultManagerStorage storage $ = _getVaultManagerStorage(); - if (!IRegistry(VAULT_REGISTRY()).isEntity(vault)) { + if (!IRegistry(_VAULT_REGISTRY()).isEntity(vault)) { revert NotVault(); } @@ -645,7 +657,7 @@ abstract contract VaultManager is BaseManager, StakePowerManager { vaultEpoch -= IVetoSlasher(slasher).vetoDuration(); } - if (vaultEpoch < SLASHING_WINDOW()) { + if (vaultEpoch < _SLASHING_WINDOW()) { revert VaultEpochTooShort(); } } diff --git a/src/managers/extensions/access/OwnableAccessManager.sol b/src/managers/extensions/access/OwnableAccessManager.sol index 6111d01..139c0b0 100644 --- a/src/managers/extensions/access/OwnableAccessManager.sol +++ b/src/managers/extensions/access/OwnableAccessManager.sol @@ -37,7 +37,7 @@ abstract contract OwnableAccessManager is AccessManager { /** * @notice Initializes the contract with an owner address * @param owner_ The address to set as the owner - */ + */ function __OwnableAccessManager_init( address owner_ ) internal onlyInitializing { diff --git a/src/managers/extensions/access/OzAccessManaged.sol b/src/managers/extensions/access/OzAccessManaged.sol index 6aed159..ae44cee 100644 --- a/src/managers/extensions/access/OzAccessManaged.sol +++ b/src/managers/extensions/access/OzAccessManaged.sol @@ -17,6 +17,7 @@ abstract contract OzAccessManaged is AccessManager, AccessManagedUpgradeable { * @param authority The address to set as the access manager authority * @dev Can only be called during initialization */ + function __OzAccessManaged_init( address authority ) internal onlyInitializing { diff --git a/src/managers/extensions/capture-timestamps/EpochCapture.sol b/src/managers/extensions/capture-timestamps/EpochCapture.sol index cde9433..dcee6bc 100644 --- a/src/managers/extensions/capture-timestamps/EpochCapture.sol +++ b/src/managers/extensions/capture-timestamps/EpochCapture.sol @@ -9,7 +9,6 @@ import {CaptureTimestampManager} from "../../base/CaptureTimestampManager.sol"; * @dev Implements CaptureTimestampManager with epoch-based timestamp capture * @dev Epochs are fixed time periods starting from a base timestamp */ - abstract contract EpochCapture is CaptureTimestampManager { uint64 public constant EpochCapture_VERSION = 1; @@ -38,7 +37,7 @@ abstract contract EpochCapture is CaptureTimestampManager { ) internal onlyInitializing { EpochCaptureStorage storage $ = _getEpochCaptureStorage(); $.epochDuration = epochDuration; - $.startTimestamp = now(); + $.startTimestamp = _now(); } /* @@ -48,7 +47,7 @@ abstract contract EpochCapture is CaptureTimestampManager { */ function getEpochStart( uint48 epoch - ) internal view returns (uint48) { + ) public view returns (uint48) { EpochCaptureStorage storage $ = _getEpochCaptureStorage(); return $.startTimestamp + epoch * $.epochDuration; } @@ -57,16 +56,16 @@ abstract contract EpochCapture is CaptureTimestampManager { * @notice Returns the current epoch. * @return The current epoch. */ - function getCurrentEpoch() internal view returns (uint48) { + function getCurrentEpoch() public view returns (uint48) { EpochCaptureStorage storage $ = _getEpochCaptureStorage(); - return (now() - $.startTimestamp) / $.epochDuration; + return (_now() - $.startTimestamp) / $.epochDuration; } /* * @notice Returns the capture timestamp for the current epoch. * @return The capture timestamp. */ - function getCaptureTimestamp() internal view override returns (uint48 timestamp) { + function getCaptureTimestamp() public view override returns (uint48 timestamp) { return getEpochStart(getCurrentEpoch()); } } diff --git a/src/managers/extensions/capture-timestamps/TimestampCapture.sol b/src/managers/extensions/capture-timestamps/TimestampCapture.sol index fe130c3..4aa6f39 100644 --- a/src/managers/extensions/capture-timestamps/TimestampCapture.sol +++ b/src/managers/extensions/capture-timestamps/TimestampCapture.sol @@ -8,7 +8,6 @@ import {CaptureTimestampManager} from "../../base/CaptureTimestampManager.sol"; * @notice A middleware extension that captures timestamps by subtracting 1 second from current time * @dev Implements CaptureTimestampManager with a simple timestamp capture mechanism */ - abstract contract TimestampCapture is CaptureTimestampManager { uint64 public constant TimestampCapture_VERSION = 1; @@ -16,7 +15,7 @@ abstract contract TimestampCapture is CaptureTimestampManager { * @notice Returns the current timestamp minus 1 second. * @return timestamp The current timestamp minus 1 second. */ - function getCaptureTimestamp() internal view override returns (uint48 timestamp) { - return now() - 1; + function getCaptureTimestamp() public view override returns (uint48 timestamp) { + return _now() - 1; } } diff --git a/src/managers/extensions/keys/KeyManager256.sol b/src/managers/extensions/keys/KeyManager256.sol index 89c3782..86fe56b 100644 --- a/src/managers/extensions/keys/KeyManager256.sol +++ b/src/managers/extensions/keys/KeyManager256.sol @@ -104,17 +104,17 @@ abstract contract KeyManager256 is KeyManager { if (s.keys[operator].length() > 0) { // try to remove disabled keys bytes32 prevKey = s.keys[operator].array[0].value; - if (s.keys[operator].checkUnregister(now(), SLASHING_WINDOW(), prevKey)) { - s.keys[operator].unregister(now(), SLASHING_WINDOW(), prevKey); + if (s.keys[operator].checkUnregister(_now(), _SLASHING_WINDOW(), prevKey)) { + s.keys[operator].unregister(_now(), _SLASHING_WINDOW(), prevKey); delete s.keyToOperator[prevKey]; - } else if (s.keys[operator].wasActiveAt(now(), prevKey)) { - s.keys[operator].pause(now(), prevKey); + } else if (s.keys[operator].wasActiveAt(_now(), prevKey)) { + s.keys[operator].pause(_now(), prevKey); } } if (key != ZERO_BYTES32) { // register the new key - s.keys[operator].register(now(), key); + s.keys[operator].register(_now(), key); s.keyToOperator[key] = operator; } } diff --git a/src/managers/extensions/keys/KeyManagerBytes.sol b/src/managers/extensions/keys/KeyManagerBytes.sol index 7c0acec..7de6588 100644 --- a/src/managers/extensions/keys/KeyManagerBytes.sol +++ b/src/managers/extensions/keys/KeyManagerBytes.sol @@ -101,17 +101,17 @@ abstract contract KeyManagerBytes is KeyManager { if ($._keys[operator].length() > 0) { // try to remove disabled keys bytes memory prevKey = $._keys[operator].array[0].value; - if ($._keys[operator].checkUnregister(now(), SLASHING_WINDOW(), prevKey)) { - $._keys[operator].unregister(now(), SLASHING_WINDOW(), prevKey); + if ($._keys[operator].checkUnregister(_now(), _SLASHING_WINDOW(), prevKey)) { + $._keys[operator].unregister(_now(), _SLASHING_WINDOW(), prevKey); delete $._keyToOperator[prevKey]; - } else if ($._keys[operator].wasActiveAt(now(), prevKey)) { - $._keys[operator].pause(now(), prevKey); + } else if ($._keys[operator].wasActiveAt(_now(), prevKey)) { + $._keys[operator].pause(_now(), prevKey); } } if (keyHash != ZERO_BYTES_HASH) { // register the new key - $._keys[operator].register(now(), key); + $._keys[operator].register(_now(), key); $._keyToOperator[key] = operator; } } diff --git a/src/managers/extensions/stake-powers/EqualStakePower.sol b/src/managers/extensions/stake-powers/EqualStakePower.sol index 81f483e..3899d52 100644 --- a/src/managers/extensions/stake-powers/EqualStakePower.sol +++ b/src/managers/extensions/stake-powers/EqualStakePower.sol @@ -15,7 +15,7 @@ abstract contract EqualStakePower is StakePowerManager { * @param stake The stake amount * @return power The calculated voting power (equal to stake) */ - function stakeToPower(address vault, uint256 stake) internal pure override returns (uint256 power) { + function stakeToPower(address vault, uint256 stake) public pure override returns (uint256 power) { return stake; } } diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 61fb8ba..23bfb7c 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -5,6 +5,7 @@ import {VaultManager} from "../managers/base/VaultManager.sol"; import {OperatorManager} from "../managers/base/OperatorManager.sol"; import {AccessManager} from "../managers/base/AccessManager.sol"; import {KeyManager} from "../managers/base/KeyManager.sol"; +// import {ViewHelper} from "./ViewHelper.sol"; /** * @title BaseMiddleware @@ -18,4 +19,18 @@ import {KeyManager} from "../managers/base/KeyManager.sol"; * This contract serves as a foundation for building custom middleware by providing essential * management capabilities that can be extended with additional functionality. */ -abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager {} +abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager { +// function __BaseMiddleware_init() internal initializer { +// // Deploy view helper contract for delegating view functions +// address viewHelper = address(new ViewHelper()); +// // _setViewHelper(viewHelper); +// } + +// fallback() external { +// (bool success, bytes memory returndata) = address(new ViewHelper()).delegatecall(msg.data); +// bytes memory revertData = abi.encode(success, returndata); +// assembly { +// revert(add(32, revertData), mload(revertData)) +// } +// } +} diff --git a/src/middleware/extensions/operators/Operators.sol b/src/middleware/extensions/operators/Operators.sol index 67c7a99..264dffb 100644 --- a/src/middleware/extensions/operators/Operators.sol +++ b/src/middleware/extensions/operators/Operators.sol @@ -76,7 +76,7 @@ abstract contract Operators is BaseMiddleware { * @param vault The address of the vault */ function registerOperatorVault(address operator, address vault) public checkAccess { - require(isOperatorRegistered(operator), "Operator not registered"); + require(_isOperatorRegistered(operator), "Operator not registered"); _beforeRegisterOperatorVault(operator, vault); _registerOperatorVault(operator, vault); } diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index 41a160c..5ae9c9d 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -223,7 +223,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg function registerOperatorVault( address vault ) public { - require(isOperatorRegistered(msg.sender), "Operator not registered"); + require(_isOperatorRegistered(msg.sender), "Operator not registered"); _beforeRegisterOperatorVault(msg.sender, vault); _registerOperatorVault(msg.sender, vault); } @@ -235,7 +235,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg * @param signature EIP712 signature authorizing vault registration */ function registerOperatorVault(address operator, address vault, bytes memory signature) public { - require(isOperatorRegistered(operator), "Operator not registered"); + require(_isOperatorRegistered(operator), "Operator not registered"); _beforeRegisterOperatorVault(operator, vault); SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( diff --git a/src/middleware/extensions/read-helper/ReadHelper.sol b/src/middleware/extensions/read-helper/ReadHelper.sol new file mode 100644 index 0000000..2137e09 --- /dev/null +++ b/src/middleware/extensions/read-helper/ReadHelper.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract ReadHelper is Initializable { + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.ReadHelper.readHelperImpl")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant ReadHelperStorageLocation = + 0xeeb01dcf9eb4176c944794e8cb12d9caba4faa2514a692b173894bc3e9135d00; + + function _readHelperImpl() internal view returns (address readHelperImpl_) { + bytes32 location = ReadHelperStorageLocation; + assembly { + readHelperImpl_ := sload(location) + } + } + + function __ReadHelper_init( + address readHelperImpl + ) internal onlyInitializing { + bytes32 location = ReadHelperStorageLocation; + assembly { + sstore(location, readHelperImpl) + } + } + + fallback() external { + (bool success, bytes memory returndata) = _readHelperImpl().delegatecall(msg.data); + bytes memory revertData = abi.encode(success, returndata); + assembly { + revert(add(32, revertData), mload(revertData)) + } + } +} diff --git a/src/middleware/extensions/read-helper/ReadHelperImpl.sol b/src/middleware/extensions/read-helper/ReadHelperImpl.sol new file mode 100644 index 0000000..79254fb --- /dev/null +++ b/src/middleware/extensions/read-helper/ReadHelperImpl.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {BaseMiddleware} from "../../../middleware/BaseMiddleware.sol"; +import {NoAccessManager} from "../../../managers/extensions/access/NoAccessManager.sol"; + +/** + * @title ReadHelperImpl + * @notice A helper contract for view functions that combines core manager functionality + * @dev This contract serves as a foundation for building custom middleware by providing essential + * management capabilities that can be extended with additional functionality. + */ +contract ReadHelperImpl is BaseMiddleware, NoAccessManager { + function getCaptureTimestamp() public view override returns (uint48 timestamp) { + (bool success, bytes memory data) = msg.sender.staticcall(msg.data); + require(success, "ReadHelper: getCaptureTimestamp failed"); + return abi.decode(data, (uint48)); + } + + function stakeToPower(address vault, uint256 stake) public view override returns (uint256 power) { + (bool success, bytes memory data) = msg.sender.staticcall(msg.data); + require(success, "ReadHelper: getStakePower failed"); + return abi.decode(data, (uint256)); + } + + function keyWasActiveAt(uint48 timestamp, bytes memory key) public view override returns (bool) { + (bool success, bytes memory data) = msg.sender.staticcall(msg.data); + require(success, "ReadHelper: keyWasActiveAt failed"); + return abi.decode(data, (bool)); + } + + function operatorKey( + address operator + ) public view override returns (bytes memory) { + (bool success, bytes memory data) = msg.sender.staticcall(msg.data); + require(success, "ReadHelper: operatorKey failed"); + return abi.decode(data, (bytes)); + } + + function operatorByKey( + bytes memory key + ) public view override returns (address) { + (bool success, bytes memory data) = msg.sender.staticcall(msg.data); + require(success, "ReadHelper: operatorByKey failed"); + return abi.decode(data, (address)); + } + + function NETWORK() external view returns (address) { + return _NETWORK(); + } + + function SLASHING_WINDOW() external view returns (uint48) { + return _SLASHING_WINDOW(); + } + + function VAULT_REGISTRY() external view returns (address) { + return _VAULT_REGISTRY(); + } + + function OPERATOR_REGISTRY() external view returns (address) { + return _OPERATOR_REGISTRY(); + } + + function OPERATOR_NET_OPTIN() external view returns (address) { + return _OPERATOR_NET_OPTIN(); + } + + function operatorsLength() external view returns (uint256) { + return _operatorsLength(); + } + + function operatorWithTimesAt( + uint256 pos + ) external view returns (address, uint48, uint48) { + return _operatorWithTimesAt(pos); + } + + function activeOperators() external view returns (address[] memory) { + return _activeOperators(); + } + + function activeOperatorsAt( + uint48 timestamp + ) external view returns (address[] memory) { + return _activeOperatorsAt(timestamp); + } + + function operatorWasActiveAt(uint48 timestamp, address operator) external view returns (bool) { + return _operatorWasActiveAt(timestamp, operator); + } + + function isOperatorRegistered( + address operator + ) external view returns (bool) { + return _isOperatorRegistered(operator); + } + + function subnetworksLength() external view returns (uint256) { + return _subnetworksLength(); + } + + function subnetworkWithTimesAt( + uint256 pos + ) external view returns (uint160, uint48, uint48) { + return _subnetworkWithTimesAt(pos); + } + + function activeSubnetworks() external view returns (uint160[] memory) { + return _activeSubnetworks(); + } + + function activeSubnetworksAt( + uint48 timestamp + ) external view returns (uint160[] memory) { + return _activeSubnetworksAt(timestamp); + } + + function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) external view returns (bool) { + return _subnetworkWasActiveAt(timestamp, subnetwork); + } + + function sharedVaultsLength() external view returns (uint256) { + return _sharedVaultsLength(); + } + + function sharedVaultWithTimesAt( + uint256 pos + ) external view returns (address, uint48, uint48) { + return _sharedVaultWithTimesAt(pos); + } + + function activeSharedVaults() external view returns (address[] memory) { + return _activeSharedVaults(); + } + + function activeSharedVaultsAt( + uint48 timestamp + ) external view returns (address[] memory) { + return _activeSharedVaultsAt(timestamp); + } + + function operatorVaultsLength( + address operator + ) external view returns (uint256) { + return _operatorVaultsLength(operator); + } + + function operatorVaultWithTimesAt(address operator, uint256 pos) external view returns (address, uint48, uint48) { + return _operatorVaultWithTimesAt(operator, pos); + } + + function activeOperatorVaults( + address operator + ) external view returns (address[] memory) { + return _activeOperatorVaults(operator); + } + + function activeOperatorVaultsAt(uint48 timestamp, address operator) external view returns (address[] memory) { + return _activeOperatorVaultsAt(timestamp, operator); + } + + function activeVaults() external view returns (address[] memory) { + return _activeVaults(); + } + + function activeVaultsAt( + uint48 timestamp + ) external view returns (address[] memory) { + return _activeVaultsAt(timestamp); + } + + function activeVaults( + address operator + ) external view returns (address[] memory) { + return _activeVaults(operator); + } + + function activeVaultsAt(uint48 timestamp, address operator) external view returns (address[] memory) { + return _activeVaultsAt(timestamp, operator); + } + + function vaultWasActiveAt(uint48 timestamp, address operator, address vault) external view returns (bool) { + return _vaultWasActiveAt(timestamp, operator, vault); + } + + function sharedVaultWasActiveAt(uint48 timestamp, address vault) external view returns (bool) { + return _sharedVaultWasActiveAt(timestamp, vault); + } + + function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) external view returns (bool) { + return _operatorVaultWasActiveAt(timestamp, operator, vault); + } + + function getOperatorPower(address operator, address vault, uint96 subnetwork) external view returns (uint256) { + return _getOperatorPower(operator, vault, subnetwork); + } + + function getOperatorPowerAt( + uint48 timestamp, + address operator, + address vault, + uint96 subnetwork + ) external view returns (uint256) { + return _getOperatorPowerAt(timestamp, operator, vault, subnetwork); + } + + function getOperatorPower( + address operator + ) external view returns (uint256) { + return _getOperatorPower(operator); + } + + function getOperatorPowerAt(uint48 timestamp, address operator) external view returns (uint256) { + return _getOperatorPowerAt(timestamp, operator); + } + + function totalPower( + address[] memory operators + ) external view returns (uint256) { + return _totalPower(operators); + } + + function _updateKey(address operator, bytes memory key) internal pure override { + revert(); + } +} From 81c65f140f653d8b9dd1245c246a8c802c44ce5e Mon Sep 17 00:00:00 2001 From: alrxy Date: Thu, 5 Dec 2024 18:59:04 +0400 Subject: [PATCH 091/115] chore: clean base middleware --- src/middleware/BaseMiddleware.sol | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 23bfb7c..61fb8ba 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -5,7 +5,6 @@ import {VaultManager} from "../managers/base/VaultManager.sol"; import {OperatorManager} from "../managers/base/OperatorManager.sol"; import {AccessManager} from "../managers/base/AccessManager.sol"; import {KeyManager} from "../managers/base/KeyManager.sol"; -// import {ViewHelper} from "./ViewHelper.sol"; /** * @title BaseMiddleware @@ -19,18 +18,4 @@ import {KeyManager} from "../managers/base/KeyManager.sol"; * This contract serves as a foundation for building custom middleware by providing essential * management capabilities that can be extended with additional functionality. */ -abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager { -// function __BaseMiddleware_init() internal initializer { -// // Deploy view helper contract for delegating view functions -// address viewHelper = address(new ViewHelper()); -// // _setViewHelper(viewHelper); -// } - -// fallback() external { -// (bool success, bytes memory returndata) = address(new ViewHelper()).delegatecall(msg.data); -// bytes memory revertData = abi.encode(success, returndata); -// assembly { -// revert(add(32, revertData), mload(revertData)) -// } -// } -} +abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager {} From 990cf3e4cd972d38baf1f4da3500f73ac54664c6 Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 6 Dec 2024 16:14:35 +0400 Subject: [PATCH 092/115] fix: read helper --- .../SelfRegisterEd25519Middleware.sol | 12 +- .../SelfRegisterMiddleware.sol | 12 +- .../SimplePosMiddleware.sol | 14 +- .../sqrt-task-network/SqrtTaskMiddleware.sol | 8 +- src/interfaces/IBaseMiddleware.sol | 121 ++++++++++++++++++ src/middleware/BaseMiddleware.sol | 42 +++++- .../ReadHelperImpl.sol => ReadHelper.sol} | 52 +++----- src/middleware/extensions/SharedVaults.sol | 8 +- src/middleware/extensions/Subnetworks.sol | 8 +- .../ForcePauseSelfRegisterOperators.sol | 8 +- .../extensions/operators/Operators.sol | 18 +-- .../operators/SelfRegisterOperators.sol | 22 ++-- .../extensions/read-helper/ReadHelper.sol | 34 ----- test/OperatorsRegistration.t.sol | 120 +++++++++++------ test/SigTests.t.sol | 29 +++-- 15 files changed, 335 insertions(+), 173 deletions(-) create mode 100644 src/interfaces/IBaseMiddleware.sol rename src/middleware/{extensions/read-helper/ReadHelperImpl.sol => ReadHelper.sol} (78%) delete mode 100644 src/middleware/extensions/read-helper/ReadHelper.sol diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index 3eb9711..04eeab6 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -39,12 +39,13 @@ contract SelfRegisterEd25519Middleware is */ constructor( address network, - address operatorRegistry, + uint48 slashingWindow, address vaultRegistry, + address operatorRegistry, address operatorNetOptin, - uint48 slashingWindow + address readHelper ) { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); } function initialize( @@ -52,9 +53,10 @@ contract SelfRegisterEd25519Middleware is uint48 slashingWindow, address vaultRegistry, address operatorRegistry, - address operatorNetOptIn + address operatorNetOptin, + address readHelper ) internal initializer { - __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); __SelfRegisterOperators_init("SelfRegisterEd25519Middleware"); } } diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index 675c80c..bf02a6b 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -39,12 +39,13 @@ contract SelfRegisterMiddleware is */ constructor( address network, - address operatorRegistry, + uint48 slashingWindow, address vaultRegistry, + address operatorRegistry, address operatorNetOptin, - uint48 slashingWindow + address readHelper ) { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); } function initialize( @@ -52,9 +53,10 @@ contract SelfRegisterMiddleware is uint48 slashingWindow, address vaultRegistry, address operatorRegistry, - address operatorNetOptIn + address operatorNetOptIn, + address readHelper ) internal initializer { - __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, readHelper); __SelfRegisterOperators_init("SelfRegisterMiddleware"); } } diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 5e51248..d61d0cd 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -56,14 +56,17 @@ contract SimplePosMiddleware is */ constructor( address network, - address operatorRegistry, + uint48 slashingWindow, address vaultRegistry, + address operatorRegistry, address operatorNetOptin, + address readHelper, address owner, - uint48 epochDuration, - uint48 slashingWindow + uint48 epochDuration ) { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, owner, epochDuration); + initialize( + network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper, owner, epochDuration + ); } function initialize( @@ -72,10 +75,11 @@ contract SimplePosMiddleware is address vaultRegistry, address operatorRegistry, address operatorNetOptin, + address readHelper, address owner, uint48 epochDuration ) internal initializer { - __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); __OwnableAccessManager_init(owner); __EpochCapture_init(epochDuration); } diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 8f42184..78721a2 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -51,13 +51,14 @@ contract SqrtTaskMiddleware is constructor( address network, + uint48 slashingWindow, address operatorRegistry, address vaultRegistry, address operatorNetOptin, - uint48 slashingWindow, + address readHelper, address owner ) EIP712("SqrtTaskMiddleware", "1") { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, owner); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper, owner); } function initialize( @@ -66,9 +67,10 @@ contract SqrtTaskMiddleware is address vaultRegistry, address operatorRegistry, address operatorNetOptin, + address readHelper, address owner ) internal initializer { - __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); __OwnableAccessManager_init(owner); } diff --git a/src/interfaces/IBaseMiddleware.sol b/src/interfaces/IBaseMiddleware.sol new file mode 100644 index 0000000..d8c2dcb --- /dev/null +++ b/src/interfaces/IBaseMiddleware.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +interface IBaseMiddleware { + function getCaptureTimestamp() external view returns (uint48 timestamp); + + function stakeToPower(address vault, uint256 stake) external view returns (uint256 power); + + function keyWasActiveAt(uint48 timestamp, bytes memory key) external view returns (bool); + + function operatorKey( + address operator + ) external view returns (bytes memory); + + function operatorByKey( + bytes memory key + ) external view returns (address); + + function NETWORK() external view returns (address); + + function SLASHING_WINDOW() external view returns (uint48); + + function VAULT_REGISTRY() external view returns (address); + + function OPERATOR_REGISTRY() external view returns (address); + + function OPERATOR_NET_OPTIN() external view returns (address); + + function operatorsLength() external view returns (uint256); + + function operatorWithTimesAt( + uint256 pos + ) external view returns (address, uint48, uint48); + + function activeOperators() external view returns (address[] memory); + + function activeOperatorsAt( + uint48 timestamp + ) external view returns (address[] memory); + + function operatorWasActiveAt(uint48 timestamp, address operator) external view returns (bool); + + function isOperatorRegistered( + address operator + ) external view returns (bool); + + function subnetworksLength() external view returns (uint256); + + function subnetworkWithTimesAt( + uint256 pos + ) external view returns (uint160, uint48, uint48); + + function activeSubnetworks() external view returns (uint160[] memory); + + function activeSubnetworksAt( + uint48 timestamp + ) external view returns (uint160[] memory); + + function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) external view returns (bool); + + function sharedVaultsLength() external view returns (uint256); + + function sharedVaultWithTimesAt( + uint256 pos + ) external view returns (address, uint48, uint48); + + function activeSharedVaults() external view returns (address[] memory); + + function activeSharedVaultsAt( + uint48 timestamp + ) external view returns (address[] memory); + + function operatorVaultsLength( + address operator + ) external view returns (uint256); + + function operatorVaultWithTimesAt(address operator, uint256 pos) external view returns (address, uint48, uint48); + + function activeOperatorVaults( + address operator + ) external view returns (address[] memory); + + function activeOperatorVaultsAt(uint48 timestamp, address operator) external view returns (address[] memory); + + function activeVaults() external view returns (address[] memory); + + function activeVaultsAt( + uint48 timestamp + ) external view returns (address[] memory); + + function activeVaults( + address operator + ) external view returns (address[] memory); + + function activeVaultsAt(uint48 timestamp, address operator) external view returns (address[] memory); + + function vaultWasActiveAt(uint48 timestamp, address operator, address vault) external view returns (bool); + + function sharedVaultWasActiveAt(uint48 timestamp, address vault) external view returns (bool); + + function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) external view returns (bool); + + function getOperatorPower(address operator, address vault, uint96 subnetwork) external view returns (uint256); + + function getOperatorPowerAt( + uint48 timestamp, + address operator, + address vault, + uint96 subnetwork + ) external view returns (uint256); + + function getOperatorPower( + address operator + ) external view returns (uint256); + + function getOperatorPowerAt(uint48 timestamp, address operator) external view returns (uint256); + + function totalPower( + address[] memory operators + ) external view returns (uint256); +} diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 61fb8ba..983873a 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -5,6 +5,7 @@ import {VaultManager} from "../managers/base/VaultManager.sol"; import {OperatorManager} from "../managers/base/OperatorManager.sol"; import {AccessManager} from "../managers/base/AccessManager.sol"; import {KeyManager} from "../managers/base/KeyManager.sol"; +import "forge-std/console.sol"; /** * @title BaseMiddleware @@ -18,4 +19,43 @@ import {KeyManager} from "../managers/base/KeyManager.sol"; * This contract serves as a foundation for building custom middleware by providing essential * management capabilities that can be extended with additional functionality. */ -abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager {} +abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager { + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseMiddleware.readHelper")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant ReadHelperStorageLocation = + 0x19370075337de2141d8d7be7b8e2dab6686d6a69c74729da94b114b78d743b00; + + function __BaseMiddleware_init( + address network, + uint48 slashingWindow, + address vaultRegistry, + address operatorRegistry, + address operatorNetOptin, + address readHelper + ) internal onlyInitializing { + __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + assembly { + sstore(ReadHelperStorageLocation, readHelper) + } + } + + /** + * @notice The fallback function is used to implement getter functions by delegating calls to the ReadHelper contract + * @dev This allows the BaseMiddleware to expose view functions defined in the ReadHelper without explicitly implementing them, + * reducing code duplication and maintaining a single source of truth for read operations + */ + fallback() external { + address readHelper_; + assembly { + readHelper_ := sload(ReadHelperStorageLocation) + } + (bool success, bytes memory returndata) = readHelper_.delegatecall(abi.encodePacked(msg.data, address(this))); + if (!success) { + assembly { + revert(add(returndata, 0x20), mload(returndata)) + } + } + assembly { + return(add(returndata, 0x20), mload(returndata)) + } + } +} diff --git a/src/middleware/extensions/read-helper/ReadHelperImpl.sol b/src/middleware/ReadHelper.sol similarity index 78% rename from src/middleware/extensions/read-helper/ReadHelperImpl.sol rename to src/middleware/ReadHelper.sol index 79254fb..947cdbf 100644 --- a/src/middleware/extensions/read-helper/ReadHelperImpl.sol +++ b/src/middleware/ReadHelper.sol @@ -1,48 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../../middleware/BaseMiddleware.sol"; -import {NoAccessManager} from "../../../managers/extensions/access/NoAccessManager.sol"; +import {BaseMiddleware} from "./BaseMiddleware.sol"; +import {NoAccessManager} from "../managers/extensions/access/NoAccessManager.sol"; +import {NoKeyManager} from "../managers/extensions/keys/NoKeyManager.sol"; +import "forge-std/console.sol"; /** - * @title ReadHelperImpl + * @title ReadHelper * @notice A helper contract for view functions that combines core manager functionality * @dev This contract serves as a foundation for building custom middleware by providing essential * management capabilities that can be extended with additional functionality. */ -contract ReadHelperImpl is BaseMiddleware, NoAccessManager { +contract ReadHelper is BaseMiddleware, NoAccessManager, NoKeyManager { function getCaptureTimestamp() public view override returns (uint48 timestamp) { - (bool success, bytes memory data) = msg.sender.staticcall(msg.data); - require(success, "ReadHelper: getCaptureTimestamp failed"); - return abi.decode(data, (uint48)); + address middleware; + assembly { + middleware := shr(96, calldataload(sub(calldatasize(), 20))) + } + return BaseMiddleware(middleware).getCaptureTimestamp(); } function stakeToPower(address vault, uint256 stake) public view override returns (uint256 power) { - (bool success, bytes memory data) = msg.sender.staticcall(msg.data); - require(success, "ReadHelper: getStakePower failed"); - return abi.decode(data, (uint256)); - } - - function keyWasActiveAt(uint48 timestamp, bytes memory key) public view override returns (bool) { - (bool success, bytes memory data) = msg.sender.staticcall(msg.data); - require(success, "ReadHelper: keyWasActiveAt failed"); - return abi.decode(data, (bool)); - } - - function operatorKey( - address operator - ) public view override returns (bytes memory) { - (bool success, bytes memory data) = msg.sender.staticcall(msg.data); - require(success, "ReadHelper: operatorKey failed"); - return abi.decode(data, (bytes)); - } - - function operatorByKey( - bytes memory key - ) public view override returns (address) { - (bool success, bytes memory data) = msg.sender.staticcall(msg.data); - require(success, "ReadHelper: operatorByKey failed"); - return abi.decode(data, (address)); + address middleware; + assembly { + middleware := shr(96, calldataload(sub(calldatasize(), 20))) + } + return BaseMiddleware(middleware).stakeToPower(vault, stake); } function NETWORK() external view returns (address) { @@ -219,8 +203,4 @@ contract ReadHelperImpl is BaseMiddleware, NoAccessManager { ) external view returns (uint256) { return _totalPower(operators); } - - function _updateKey(address operator, bytes memory key) internal pure override { - revert(); - } } diff --git a/src/middleware/extensions/SharedVaults.sol b/src/middleware/extensions/SharedVaults.sol index 96f38f5..9afffcc 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/middleware/extensions/SharedVaults.sol @@ -61,7 +61,7 @@ abstract contract SharedVaults is BaseMiddleware { */ function _beforeRegisterSharedVault( address sharedVault - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before pausing a shared vault @@ -69,7 +69,7 @@ abstract contract SharedVaults is BaseMiddleware { */ function _beforePauseSharedVault( address sharedVault - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before unpausing a shared vault @@ -77,7 +77,7 @@ abstract contract SharedVaults is BaseMiddleware { */ function _beforeUnpauseSharedVault( address sharedVault - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before unregistering a shared vault @@ -85,5 +85,5 @@ abstract contract SharedVaults is BaseMiddleware { */ function _beforeUnregisterSharedVault( address sharedVault - ) public virtual {} + ) internal virtual {} } diff --git a/src/middleware/extensions/Subnetworks.sol b/src/middleware/extensions/Subnetworks.sol index e610a5d..d28bc1e 100644 --- a/src/middleware/extensions/Subnetworks.sol +++ b/src/middleware/extensions/Subnetworks.sol @@ -61,7 +61,7 @@ abstract contract Subnetworks is BaseMiddleware { */ function _beforeRegisterSubnetwork( uint96 subnetwork - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before pausing a subnetwork @@ -69,7 +69,7 @@ abstract contract Subnetworks is BaseMiddleware { */ function _beforePauseSubnetwork( uint96 subnetwork - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before unpausing a subnetwork @@ -77,7 +77,7 @@ abstract contract Subnetworks is BaseMiddleware { */ function _beforeUnpauseSubnetwork( uint96 subnetwork - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before unregistering a subnetwork @@ -85,5 +85,5 @@ abstract contract Subnetworks is BaseMiddleware { */ function _beforeUnregisterSubnetwork( uint96 subnetwork - ) public virtual {} + ) internal virtual {} } diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol index 41bd2f6..bad95d3 100644 --- a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -90,7 +90,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { */ function _beforeUnpauseOperator( address operator - ) public virtual override { + ) internal virtual override { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); if ($.forcePaused[operator]) revert OperatorForcePaused(); super._beforeUnpauseOperator(operator); @@ -102,7 +102,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { */ function _beforeUnregisterOperator( address operator - ) public virtual override { + ) internal virtual override { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); if ($.forcePaused[operator]) revert OperatorForcePaused(); super._beforeUnregisterOperator(operator); @@ -113,7 +113,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @param operator The operator address * @param vault The vault address */ - function _beforeUnpauseOperatorVault(address operator, address vault) public virtual override { + function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual override { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); if ($.forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); super._beforeUnpauseOperatorVault(operator, vault); @@ -124,7 +124,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { * @param operator The operator address * @param vault The vault address */ - function _beforeUnregisterOperatorVault(address operator, address vault) public virtual override { + function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual override { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); if ($.forcePausedVault[operator][vault]) revert OperatorVaultForcePaused(); super._beforeUnregisterOperatorVault(operator, vault); diff --git a/src/middleware/extensions/operators/Operators.sol b/src/middleware/extensions/operators/Operators.sol index 264dffb..80b0331 100644 --- a/src/middleware/extensions/operators/Operators.sol +++ b/src/middleware/extensions/operators/Operators.sol @@ -116,7 +116,7 @@ abstract contract Operators is BaseMiddleware { * @param operator The operator address * @param key The new key */ - function _beforeUpdateOperatorKey(address operator, bytes memory key) public virtual {} + function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} /** * @notice Hook called before registering an operator @@ -124,7 +124,7 @@ abstract contract Operators is BaseMiddleware { * @param key The operator's key * @param vault Optional vault address */ - function _beforeRegisterOperator(address operator, bytes memory key, address vault) public virtual {} + function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} /** * @notice Hook called before unregistering an operator @@ -132,7 +132,7 @@ abstract contract Operators is BaseMiddleware { */ function _beforeUnregisterOperator( address operator - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before pausing an operator @@ -140,7 +140,7 @@ abstract contract Operators is BaseMiddleware { */ function _beforePauseOperator( address operator - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before unpausing an operator @@ -148,33 +148,33 @@ abstract contract Operators is BaseMiddleware { */ function _beforeUnpauseOperator( address operator - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before registering an operator-vault pair * @param operator The operator address * @param vault The vault address */ - function _beforeRegisterOperatorVault(address operator, address vault) public virtual {} + function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} /** * @notice Hook called before unregistering an operator-vault pair * @param operator The operator address * @param vault The vault address */ - function _beforeUnregisterOperatorVault(address operator, address vault) public virtual {} + function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual {} /** * @notice Hook called before pausing an operator-vault pair * @param operator The operator address * @param vault The vault address */ - function _beforePauseOperatorVault(address operator, address vault) public virtual {} + function _beforePauseOperatorVault(address operator, address vault) internal virtual {} /** * @notice Hook called before unpausing an operator-vault pair * @param operator The operator address * @param vault The vault address */ - function _beforeUnpauseOperatorVault(address operator, address vault) public virtual {} + function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual {} } diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/middleware/extensions/operators/SelfRegisterOperators.sol index 5ae9c9d..180f578 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/middleware/extensions/operators/SelfRegisterOperators.sol @@ -336,7 +336,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg * @param key The public key to verify * @param signature The signature to verify */ - function _verifyKey(address operator, bytes memory key, bytes memory signature) public { + function _verifyKey(address operator, bytes memory key, bytes memory signature) internal { if (key.length != 0 && !_verifyKeySignature(operator, key, signature)) { revert InvalidSignature(); } @@ -348,7 +348,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg * @param structHash The hash of the EIP712 struct * @param signature The signature to verify */ - function _verifyEIP712(address operator, bytes32 structHash, bytes memory signature) public view { + function _verifyEIP712(address operator, bytes32 structHash, bytes memory signature) internal view { if (!SignatureChecker.isValidSignatureNow(operator, _hashTypedDataV4(structHash), signature)) { revert InvalidSignature(); } @@ -359,7 +359,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg * @param operator The operator address * @param key The new key */ - function _beforeUpdateOperatorKey(address operator, bytes memory key) public virtual {} + function _beforeUpdateOperatorKey(address operator, bytes memory key) internal virtual {} /** * @notice Hook called before registering an operator @@ -367,7 +367,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg * @param key The operator's key * @param vault Optional vault address */ - function _beforeRegisterOperator(address operator, bytes memory key, address vault) public virtual {} + function _beforeRegisterOperator(address operator, bytes memory key, address vault) internal virtual {} /** * @notice Hook called before unregistering an operator @@ -375,7 +375,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg */ function _beforeUnregisterOperator( address operator - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before pausing an operator @@ -383,7 +383,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg */ function _beforePauseOperator( address operator - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before unpausing an operator @@ -391,33 +391,33 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg */ function _beforeUnpauseOperator( address operator - ) public virtual {} + ) internal virtual {} /** * @notice Hook called before registering an operator vault * @param operator The operator address * @param vault The vault address */ - function _beforeRegisterOperatorVault(address operator, address vault) public virtual {} + function _beforeRegisterOperatorVault(address operator, address vault) internal virtual {} /** * @notice Hook called before unregistering an operator vault * @param operator The operator address * @param vault The vault address */ - function _beforeUnregisterOperatorVault(address operator, address vault) public virtual {} + function _beforeUnregisterOperatorVault(address operator, address vault) internal virtual {} /** * @notice Hook called before pausing an operator vault * @param operator The operator address * @param vault The vault address */ - function _beforePauseOperatorVault(address operator, address vault) public virtual {} + function _beforePauseOperatorVault(address operator, address vault) internal virtual {} /** * @notice Hook called before unpausing an operator vault * @param operator The operator address * @param vault The vault address */ - function _beforeUnpauseOperatorVault(address operator, address vault) public virtual {} + function _beforeUnpauseOperatorVault(address operator, address vault) internal virtual {} } diff --git a/src/middleware/extensions/read-helper/ReadHelper.sol b/src/middleware/extensions/read-helper/ReadHelper.sol deleted file mode 100644 index 2137e09..0000000 --- a/src/middleware/extensions/read-helper/ReadHelper.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; - -contract ReadHelper is Initializable { - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.ReadHelper.readHelperImpl")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant ReadHelperStorageLocation = - 0xeeb01dcf9eb4176c944794e8cb12d9caba4faa2514a692b173894bc3e9135d00; - - function _readHelperImpl() internal view returns (address readHelperImpl_) { - bytes32 location = ReadHelperStorageLocation; - assembly { - readHelperImpl_ := sload(location) - } - } - - function __ReadHelper_init( - address readHelperImpl - ) internal onlyInitializing { - bytes32 location = ReadHelperStorageLocation; - assembly { - sstore(location, readHelperImpl) - } - } - - fallback() external { - (bool success, bytes memory returndata) = _readHelperImpl().delegatecall(msg.data); - bytes memory revertData = abi.encode(success, returndata); - assembly { - revert(add(32, revertData), mload(revertData)) - } - } -} diff --git a/test/OperatorsRegistration.t.sol b/test/OperatorsRegistration.t.sol index 275e24f..ba0fd25 100644 --- a/test/OperatorsRegistration.t.sol +++ b/test/OperatorsRegistration.t.sol @@ -4,12 +4,16 @@ pragma solidity ^0.8.25; import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; import {SimplePosMiddleware} from "../src/examples/simple-pos-network/SimplePosMiddleware.sol"; +import {IBaseMiddleware} from "../src/interfaces/IBaseMiddleware.sol"; + //import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; //import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; // import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {ReadHelper} from "../src/middleware/ReadHelper.sol"; +import "forge-std/console.sol"; //import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; //import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; @@ -35,15 +39,18 @@ contract OperatorsRegistrationTest is POCBaseTest { _deposit(vault2, alice, 1000 ether); _deposit(vault3, alice, 1000 ether); + address readHelper = address(new ReadHelper()); + // Initialize middleware contract middleware = new SimplePosMiddleware( address(network), - address(operatorRegistry), + slashingWindow, address(vaultFactory), + address(operatorRegistry), address(operatorNetworkOptInService), + readHelper, owner, - epochDuration, - slashingWindow + epochDuration ); _registerNetwork(network, address(middleware)); @@ -52,7 +59,7 @@ contract OperatorsRegistrationTest is POCBaseTest { function testOperators() public { address operator = address(0x1337); bytes memory key = hex"0000000000000000000000000000000000000000000000000000000000000005"; - uint256 operatorsLength = middleware.operatorsLength(); + uint256 operatorsLength = IBaseMiddleware(address(middleware)).operatorsLength(); assertEq(operatorsLength, 0, "Operators length should be 0"); // can't register without registration @@ -73,9 +80,9 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.registerOperator(operator, key, address(0)); - (address op, uint48 s, uint48 f) = middleware.operatorWithTimesAt(0); + (address op, uint48 s, uint48 f) = IBaseMiddleware(address(middleware)).operatorWithTimesAt(0); - operatorsLength = middleware.operatorsLength(); + operatorsLength = IBaseMiddleware(address(middleware)).operatorsLength(); assertEq(operatorsLength, 1, "Operators length should be 1"); // can't register twice @@ -83,10 +90,10 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.registerOperator(operator, key, address(0)); // activates on next epoch - address[] memory operators = middleware.activeOperators(); + address[] memory operators = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(operators.length, 0, "1 Active operators length should be 0"); skipEpoch(); - operators = middleware.activeOperators(); + operators = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(operators.length, 1, "2 Active operators length should be 1"); // pause @@ -97,7 +104,7 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.pauseOperator(operator); // pause applies on next epoch - operators = middleware.activeOperators(); + operators = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(operators.length, 1, "3 Active operators length should be 1"); // can't unpause right now, minumum one epoch before immutable period passed @@ -106,20 +113,20 @@ contract OperatorsRegistrationTest is POCBaseTest { skipImmutablePeriod(); skipImmutablePeriod(); - operators = middleware.activeOperators(); + operators = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(operators.length, 0, "4 Active operators length should be 0"); - (op, s, f) = middleware.operatorWithTimesAt(0); + (op, s, f) = IBaseMiddleware(address(middleware)).operatorWithTimesAt(0); // unpause middleware.unpauseOperator(operator); - (op, s, f) = middleware.operatorWithTimesAt(0); + (op, s, f) = IBaseMiddleware(address(middleware)).operatorWithTimesAt(0); // unpause applies on next epoch - operators = middleware.activeOperators(); + operators = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(operators.length, 0, "5 Active operators length should be 0"); skipEpoch(); - operators = middleware.activeOperators(); + operators = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(operators.length, 1, "6 Active operators length should be 1"); // pause and unregister @@ -134,7 +141,7 @@ contract OperatorsRegistrationTest is POCBaseTest { skipEpoch(); middleware.unregisterOperator(operator); - operatorsLength = middleware.operatorsLength(); + operatorsLength = IBaseMiddleware(address(middleware)).operatorsLength(); assertEq(operatorsLength, 0, "7 Operators length should be 0"); } @@ -163,34 +170,43 @@ contract OperatorsRegistrationTest is POCBaseTest { skipEpoch(); // Verify all operators are active - address[] memory activeOps = middleware.activeOperators(); + address[] memory activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 3, "Should have 3 active operators"); // Test complex pause/unpause sequence // Pause operator 0 middleware.pauseOperator(operators[0]); skipEpoch(); - activeOps = middleware.activeOperators(); + activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should have 2 active operators after pause"); // Pause operator 1 middleware.pauseOperator(operators[1]); skipEpoch(); - activeOps = middleware.activeOperators(); + activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 1, "Should have 1 active operator after second pause"); // Wait for immutable period and try to unpause operator 0 skipImmutablePeriod(); middleware.unpauseOperator(operators[0]); skipEpoch(); - activeOps = middleware.activeOperators(); + activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should have 2 active operators after unpause"); // Test operator was active at specific timestamps - uint48 currentTimestamp = middleware.getCaptureTimestamp(); - assertTrue(middleware.operatorWasActiveAt(currentTimestamp, operators[0]), "Operator 0 should be active"); - assertFalse(middleware.operatorWasActiveAt(currentTimestamp, operators[1]), "Operator 1 should be inactive"); - assertTrue(middleware.operatorWasActiveAt(currentTimestamp, operators[2]), "Operator 2 should be active"); + uint48 currentTimestamp = IBaseMiddleware(address(middleware)).getCaptureTimestamp(); + assertTrue( + IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[0]), + "Operator 0 should be active" + ); + assertFalse( + IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[1]), + "Operator 1 should be inactive" + ); + assertTrue( + IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[2]), + "Operator 2 should be active" + ); // Test unregistration with active and inactive operators vm.expectRevert(); @@ -200,8 +216,8 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.unregisterOperator(operators[1]); // Should succeed - operator is paused // Verify final state - assertEq(middleware.operatorsLength(), 2, "Should have 2 operators remaining"); - activeOps = middleware.activeOperators(); + assertEq(IBaseMiddleware(address(middleware)).operatorsLength(), 2, "Should have 2 operators remaining"); + activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should still have 2 active operators"); } @@ -219,17 +235,21 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate operator skipEpoch(); - assertEq(middleware.activeOperators().length, 1, "Should have 1 active operator"); + assertEq(IBaseMiddleware(address(middleware)).activeOperators().length, 1, "Should have 1 active operator"); // Pause operator middleware.pauseOperator(operator); skipEpoch(); - assertEq(middleware.activeOperators().length, 0, "Should have 0 active operators after pause"); + assertEq( + IBaseMiddleware(address(middleware)).activeOperators().length, + 0, + "Should have 0 active operators after pause" + ); // Wait for immutable period and unregister skipImmutablePeriod(); middleware.unregisterOperator(operator); - assertEq(middleware.operatorsLength(), 0, "Should have 0 operators after unregister"); + assertEq(IBaseMiddleware(address(middleware)).operatorsLength(), 0, "Should have 0 operators after unregister"); // Register same operator again bytes memory keyNew = hex"0000000000000000000000000000000000000000000000000000000000001112"; @@ -237,9 +257,15 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate operator skipEpoch(); - assertEq(middleware.activeOperators().length, 1, "Should have 1 active operator after reregistration"); + assertEq( + IBaseMiddleware(address(middleware)).activeOperators().length, + 1, + "Should have 1 active operator after reregistration" + ); assertTrue( - middleware.operatorWasActiveAt(middleware.getCaptureTimestamp(), operator), + IBaseMiddleware(address(middleware)).operatorWasActiveAt( + IBaseMiddleware(address(middleware)).getCaptureTimestamp(), operator + ), "Operator should be active after reregistration" ); } @@ -257,11 +283,19 @@ contract OperatorsRegistrationTest is POCBaseTest { // Register operator just before epoch boundary middleware.registerOperator(operator, key, address(0)); vm.warp(middleware.getEpochStart(1) - 1); - assertEq(middleware.activeOperators().length, 0, "Should have no active operators before epoch"); + assertEq( + IBaseMiddleware(address(middleware)).activeOperators().length, + 0, + "Should have no active operators before epoch" + ); // Check right at epoch boundary vm.warp(middleware.getEpochStart(1)); - assertEq(middleware.activeOperators().length, 1, "Should have 1 active operator at epoch start"); + assertEq( + IBaseMiddleware(address(middleware)).activeOperators().length, + 1, + "Should have 1 active operator at epoch start" + ); // Test pause timing edge cases middleware.pauseOperator(operator); @@ -283,13 +317,15 @@ contract OperatorsRegistrationTest is POCBaseTest { vm.warp(currentEpochStart - 1); middleware.pauseOperator(operator); assertTrue( - middleware.operatorWasActiveAt(currentEpochStart - 1, operator), "Operator should be active before pause" + IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentEpochStart - 1, operator), + "Operator should be active before pause" ); // Check status at capture timestamp vm.warp(currentEpochStart); assertFalse( - middleware.operatorWasActiveAt(currentEpochStart, operator), "Operator should be inactive at capture" + IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentEpochStart, operator), + "Operator should be inactive at capture" ); // Test unregister timing @@ -340,13 +376,13 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.registerOperator(operator2, key2, address(0)); // At this point, operator1 should be active, operator2 pending - address[] memory activeOps = middleware.activeOperators(); + address[] memory activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 1, "Should have 1 active operator"); assertEq(activeOps[0], operator1, "Active operator should be operator1"); // Skip epoch to activate operator2 skipEpoch(); - activeOps = middleware.activeOperators(); + activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should have 2 active operators"); // Pause operator1, wait partial immutable period, pause operator2 @@ -367,13 +403,13 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate both operators skipEpoch(); - activeOps = middleware.activeOperators(); + activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should have 2 active operators again"); // Test historical activity - uint48 timestamp = middleware.getCaptureTimestamp(); - assertTrue(middleware.operatorWasActiveAt(timestamp, operator1)); - assertTrue(middleware.operatorWasActiveAt(timestamp, operator2)); + uint48 timestamp = IBaseMiddleware(address(middleware)).getCaptureTimestamp(); + assertTrue(IBaseMiddleware(address(middleware)).operatorWasActiveAt(timestamp, operator1)); + assertTrue(IBaseMiddleware(address(middleware)).operatorWasActiveAt(timestamp, operator2)); // Pause both operators again but unregister at different times middleware.pauseOperator(operator1); @@ -388,8 +424,8 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.unregisterOperator(operator2); // Verify final state - assertEq(middleware.operatorsLength(), 0, "Should have no operators"); - activeOps = middleware.activeOperators(); + assertEq(IBaseMiddleware(address(middleware)).operatorsLength(), 0, "Should have no operators"); + activeOps = IBaseMiddleware(address(middleware)).activeOperators(); assertEq(activeOps.length, 0, "Should have no active operators"); } diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol index d587bbb..74ff1dd 100644 --- a/test/SigTests.t.sol +++ b/test/SigTests.t.sol @@ -8,6 +8,9 @@ import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IVaultConfigurator} from "@symbiotic/interfaces/IVaultConfigurator.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {IBaseMiddleware} from "../src/interfaces/IBaseMiddleware.sol"; + +import {ReadHelper} from "../src/middleware/ReadHelper.sol"; import {SelfRegisterMiddleware} from "../src/examples/self-register-network/SelfRegisterMiddleware.sol"; import {SelfRegisterEd25519Middleware} from "../src/examples/self-register-network/SelfRegisterEd25519Middleware.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -39,21 +42,25 @@ contract SigTests is POCBaseTest { string memory json = vm.readFile(ED25519_TEST_DATA); ed25519Operator = abi.decode(vm.parseJson(json, ".operator"), (address)); + address readHelper = address(new ReadHelper()); + // Initialize both middlewares middleware = new SelfRegisterMiddleware( address(0x123), - address(operatorRegistry), + 1200, // slashing window address(vaultFactory), + address(operatorRegistry), address(operatorNetworkOptInService), - 1200 // slashing window + readHelper ); ed25519Middleware = new SelfRegisterEd25519Middleware( address(0x456), - address(operatorRegistry), + 1200, // slashing window address(vaultFactory), + address(operatorRegistry), address(operatorNetworkOptInService), - 1200 // slashing window + readHelper ); _registerNetwork(address(0x123), address(middleware)); @@ -80,11 +87,13 @@ contract SigTests is POCBaseTest { ed25519Middleware.registerOperator(abi.encode(key), address(vaultEd), signature); // Verify operator is registered correctly - assertTrue(ed25519Middleware.isOperatorRegistered(ed25519Operator)); + assertTrue(IBaseMiddleware(address(ed25519Middleware)).isOperatorRegistered(ed25519Operator)); - assertEq(abi.decode(ed25519Middleware.operatorKey(ed25519Operator), (bytes32)), bytes32(0)); + assertEq( + abi.decode(IBaseMiddleware(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), bytes32(0) + ); vm.warp(block.timestamp + 2); - assertEq(abi.decode(ed25519Middleware.operatorKey(ed25519Operator), (bytes32)), key); + assertEq(abi.decode(IBaseMiddleware(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), key); } function testEd25519RegisterOperatorInvalidSignature() public { @@ -130,11 +139,11 @@ contract SigTests is POCBaseTest { middleware.registerOperator(abi.encode(operatorPublicKey), address(vault), signature); // Verify operator is registered correctly - assertTrue(middleware.isOperatorRegistered(operator)); + assertTrue(IBaseMiddleware(address(middleware)).isOperatorRegistered(operator)); - assertEq(abi.decode(middleware.operatorKey(operator), (bytes32)), bytes32(0)); + assertEq(abi.decode(IBaseMiddleware(address(middleware)).operatorKey(operator), (bytes32)), bytes32(0)); vm.warp(vm.getBlockTimestamp() + 100); - assertEq(abi.decode(middleware.operatorKey(operator), (bytes32)), operatorPublicKey); + assertEq(abi.decode(IBaseMiddleware(address(middleware)).operatorKey(operator), (bytes32)), operatorPublicKey); } function testSelxfRegisterOperatorInvalidSignature() public { From 3957a6167dbc9d5837a8c58b1756b739e7e0b61c Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 6 Dec 2024 17:27:41 +0400 Subject: [PATCH 093/115] refactor: restructure managers --- .../SelfRegisterEd25519Middleware.sol | 22 ++---- .../SelfRegisterMiddleware.sol | 22 ++---- .../SimplePosMiddleware.sol | 12 +-- .../sqrt-task-network/SqrtTaskMiddleware.sol | 13 ++-- .../extensions/SharedVaults.sol | 2 +- .../extensions/Subnetworks.sol | 2 +- .../managers}/access/NoAccessManager.sol | 2 +- .../managers}/access/OwnableAccessManager.sol | 6 +- .../managers}/access/OzAccessControl.sol | 2 +- .../managers}/access/OzAccessManaged.sol | 2 +- .../capture-timestamps/EpochCapture.sol | 2 +- .../capture-timestamps/TimestampCapture.sol | 2 +- .../managers}/keys/KeyManager256.sol | 2 +- .../managers}/keys/KeyManagerBytes.sol | 2 +- .../managers}/keys/NoKeyManager.sol | 2 +- .../managers}/sigs/ECDSASig.sol | 2 +- .../managers}/sigs/EdDSASig.sol | 2 +- .../stake-powers/EqualStakePower.sol | 2 +- .../ForcePauseSelfRegisterOperators.sol | 0 .../extensions/operators/Operators.sol | 2 +- .../operators/SelfRegisterOperators.sol | 4 +- src/managers/NetworkManager.sol | 34 +++++++++ src/managers/{base => }/OperatorManager.sol | 49 ++++++++++-- src/managers/SlashingWindowManager.sol | 24 ++++++ src/managers/{base => }/VaultManager.sol | 59 ++++++++++---- src/managers/base/BaseManager.sol | 76 ------------------- .../{base => extendable}/AccessManager.sol | 0 .../CaptureTimestampManager.sol | 0 .../{base => extendable}/KeyManager.sol | 5 +- .../{base => extendable}/SigManager.sol | 0 .../StakePowerManager.sol | 0 src/middleware/BaseMiddleware.sol | 18 +++-- ...eadHelper.sol => BaseMiddlewareReader.sol} | 29 ++++--- test/OperatorsRegistration.t.sol | 4 +- test/SigTests.t.sol | 4 +- 35 files changed, 222 insertions(+), 187 deletions(-) rename src/{middleware => }/extensions/SharedVaults.sol (97%) rename src/{middleware => }/extensions/Subnetworks.sol (97%) rename src/{managers/extensions => extensions/managers}/access/NoAccessManager.sol (88%) rename src/{managers/extensions => extensions/managers}/access/OwnableAccessManager.sol (91%) rename src/{managers/extensions => extensions/managers}/access/OzAccessControl.sol (98%) rename src/{managers/extensions => extensions/managers}/access/OzAccessManaged.sol (93%) rename src/{managers/extensions => extensions/managers}/capture-timestamps/EpochCapture.sol (95%) rename src/{managers/extensions => extensions/managers}/capture-timestamps/TimestampCapture.sol (87%) rename src/{managers/extensions => extensions/managers}/keys/KeyManager256.sol (98%) rename src/{managers/extensions => extensions/managers}/keys/KeyManagerBytes.sol (98%) rename src/{managers/extensions => extensions/managers}/keys/NoKeyManager.sol (95%) rename src/{managers/extensions => extensions/managers}/sigs/ECDSASig.sol (96%) rename src/{managers/extensions => extensions/managers}/sigs/EdDSASig.sol (96%) rename src/{managers/extensions => extensions/managers}/stake-powers/EqualStakePower.sol (88%) rename src/{middleware => }/extensions/operators/ForcePauseSelfRegisterOperators.sol (100%) rename src/{middleware => }/extensions/operators/Operators.sol (98%) rename src/{middleware => }/extensions/operators/SelfRegisterOperators.sol (99%) create mode 100644 src/managers/NetworkManager.sol rename src/managers/{base => }/OperatorManager.sol (73%) create mode 100644 src/managers/SlashingWindowManager.sol rename src/managers/{base => }/VaultManager.sol (93%) delete mode 100644 src/managers/base/BaseManager.sol rename src/managers/{base => extendable}/AccessManager.sol (100%) rename src/managers/{base => extendable}/CaptureTimestampManager.sol (100%) rename src/managers/{base => extendable}/KeyManager.sol (86%) rename src/managers/{base => extendable}/SigManager.sol (100%) rename src/managers/{base => extendable}/StakePowerManager.sol (100%) rename src/middleware/{ReadHelper.sol => BaseMiddlewareReader.sol} (92%) diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index 04eeab6..3607f15 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -1,23 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; -import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; -import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; +import {SharedVaults} from "../../extensions/SharedVaults.sol"; +import {SelfRegisterOperators} from "../../extensions/operators/SelfRegisterOperators.sol"; -import {NoAccessManager} from "../../managers/extensions/access/NoAccessManager.sol"; -import {TimestampCapture} from "../../managers/extensions/capture-timestamps/TimestampCapture.sol"; -import {EqualStakePower} from "../../managers/extensions/stake-powers/EqualStakePower.sol"; -import {KeyManager256} from "../../managers/extensions/keys/KeyManager256.sol"; -import {EdDSASig} from "../../managers/extensions/sigs/EdDSASig.sol"; +import {NoAccessManager} from "../../extensions/managers/access/NoAccessManager.sol"; +import {TimestampCapture} from "../../extensions/managers/capture-timestamps/TimestampCapture.sol"; +import {EqualStakePower} from "../../extensions/managers/stake-powers/EqualStakePower.sol"; +import {KeyManager256} from "../../extensions/managers/keys/KeyManager256.sol"; +import {EdDSASig} from "../../extensions/managers/sigs/EdDSASig.sol"; contract SelfRegisterEd25519Middleware is SharedVaults, diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index bf02a6b..38c95bb 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -1,23 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; - -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; -import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; -import {SelfRegisterOperators} from "../../middleware/extensions/operators/SelfRegisterOperators.sol"; +import {SharedVaults} from "../../extensions/SharedVaults.sol"; +import {SelfRegisterOperators} from "../../extensions/operators/SelfRegisterOperators.sol"; -import {ECDSASig} from "../../managers/extensions/sigs/ECDSASig.sol"; -import {NoAccessManager} from "../../managers/extensions/access/NoAccessManager.sol"; -import {TimestampCapture} from "../../managers/extensions/capture-timestamps/TimestampCapture.sol"; -import {EqualStakePower} from "../../managers/extensions/stake-powers/EqualStakePower.sol"; -import {KeyManager256} from "../../managers/extensions/keys/KeyManager256.sol"; +import {ECDSASig} from "../../extensions/managers/sigs/ECDSASig.sol"; +import {NoAccessManager} from "../../extensions/managers/access/NoAccessManager.sol"; +import {TimestampCapture} from "../../extensions/managers/capture-timestamps/TimestampCapture.sol"; +import {EqualStakePower} from "../../extensions/managers/stake-powers/EqualStakePower.sol"; +import {KeyManager256} from "../../extensions/managers/keys/KeyManager256.sol"; contract SelfRegisterMiddleware is SharedVaults, diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index d61d0cd..4435c35 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -8,13 +8,13 @@ import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; -import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; -import {Operators} from "../../middleware/extensions/operators/Operators.sol"; +import {SharedVaults} from "../../extensions/SharedVaults.sol"; +import {Operators} from "../../extensions/operators/Operators.sol"; -import {OwnableAccessManager} from "../../managers/extensions/access/OwnableAccessManager.sol"; -import {EpochCapture} from "../../managers/extensions/capture-timestamps/EpochCapture.sol"; -import {KeyManager256} from "../../managers/extensions/keys/KeyManager256.sol"; -import {EqualStakePower} from "../../managers/extensions/stake-powers/EqualStakePower.sol"; +import {OwnableAccessManager} from "../../extensions/managers/access/OwnableAccessManager.sol"; +import {EpochCapture} from "../../extensions/managers/capture-timestamps/EpochCapture.sol"; +import {KeyManager256} from "../../extensions/managers/keys/KeyManager256.sol"; +import {EqualStakePower} from "../../extensions/managers/stake-powers/EqualStakePower.sol"; contract SimplePosMiddleware is SharedVaults, diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 78721a2..9fe53f1 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -5,19 +5,18 @@ import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; -import {SharedVaults} from "../../middleware/extensions/SharedVaults.sol"; -import {Operators} from "../../middleware/extensions/operators/Operators.sol"; +import {SharedVaults} from "../../extensions/SharedVaults.sol"; +import {Operators} from "../../extensions/operators/Operators.sol"; -import {OwnableAccessManager} from "../../managers/extensions/access/OwnableAccessManager.sol"; -import {NoKeyManager} from "../../managers/extensions/keys/NoKeyManager.sol"; -import {TimestampCapture} from "../../managers/extensions/capture-timestamps/TimestampCapture.sol"; -import {EqualStakePower} from "../../managers/extensions/stake-powers/EqualStakePower.sol"; +import {OwnableAccessManager} from "../../extensions/managers/access/OwnableAccessManager.sol"; +import {NoKeyManager} from "../../extensions/managers/keys/NoKeyManager.sol"; +import {TimestampCapture} from "../../extensions/managers/capture-timestamps/TimestampCapture.sol"; +import {EqualStakePower} from "../../extensions/managers/stake-powers/EqualStakePower.sol"; contract SqrtTaskMiddleware is SharedVaults, diff --git a/src/middleware/extensions/SharedVaults.sol b/src/extensions/SharedVaults.sol similarity index 97% rename from src/middleware/extensions/SharedVaults.sol rename to src/extensions/SharedVaults.sol index 9afffcc..b8f698d 100644 --- a/src/middleware/extensions/SharedVaults.sol +++ b/src/extensions/SharedVaults.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseMiddleware} from "../middleware/BaseMiddleware.sol"; /** * @title SharedVaults diff --git a/src/middleware/extensions/Subnetworks.sol b/src/extensions/Subnetworks.sol similarity index 97% rename from src/middleware/extensions/Subnetworks.sol rename to src/extensions/Subnetworks.sol index d28bc1e..0b85733 100644 --- a/src/middleware/extensions/Subnetworks.sol +++ b/src/extensions/Subnetworks.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../BaseMiddleware.sol"; +import {BaseMiddleware} from "../middleware/BaseMiddleware.sol"; /** * @title Subnetworks diff --git a/src/managers/extensions/access/NoAccessManager.sol b/src/extensions/managers/access/NoAccessManager.sol similarity index 88% rename from src/managers/extensions/access/NoAccessManager.sol rename to src/extensions/managers/access/NoAccessManager.sol index 4cff60f..a541bff 100644 --- a/src/managers/extensions/access/NoAccessManager.sol +++ b/src/extensions/managers/access/NoAccessManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {AccessManager} from "../../base/AccessManager.sol"; +import {AccessManager} from "../../../managers/extendable/AccessManager.sol"; /** * @title NoAccessManager diff --git a/src/managers/extensions/access/OwnableAccessManager.sol b/src/extensions/managers/access/OwnableAccessManager.sol similarity index 91% rename from src/managers/extensions/access/OwnableAccessManager.sol rename to src/extensions/managers/access/OwnableAccessManager.sol index 139c0b0..651c318 100644 --- a/src/managers/extensions/access/OwnableAccessManager.sol +++ b/src/extensions/managers/access/OwnableAccessManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {AccessManager} from "../../base/AccessManager.sol"; +import {AccessManager} from "../../../managers/extendable/AccessManager.sol"; /** * @title OwnableAccessManager @@ -23,9 +23,9 @@ abstract contract OwnableAccessManager is AccessManager { */ error InvalidOwner(address owner); - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.OwnableAccessManager.owner")) - 1)) & ~bytes32(uint256(0xff)) + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.OwnableAccessManager")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant OwnableAccessManagerStorageLocation = - 0xeeb01dcf9eb4176c944794e8cb12d9caba4faa2514a692b173894bc3e9135d00; + 0xcee92923a0c63eca6fc0402d78c9efde9f9f3dc73e6f9e14501bf734ed77f100; function _owner() private view returns (address owner_) { bytes32 location = OwnableAccessManagerStorageLocation; diff --git a/src/managers/extensions/access/OzAccessControl.sol b/src/extensions/managers/access/OzAccessControl.sol similarity index 98% rename from src/managers/extensions/access/OzAccessControl.sol rename to src/extensions/managers/access/OzAccessControl.sol index 666356f..10a0d07 100644 --- a/src/managers/extensions/access/OzAccessControl.sol +++ b/src/extensions/managers/access/OzAccessControl.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {AccessManager} from "../../base/AccessManager.sol"; +import {AccessManager} from "../../../managers/extendable/AccessManager.sol"; /** * @title OzAccessControl diff --git a/src/managers/extensions/access/OzAccessManaged.sol b/src/extensions/managers/access/OzAccessManaged.sol similarity index 93% rename from src/managers/extensions/access/OzAccessManaged.sol rename to src/extensions/managers/access/OzAccessManaged.sol index ae44cee..b4540c2 100644 --- a/src/managers/extensions/access/OzAccessManaged.sol +++ b/src/extensions/managers/access/OzAccessManaged.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.25; import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; -import {AccessManager} from "../../base/AccessManager.sol"; +import {AccessManager} from "../../../managers/extendable/AccessManager.sol"; /** * @title OzAccessManaged diff --git a/src/managers/extensions/capture-timestamps/EpochCapture.sol b/src/extensions/managers/capture-timestamps/EpochCapture.sol similarity index 95% rename from src/managers/extensions/capture-timestamps/EpochCapture.sol rename to src/extensions/managers/capture-timestamps/EpochCapture.sol index dcee6bc..0fd9a8d 100644 --- a/src/managers/extensions/capture-timestamps/EpochCapture.sol +++ b/src/extensions/managers/capture-timestamps/EpochCapture.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {CaptureTimestampManager} from "../../base/CaptureTimestampManager.sol"; +import {CaptureTimestampManager} from "../../../managers/extendable/CaptureTimestampManager.sol"; /** * @title EpochCapture diff --git a/src/managers/extensions/capture-timestamps/TimestampCapture.sol b/src/extensions/managers/capture-timestamps/TimestampCapture.sol similarity index 87% rename from src/managers/extensions/capture-timestamps/TimestampCapture.sol rename to src/extensions/managers/capture-timestamps/TimestampCapture.sol index 4aa6f39..aaf921e 100644 --- a/src/managers/extensions/capture-timestamps/TimestampCapture.sol +++ b/src/extensions/managers/capture-timestamps/TimestampCapture.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {CaptureTimestampManager} from "../../base/CaptureTimestampManager.sol"; +import {CaptureTimestampManager} from "../../../managers/extendable/CaptureTimestampManager.sol"; /** * @title TimestampCapture diff --git a/src/managers/extensions/keys/KeyManager256.sol b/src/extensions/managers/keys/KeyManager256.sol similarity index 98% rename from src/managers/extensions/keys/KeyManager256.sol rename to src/extensions/managers/keys/KeyManager256.sol index 86fe56b..9de8690 100644 --- a/src/managers/extensions/keys/KeyManager256.sol +++ b/src/extensions/managers/keys/KeyManager256.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {KeyManager} from "../../base/KeyManager.sol"; +import {KeyManager} from "../../../managers/extendable/KeyManager.sol"; import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet.sol"; /** diff --git a/src/managers/extensions/keys/KeyManagerBytes.sol b/src/extensions/managers/keys/KeyManagerBytes.sol similarity index 98% rename from src/managers/extensions/keys/KeyManagerBytes.sol rename to src/extensions/managers/keys/KeyManagerBytes.sol index 7de6588..abed3cc 100644 --- a/src/managers/extensions/keys/KeyManagerBytes.sol +++ b/src/extensions/managers/keys/KeyManagerBytes.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {KeyManager} from "../../base/KeyManager.sol"; +import {KeyManager} from "../../../managers/extendable/KeyManager.sol"; import {PauseableEnumerableSet} from "../../../libraries/PauseableEnumerableSet.sol"; /** diff --git a/src/managers/extensions/keys/NoKeyManager.sol b/src/extensions/managers/keys/NoKeyManager.sol similarity index 95% rename from src/managers/extensions/keys/NoKeyManager.sol rename to src/extensions/managers/keys/NoKeyManager.sol index 99e41a8..87b311f 100644 --- a/src/managers/extensions/keys/NoKeyManager.sol +++ b/src/extensions/managers/keys/NoKeyManager.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {KeyManager} from "../../base/KeyManager.sol"; +import {KeyManager} from "../../../managers/extendable/KeyManager.sol"; /** * @title NoKeyManager diff --git a/src/managers/extensions/sigs/ECDSASig.sol b/src/extensions/managers/sigs/ECDSASig.sol similarity index 96% rename from src/managers/extensions/sigs/ECDSASig.sol rename to src/extensions/managers/sigs/ECDSASig.sol index 29812c5..67941d8 100644 --- a/src/managers/extensions/sigs/ECDSASig.sol +++ b/src/extensions/managers/sigs/ECDSASig.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {SigManager} from "../../base/SigManager.sol"; +import {SigManager} from "../../../managers/extendable/SigManager.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; /** diff --git a/src/managers/extensions/sigs/EdDSASig.sol b/src/extensions/managers/sigs/EdDSASig.sol similarity index 96% rename from src/managers/extensions/sigs/EdDSASig.sol rename to src/extensions/managers/sigs/EdDSASig.sol index 644d63c..9dee07b 100644 --- a/src/managers/extensions/sigs/EdDSASig.sol +++ b/src/extensions/managers/sigs/EdDSASig.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.25; import {EdDSA} from "../../../libraries/EdDSA.sol"; -import {SigManager} from "../../base/SigManager.sol"; +import {SigManager} from "../../../managers/extendable/SigManager.sol"; /** * @title EdDSASig diff --git a/src/managers/extensions/stake-powers/EqualStakePower.sol b/src/extensions/managers/stake-powers/EqualStakePower.sol similarity index 88% rename from src/managers/extensions/stake-powers/EqualStakePower.sol rename to src/extensions/managers/stake-powers/EqualStakePower.sol index 3899d52..7402865 100644 --- a/src/managers/extensions/stake-powers/EqualStakePower.sol +++ b/src/extensions/managers/stake-powers/EqualStakePower.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {StakePowerManager} from "../../base/StakePowerManager.sol"; +import {StakePowerManager} from "../../../managers/extendable/StakePowerManager.sol"; /** * @title EqualStakePower diff --git a/src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/extensions/operators/ForcePauseSelfRegisterOperators.sol similarity index 100% rename from src/middleware/extensions/operators/ForcePauseSelfRegisterOperators.sol rename to src/extensions/operators/ForcePauseSelfRegisterOperators.sol diff --git a/src/middleware/extensions/operators/Operators.sol b/src/extensions/operators/Operators.sol similarity index 98% rename from src/middleware/extensions/operators/Operators.sol rename to src/extensions/operators/Operators.sol index 80b0331..9db7487 100644 --- a/src/middleware/extensions/operators/Operators.sol +++ b/src/extensions/operators/Operators.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; +import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; /** * @title Operators diff --git a/src/middleware/extensions/operators/SelfRegisterOperators.sol b/src/extensions/operators/SelfRegisterOperators.sol similarity index 99% rename from src/middleware/extensions/operators/SelfRegisterOperators.sol rename to src/extensions/operators/SelfRegisterOperators.sol index 180f578..841e6dd 100644 --- a/src/middleware/extensions/operators/SelfRegisterOperators.sol +++ b/src/extensions/operators/SelfRegisterOperators.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseMiddleware} from "../../BaseMiddleware.sol"; -import {SigManager} from "../../../managers/base/SigManager.sol"; +import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {SigManager} from "../../managers/extendable/SigManager.sol"; import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; diff --git a/src/managers/NetworkManager.sol b/src/managers/NetworkManager.sol new file mode 100644 index 0000000..5a63c64 --- /dev/null +++ b/src/managers/NetworkManager.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +abstract contract NetworkManager is Initializable { + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.NetworkManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant NetworkManagerStorageLocation = + 0x779150488f5e984d1f840ba606e388ada6c73b44f261274c3595c61a30023e00; + + /** + * @notice Initializes the NetworkManager contract + * @param network The address of the network + */ + function __NetworkManager_init_private( + address network + ) internal onlyInitializing { + assembly { + sstore(NetworkManagerStorageLocation, network) + } + } + + /** + * @notice Returns the address of the network + * @return network The address of the network + */ + function _NETWORK() internal view returns (address) { + address network; + assembly { + network := sload(NetworkManagerStorageLocation) + } + return network; + } +} diff --git a/src/managers/base/OperatorManager.sol b/src/managers/OperatorManager.sol similarity index 73% rename from src/managers/base/OperatorManager.sol rename to src/managers/OperatorManager.sol index 334670d..e1d04b5 100644 --- a/src/managers/base/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -4,13 +4,14 @@ pragma solidity ^0.8.25; import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {NetworkManager} from "./NetworkManager.sol"; +import {SlashingWindowManager} from "./SlashingWindowManager.sol"; -import {BaseManager} from "./BaseManager.sol"; -import {PauseableEnumerableSet} from "../../libraries/PauseableEnumerableSet.sol"; +import {CaptureTimestampManager} from "./extendable/CaptureTimestampManager.sol"; -abstract contract OperatorManager is BaseManager { +import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; + +abstract contract OperatorManager is NetworkManager, SlashingWindowManager, CaptureTimestampManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; error NotOperator(); @@ -18,6 +19,8 @@ abstract contract OperatorManager is BaseManager { /// @custom:storage-location erc7201:symbiotic.storage.OperatorManager struct OperatorManagerStorage { + address _operatorRegistry; // Address of the operator registry + address _operatorNetOptin; // Address of the operator network opt-in service PauseableEnumerableSet.AddressSet _operators; } @@ -25,12 +28,48 @@ abstract contract OperatorManager is BaseManager { bytes32 private constant OperatorManagerStorageLocation = 0x3b2b549db680c436ebf9aa3c8eeee850852f16da5cdb5137dbc0299ebb219e00; + /** + * @notice Gets the storage pointer for OperatorManager state + * @return $ Storage pointer to OperatorManagerStorage struct + */ function _getOperatorManagerStorage() internal pure returns (OperatorManagerStorage storage $) { assembly { $.slot := OperatorManagerStorageLocation } } + /** + * @notice Initializes the OperatorManager with required parameters + * @param operatorRegistry The address of the operator registry contract + * @param operatorNetOptin The address of the operator network opt-in service + */ + function __OperatorManager_init_private( + address operatorRegistry, + address operatorNetOptin + ) internal onlyInitializing { + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + $._operatorRegistry = operatorRegistry; + $._operatorNetOptin = operatorNetOptin; + } + + /** + * @notice Gets the address of the operator registry contract + * @return The operator registry contract address + */ + function _OPERATOR_REGISTRY() internal view returns (address) { + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operatorRegistry; + } + + /** + * @notice Gets the address of the operator network opt-in service contract + * @return The operator network opt-in service contract address + */ + function _OPERATOR_NET_OPTIN() internal view returns (address) { + OperatorManagerStorage storage $ = _getOperatorManagerStorage(); + return $._operatorNetOptin; + } + /** * @notice Returns the total number of registered operators, including both active and inactive * @return The number of registered operators diff --git a/src/managers/SlashingWindowManager.sol b/src/managers/SlashingWindowManager.sol new file mode 100644 index 0000000..da5e77c --- /dev/null +++ b/src/managers/SlashingWindowManager.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +abstract contract SlashingWindowManager is Initializable { + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.SlashingWindowManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant SlashingWindowManagerStorageLocation = + 0x52becd5b30d67421b1f63b9d90d513daf82b3973912d3edfdac9468c1743c000; + + function __SlashingWindowManager_init_private(uint48 _slashingWindow) internal onlyInitializing { + assembly { + sstore(SlashingWindowManagerStorageLocation, _slashingWindow) + } + } + + function _SLASHING_WINDOW() internal view returns (uint48) { + uint48 slashingWindow; + assembly { + slashingWindow := sload(SlashingWindowManagerStorageLocation) + } + return slashingWindow; + } +} diff --git a/src/managers/base/VaultManager.sol b/src/managers/VaultManager.sol similarity index 93% rename from src/managers/base/VaultManager.sol rename to src/managers/VaultManager.sol index 897c8ab..74ee284 100644 --- a/src/managers/base/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -12,16 +12,20 @@ import {IOperatorSpecificDelegator} from "@symbiotic/interfaces/delegator/IOpera import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import {BaseManager} from "./BaseManager.sol"; -import {StakePowerManager} from "./StakePowerManager.sol"; -import {PauseableEnumerableSet} from "../../libraries/PauseableEnumerableSet.sol"; +import {StakePowerManager} from "./extendable/StakePowerManager.sol"; +import {CaptureTimestampManager} from "./extendable/CaptureTimestampManager.sol"; + +import {NetworkManager} from "./NetworkManager.sol"; +import {SlashingWindowManager} from "./SlashingWindowManager.sol"; + +import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; /** * @title VaultManager * @notice Abstract contract for managing vaults and their relationships with operators and subnetworks * @dev Extends BaseManager and provides functionality for registering, pausing, and managing vaults */ -abstract contract VaultManager is BaseManager, StakePowerManager { +abstract contract VaultManager is NetworkManager, SlashingWindowManager, CaptureTimestampManager, StakePowerManager { using EnumerableMap for EnumerableMap.AddressToUintMap; using EnumerableMap for EnumerableMap.AddressToAddressMap; using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; @@ -42,36 +46,61 @@ abstract contract VaultManager is BaseManager, StakePowerManager { /// @custom:storage-location erc7201:symbiotic.storage.VaultManager struct VaultManagerStorage { + address _vaultRegistry; PauseableEnumerableSet.Uint160Set _subnetworks; PauseableEnumerableSet.AddressSet _sharedVaults; mapping(address => PauseableEnumerableSet.AddressSet) _operatorVaults; EnumerableMap.AddressToAddressMap _vaultOperator; } + /** + * @dev Struct containing information about a slash response + * @param vault The address of the vault being slashed + * @param slasherType The type identifier of the slasher + * @param subnetwork The subnetwork identifier where the slash occurred + * @param response For instant slashing: the slashed amount, for veto slashing: the slash index + */ + struct SlashResponse { + address vault; + uint64 slasherType; + bytes32 subnetwork; + uint256 response; + } + + uint64 internal constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type + uint64 internal constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type + uint64 internal constant OPERATOR_SPECIFIC_DELEGATOR_TYPE = 2; // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.VaultManager")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant VaultManagerStorageLocation = 0x485f0695561726d087d0cb5cf546efed37ef61dfced21455f1ba7eb5e5b3db00; + /** + * @notice Internal helper to access the VaultManager storage struct + * @dev Uses assembly to load storage location from a constant slot + * @return $ Storage pointer to the VaultManagerStorage struct + */ function _getVaultManagerStorage() internal pure returns (VaultManagerStorage storage $) { assembly { $.slot := VaultManagerStorageLocation } } - uint64 internal constant OPERATOR_SPECIFIC_DELEGATOR_TYPE = 2; + /** + * @notice Initializes the VaultManager with required parameters + * @param vaultRegistry The address of the vault registry contract + */ + function __VaultManager_init_private(address vaultRegistry) internal onlyInitializing { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._vaultRegistry = vaultRegistry; + } /** - * @dev Struct containing information about a slash response - * @param vault The address of the vault being slashed - * @param slasherType The type identifier of the slasher - * @param subnetwork The subnetwork identifier where the slash occurred - * @param response For instant slashing: the slashed amount, for veto slashing: the slash index + * @notice Gets the address of the vault registry contract + * @return The vault registry contract address */ - struct SlashResponse { - address vault; - uint64 slasherType; - bytes32 subnetwork; - uint256 response; + function _VAULT_REGISTRY() internal view returns (address) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._vaultRegistry; } /** diff --git a/src/managers/base/BaseManager.sol b/src/managers/base/BaseManager.sol deleted file mode 100644 index ab7b827..0000000 --- a/src/managers/base/BaseManager.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {CaptureTimestampManager} from "./CaptureTimestampManager.sol"; - -abstract contract BaseManager is CaptureTimestampManager { - uint64 internal constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type - uint64 internal constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type - - /// @custom:storage-location erc7201:symbiotic.storage.BaseManager - struct BaseManagerStorage { - address _network; // Address of the network - uint48 _slashingWindow; // Duration of the slashing window - address _vaultRegistry; // Address of the vault registry - address _operatorRegistry; // Address of the operator registry - address _operatorNetOptin; // Address of the operator network opt-in service - } - - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseManager")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant BaseManagerStorageLocation = - 0xb3503c3f5ee7753561129bea19627692ca916ecb48491bfcd223db17a12b8e00; - - function _getBaseManagerStorage() internal pure returns (BaseManagerStorage storage $) { - assembly { - $.slot := BaseManagerStorageLocation - } - } - - /** - * @notice Initializes the BaseManager contract - * @param network The address of the network - * @param slashingWindow The duration of the slashing window - * @param vaultRegistry The address of the vault registry - * @param operatorRegistry The address of the operator registry - * @param operatorNetOptIn The address of the operator network opt-in service - */ - function __BaseManager_init( - address network, - uint48 slashingWindow, - address vaultRegistry, - address operatorRegistry, - address operatorNetOptIn - ) internal onlyInitializing { - BaseManagerStorage storage $ = _getBaseManagerStorage(); - $._network = network; - $._slashingWindow = slashingWindow; - $._vaultRegistry = vaultRegistry; - $._operatorRegistry = operatorRegistry; - $._operatorNetOptin = operatorNetOptIn; - } - - function _NETWORK() internal view returns (address) { - BaseManagerStorage storage $ = _getBaseManagerStorage(); - return $._network; - } - - function _SLASHING_WINDOW() internal view returns (uint48) { - BaseManagerStorage storage $ = _getBaseManagerStorage(); - return $._slashingWindow; - } - - function _VAULT_REGISTRY() internal view returns (address) { - BaseManagerStorage storage $ = _getBaseManagerStorage(); - return $._vaultRegistry; - } - - function _OPERATOR_REGISTRY() internal view returns (address) { - BaseManagerStorage storage $ = _getBaseManagerStorage(); - return $._operatorRegistry; - } - - function _OPERATOR_NET_OPTIN() internal view returns (address) { - BaseManagerStorage storage $ = _getBaseManagerStorage(); - return $._operatorNetOptin; - } -} diff --git a/src/managers/base/AccessManager.sol b/src/managers/extendable/AccessManager.sol similarity index 100% rename from src/managers/base/AccessManager.sol rename to src/managers/extendable/AccessManager.sol diff --git a/src/managers/base/CaptureTimestampManager.sol b/src/managers/extendable/CaptureTimestampManager.sol similarity index 100% rename from src/managers/base/CaptureTimestampManager.sol rename to src/managers/extendable/CaptureTimestampManager.sol diff --git a/src/managers/base/KeyManager.sol b/src/managers/extendable/KeyManager.sol similarity index 86% rename from src/managers/base/KeyManager.sol rename to src/managers/extendable/KeyManager.sol index 6f811b8..813b7cd 100644 --- a/src/managers/base/KeyManager.sol +++ b/src/managers/extendable/KeyManager.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {BaseManager} from "./BaseManager.sol"; +import {SlashingWindowManager} from "../SlashingWindowManager.sol"; +import {CaptureTimestampManager} from "./CaptureTimestampManager.sol"; /** * @title KeyManager * @notice Abstract contract for managing keys */ -abstract contract KeyManager is BaseManager { +abstract contract KeyManager is SlashingWindowManager, CaptureTimestampManager { /** * @notice Updates the key associated with an operator * @param operator The address of the operator diff --git a/src/managers/base/SigManager.sol b/src/managers/extendable/SigManager.sol similarity index 100% rename from src/managers/base/SigManager.sol rename to src/managers/extendable/SigManager.sol diff --git a/src/managers/base/StakePowerManager.sol b/src/managers/extendable/StakePowerManager.sol similarity index 100% rename from src/managers/base/StakePowerManager.sol rename to src/managers/extendable/StakePowerManager.sol diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 983873a..aec105c 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {VaultManager} from "../managers/base/VaultManager.sol"; -import {OperatorManager} from "../managers/base/OperatorManager.sol"; -import {AccessManager} from "../managers/base/AccessManager.sol"; -import {KeyManager} from "../managers/base/KeyManager.sol"; -import "forge-std/console.sol"; +import {VaultManager} from "../managers/VaultManager.sol"; +import {OperatorManager} from "../managers/OperatorManager.sol"; +import {AccessManager} from "../managers/extendable/AccessManager.sol"; +import {KeyManager} from "../managers/extendable/KeyManager.sol"; /** * @title BaseMiddleware @@ -20,9 +19,9 @@ import "forge-std/console.sol"; * management capabilities that can be extended with additional functionality. */ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager { - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseMiddleware.readHelper")) - 1)) & ~bytes32(uint256(0xff)) + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseMiddleware")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant ReadHelperStorageLocation = - 0x19370075337de2141d8d7be7b8e2dab6686d6a69c74729da94b114b78d743b00; + 0xfd87879bc98f37af7578af722aecfbe5843e5ad354da2d1e70cb5157c4ec8800; function __BaseMiddleware_init( address network, @@ -32,7 +31,10 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager address operatorNetOptin, address readHelper ) internal onlyInitializing { - __BaseManager_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin); + __NetworkManager_init_private(network); + __SlashingWindowManager_init_private(slashingWindow); + __VaultManager_init_private(vaultRegistry); + __OperatorManager_init_private(operatorRegistry, operatorNetOptin); assembly { sstore(ReadHelperStorageLocation, readHelper) } diff --git a/src/middleware/ReadHelper.sol b/src/middleware/BaseMiddlewareReader.sol similarity index 92% rename from src/middleware/ReadHelper.sol rename to src/middleware/BaseMiddlewareReader.sol index 947cdbf..e0a25e2 100644 --- a/src/middleware/ReadHelper.sol +++ b/src/middleware/BaseMiddlewareReader.sol @@ -2,31 +2,22 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "./BaseMiddleware.sol"; -import {NoAccessManager} from "../managers/extensions/access/NoAccessManager.sol"; -import {NoKeyManager} from "../managers/extensions/keys/NoKeyManager.sol"; -import "forge-std/console.sol"; +import {NoAccessManager} from "../extensions/managers/access/NoAccessManager.sol"; +import {NoKeyManager} from "../extensions/managers/keys/NoKeyManager.sol"; /** - * @title ReadHelper + * @title BaseMiddlewareReader * @notice A helper contract for view functions that combines core manager functionality * @dev This contract serves as a foundation for building custom middleware by providing essential * management capabilities that can be extended with additional functionality. */ -contract ReadHelper is BaseMiddleware, NoAccessManager, NoKeyManager { +contract BaseMiddlewareReader is BaseMiddleware, NoAccessManager, NoKeyManager { function getCaptureTimestamp() public view override returns (uint48 timestamp) { - address middleware; - assembly { - middleware := shr(96, calldataload(sub(calldatasize(), 20))) - } - return BaseMiddleware(middleware).getCaptureTimestamp(); + return BaseMiddleware(_getMiddleware()).getCaptureTimestamp(); } function stakeToPower(address vault, uint256 stake) public view override returns (uint256 power) { - address middleware; - assembly { - middleware := shr(96, calldataload(sub(calldatasize(), 20))) - } - return BaseMiddleware(middleware).stakeToPower(vault, stake); + return BaseMiddleware(_getMiddleware()).stakeToPower(vault, stake); } function NETWORK() external view returns (address) { @@ -203,4 +194,12 @@ contract ReadHelper is BaseMiddleware, NoAccessManager, NoKeyManager { ) external view returns (uint256) { return _totalPower(operators); } + + function _getMiddleware() private view returns (address) { + address middleware; + assembly { + middleware := shr(96, calldataload(sub(calldatasize(), 20))) + } + return middleware; + } } diff --git a/test/OperatorsRegistration.t.sol b/test/OperatorsRegistration.t.sol index ba0fd25..1e5d86e 100644 --- a/test/OperatorsRegistration.t.sol +++ b/test/OperatorsRegistration.t.sol @@ -12,7 +12,7 @@ import {IBaseMiddleware} from "../src/interfaces/IBaseMiddleware.sol"; import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {ReadHelper} from "../src/middleware/ReadHelper.sol"; +import {BaseMiddlewareReader} from "../src/middleware/BaseMiddlewareReader.sol"; import "forge-std/console.sol"; //import {Slasher} from "@symbiotic/contracts/slasher/Slasher.sol"; //import {VetoSlasher} from "@symbiotic/contracts/slasher/VetoSlasher.sol"; @@ -39,7 +39,7 @@ contract OperatorsRegistrationTest is POCBaseTest { _deposit(vault2, alice, 1000 ether); _deposit(vault3, alice, 1000 ether); - address readHelper = address(new ReadHelper()); + address readHelper = address(new BaseMiddlewareReader()); // Initialize middleware contract middleware = new SimplePosMiddleware( diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol index 74ff1dd..fb37df1 100644 --- a/test/SigTests.t.sol +++ b/test/SigTests.t.sol @@ -10,7 +10,7 @@ import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol import {IBaseMiddleware} from "../src/interfaces/IBaseMiddleware.sol"; -import {ReadHelper} from "../src/middleware/ReadHelper.sol"; +import {BaseMiddlewareReader} from "../src/middleware/BaseMiddlewareReader.sol"; import {SelfRegisterMiddleware} from "../src/examples/self-register-network/SelfRegisterMiddleware.sol"; import {SelfRegisterEd25519Middleware} from "../src/examples/self-register-network/SelfRegisterEd25519Middleware.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -42,7 +42,7 @@ contract SigTests is POCBaseTest { string memory json = vm.readFile(ED25519_TEST_DATA); ed25519Operator = abi.decode(vm.parseJson(json, ".operator"), (address)); - address readHelper = address(new ReadHelper()); + address readHelper = address(new BaseMiddlewareReader()); // Initialize both middlewares middleware = new SelfRegisterMiddleware( From a99aecb6e0d5c35d65fe28d5ec64f92a5b4d75e2 Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 6 Dec 2024 17:28:16 +0400 Subject: [PATCH 094/115] chore: lint --- src/managers/OperatorManager.sol | 2 +- src/managers/SlashingWindowManager.sol | 4 +++- src/managers/VaultManager.sol | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index e1d04b5..9344c21 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -20,7 +20,7 @@ abstract contract OperatorManager is NetworkManager, SlashingWindowManager, Capt /// @custom:storage-location erc7201:symbiotic.storage.OperatorManager struct OperatorManagerStorage { address _operatorRegistry; // Address of the operator registry - address _operatorNetOptin; // Address of the operator network opt-in service + address _operatorNetOptin; // Address of the operator network opt-in service PauseableEnumerableSet.AddressSet _operators; } diff --git a/src/managers/SlashingWindowManager.sol b/src/managers/SlashingWindowManager.sol index da5e77c..36e0903 100644 --- a/src/managers/SlashingWindowManager.sol +++ b/src/managers/SlashingWindowManager.sol @@ -8,7 +8,9 @@ abstract contract SlashingWindowManager is Initializable { bytes32 private constant SlashingWindowManagerStorageLocation = 0x52becd5b30d67421b1f63b9d90d513daf82b3973912d3edfdac9468c1743c000; - function __SlashingWindowManager_init_private(uint48 _slashingWindow) internal onlyInitializing { + function __SlashingWindowManager_init_private( + uint48 _slashingWindow + ) internal onlyInitializing { assembly { sstore(SlashingWindowManagerStorageLocation, _slashingWindow) } diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 74ee284..d8c3f34 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -89,7 +89,9 @@ abstract contract VaultManager is NetworkManager, SlashingWindowManager, Capture * @notice Initializes the VaultManager with required parameters * @param vaultRegistry The address of the vault registry contract */ - function __VaultManager_init_private(address vaultRegistry) internal onlyInitializing { + function __VaultManager_init_private( + address vaultRegistry + ) internal onlyInitializing { VaultManagerStorage storage $ = _getVaultManagerStorage(); $._vaultRegistry = vaultRegistry; } From 93b9ed142fae6942798b082eae42521f9b7b68a4 Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 6 Dec 2024 18:24:08 +0400 Subject: [PATCH 095/115] chore: interfaces and readme --- src/extensions/SharedVaults.sol | 15 +- src/extensions/Subnetworks.sol | 15 +- .../managers/access/OwnableAccessManager.sol | 22 +-- .../managers/access/OzAccessControl.sol | 46 ++---- .../capture-timestamps/EpochCapture.sol | 14 +- src/extensions/managers/sigs/ECDSASig.sol | 11 +- src/extensions/managers/sigs/EdDSASig.sol | 12 +- .../ForcePauseSelfRegisterOperators.sol | 24 +-- src/extensions/operators/Operators.sol | 37 ++--- .../operators/SelfRegisterOperators.sol | 77 +++------ ...ddleware.sol => IBaseMiddlewareReader.sol} | 2 +- src/interfaces/extensions/ISharedVaults.sol | 32 ++++ src/interfaces/extensions/ISubnetworks.sol | 32 ++++ .../managers/access/IOwnableAccessManager.sol | 33 ++++ .../managers/access/IOzAccessControl.sol | 59 +++++++ .../capture-timestamps/IEpochCapture.sol | 22 +++ .../extensions/managers/sigs/IECDSASig.sol | 18 ++ .../extensions/managers/sigs/IEdDSASig.sol | 19 +++ .../IForcePauseSelfRegisterOperators.sol | 39 +++++ .../extensions/operators/IOperators.sol | 69 ++++++++ .../operators/ISelfRegisterOperators.sol | 154 ++++++++++++++++++ src/managers/OperatorManager.sol | 6 +- src/managers/VaultManager.sol | 30 ++-- src/managers/extendable/KeyManager.sol | 4 +- src/managers/extendable/SigManager.sol | 2 +- .../NetworkStorage.sol} | 12 +- .../SlashingWindowStorage.sol} | 12 +- src/middleware/BaseMiddleware.sol | 7 +- test/OperatorsRegistration.t.sol | 82 +++++----- test/SigTests.t.sol | 14 +- 30 files changed, 648 insertions(+), 273 deletions(-) rename src/interfaces/{IBaseMiddleware.sol => IBaseMiddlewareReader.sol} (99%) create mode 100644 src/interfaces/extensions/ISharedVaults.sol create mode 100644 src/interfaces/extensions/ISubnetworks.sol create mode 100644 src/interfaces/extensions/managers/access/IOwnableAccessManager.sol create mode 100644 src/interfaces/extensions/managers/access/IOzAccessControl.sol create mode 100644 src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol create mode 100644 src/interfaces/extensions/managers/sigs/IECDSASig.sol create mode 100644 src/interfaces/extensions/managers/sigs/IEdDSASig.sol create mode 100644 src/interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol create mode 100644 src/interfaces/extensions/operators/IOperators.sol create mode 100644 src/interfaces/extensions/operators/ISelfRegisterOperators.sol rename src/managers/{NetworkManager.sol => storages/NetworkStorage.sol} (70%) rename src/managers/{SlashingWindowManager.sol => storages/SlashingWindowStorage.sol} (59%) diff --git a/src/extensions/SharedVaults.sol b/src/extensions/SharedVaults.sol index b8f698d..0ba6527 100644 --- a/src/extensions/SharedVaults.sol +++ b/src/extensions/SharedVaults.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../middleware/BaseMiddleware.sol"; +import {ISharedVaults} from "../interfaces/extensions/ISharedVaults.sol"; /** * @title SharedVaults * @notice Contract for managing shared vaults that can be used by multiple operators * @dev Extends BaseMiddleware to provide access control for vault management functions */ -abstract contract SharedVaults is BaseMiddleware { +abstract contract SharedVaults is BaseMiddleware, ISharedVaults { uint64 public constant SharedVaults_VERSION = 1; /** - * @notice Registers a new shared vault - * @param sharedVault The address of the vault to register + * @inheritdoc ISharedVaults */ function registerSharedVault( address sharedVault @@ -23,8 +23,7 @@ abstract contract SharedVaults is BaseMiddleware { } /** - * @notice Pauses a shared vault - * @param sharedVault The address of the vault to pause + * @inheritdoc ISharedVaults */ function pauseSharedVault( address sharedVault @@ -34,8 +33,7 @@ abstract contract SharedVaults is BaseMiddleware { } /** - * @notice Unpauses a shared vault - * @param sharedVault The address of the vault to unpause + * @inheritdoc ISharedVaults */ function unpauseSharedVault( address sharedVault @@ -45,8 +43,7 @@ abstract contract SharedVaults is BaseMiddleware { } /** - * @notice Unregisters a shared vault - * @param sharedVault The address of the vault to unregister + * @inheritdoc ISharedVaults */ function unregisterSharedVault( address sharedVault diff --git a/src/extensions/Subnetworks.sol b/src/extensions/Subnetworks.sol index 0b85733..528fe8b 100644 --- a/src/extensions/Subnetworks.sol +++ b/src/extensions/Subnetworks.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../middleware/BaseMiddleware.sol"; +import {ISubnetworks} from "../interfaces/extensions/ISubnetworks.sol"; /** * @title Subnetworks * @notice Contract for managing subnetworks that can be registered and controlled * @dev Extends BaseMiddleware to provide access control for subnetwork management functions */ -abstract contract Subnetworks is BaseMiddleware { +abstract contract Subnetworks is BaseMiddleware, ISubnetworks { uint64 public constant Subnetworks_VERSION = 1; /** - * @notice Registers a new subnetwork - * @param subnetwork The ID of the subnetwork to register + * @inheritdoc ISubnetworks */ function registerSubnetwork( uint96 subnetwork @@ -23,8 +23,7 @@ abstract contract Subnetworks is BaseMiddleware { } /** - * @notice Pauses a subnetwork - * @param subnetwork The ID of the subnetwork to pause + * @inheritdoc ISubnetworks */ function pauseSubnetwork( uint96 subnetwork @@ -34,8 +33,7 @@ abstract contract Subnetworks is BaseMiddleware { } /** - * @notice Unpauses a subnetwork - * @param subnetwork The ID of the subnetwork to unpause + * @inheritdoc ISubnetworks */ function unpauseSubnetwork( uint96 subnetwork @@ -45,8 +43,7 @@ abstract contract Subnetworks is BaseMiddleware { } /** - * @notice Unregisters a subnetwork - * @param subnetwork The ID of the subnetwork to unregister + * @inheritdoc ISubnetworks */ function unregisterSubnetwork( uint96 subnetwork diff --git a/src/extensions/managers/access/OwnableAccessManager.sol b/src/extensions/managers/access/OwnableAccessManager.sol index 651c318..8b525fa 100644 --- a/src/extensions/managers/access/OwnableAccessManager.sol +++ b/src/extensions/managers/access/OwnableAccessManager.sol @@ -2,27 +2,16 @@ pragma solidity ^0.8.25; import {AccessManager} from "../../../managers/extendable/AccessManager.sol"; +import {IOwnableAccessManager} from "../../../interfaces/extensions/managers/access/IOwnableAccessManager.sol"; /** * @title OwnableAccessManager * @notice A middleware extension that restricts access to a single owner address * @dev Implements AccessManager with owner-based access control */ -abstract contract OwnableAccessManager is AccessManager { +abstract contract OwnableAccessManager is AccessManager, IOwnableAccessManager { uint64 public constant OwnableAccessManager_VERSION = 1; - /** - * @notice Error thrown when a non-owner address attempts to call a restricted function - * @param sender The address that attempted the call - */ - error OnlyOwnerCanCall(address sender); - - /** - * @notice Error thrown when trying to set an invalid owner address - * @param owner The invalid owner address - */ - error InvalidOwner(address owner); - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.OwnableAccessManager")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant OwnableAccessManagerStorageLocation = 0xcee92923a0c63eca6fc0402d78c9efde9f9f3dc73e6f9e14501bf734ed77f100; @@ -54,8 +43,7 @@ abstract contract OwnableAccessManager is AccessManager { } /** - * @notice Gets the current owner address - * @return The owner address + * @inheritdoc IOwnableAccessManager */ function owner() public view returns (address) { return _owner(); @@ -72,9 +60,7 @@ abstract contract OwnableAccessManager is AccessManager { } /** - * @notice Updates the owner address - * @param owner_ The new owner address - * @dev Can only be called by the current owner + * @inheritdoc IOwnableAccessManager */ function setOwner( address owner_ diff --git a/src/extensions/managers/access/OzAccessControl.sol b/src/extensions/managers/access/OzAccessControl.sol index 10a0d07..05bb9c9 100644 --- a/src/extensions/managers/access/OzAccessControl.sol +++ b/src/extensions/managers/access/OzAccessControl.sol @@ -2,13 +2,14 @@ pragma solidity ^0.8.25; import {AccessManager} from "../../../managers/extendable/AccessManager.sol"; +import {IOzAccessControl} from "../../../interfaces/extensions/managers/access/IOzAccessControl.sol"; /** * @title OzAccessControl * @notice A middleware extension that implements role-based access control * @dev Implements AccessManager with role-based access control functionality */ -abstract contract OzAccessControl is AccessManager { +abstract contract OzAccessControl is AccessManager, IOzAccessControl { uint64 public constant OzAccessControl_VERSION = 1; struct RoleData { @@ -33,15 +34,7 @@ abstract contract OzAccessControl is AccessManager { $.slot := OzAccessControlStorageLocation } } - - error AccessControlUnauthorizedAccount(address account, bytes32 role); - error AccessControlBadConfirmation(); - - event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); - event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); - event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); - event SelectorRoleSet(bytes4 indexed selector, bytes32 indexed role); - + /** * @notice Initializes the contract with a default admin * @param defaultAdmin The address to set as the default admin @@ -53,10 +46,7 @@ abstract contract OzAccessControl is AccessManager { } /** - * @notice Returns true if account has been granted role - * @param role The role to check - * @param account The account to check - * @return bool True if account has role + * @inheritdoc IOzAccessControl */ function hasRole(bytes32 role, address account) public view virtual returns (bool) { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); @@ -64,9 +54,7 @@ abstract contract OzAccessControl is AccessManager { } /** - * @notice Returns the admin role that controls the specified role - * @param role The role to get the admin for - * @return bytes32 The admin role + * @inheritdoc IOzAccessControl */ function getRoleAdmin( bytes32 role @@ -76,9 +64,7 @@ abstract contract OzAccessControl is AccessManager { } /** - * @notice Returns the role required for a function selector - * @param selector The function selector - * @return bytes32 The required role + * @inheritdoc IOzAccessControl */ function getRole( bytes4 selector @@ -88,9 +74,7 @@ abstract contract OzAccessControl is AccessManager { } /** - * @notice Grants role to account if caller has admin role - * @param role The role to grant - * @param account The account to grant the role to + * @inheritdoc IOzAccessControl */ function grantRole(bytes32 role, address account) public virtual { bytes32 adminRole = getRoleAdmin(role); @@ -101,9 +85,7 @@ abstract contract OzAccessControl is AccessManager { } /** - * @notice Revokes role from account if caller has admin role - * @param role The role to revoke - * @param account The account to revoke the role from + * @inheritdoc IOzAccessControl */ function revokeRole(bytes32 role, address account) public virtual { bytes32 adminRole = getRoleAdmin(role); @@ -114,9 +96,7 @@ abstract contract OzAccessControl is AccessManager { } /** - * @notice Allows an account to renounce a role they have - * @param role The role to renounce - * @param callerConfirmation Address of the caller for confirmation + * @inheritdoc IOzAccessControl */ function renounceRole(bytes32 role, address callerConfirmation) public virtual { if (callerConfirmation != msg.sender) { @@ -130,7 +110,7 @@ abstract contract OzAccessControl is AccessManager { * @param selector The function selector * @param role The required role */ - function _setSelectorRole(bytes4 selector, bytes32 role) public virtual { + function _setSelectorRole(bytes4 selector, bytes32 role) internal virtual { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); $._selectorRoles[selector] = role; emit SelectorRoleSet(selector, role); @@ -141,7 +121,7 @@ abstract contract OzAccessControl is AccessManager { * @param role The role to set admin for * @param adminRole The new admin role */ - function _setRoleAdmin(bytes32 role, bytes32 adminRole) public virtual { + function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); bytes32 previousAdminRole = getRoleAdmin(role); $._roles[role].adminRole = adminRole; @@ -154,7 +134,7 @@ abstract contract OzAccessControl is AccessManager { * @param account The account to grant the role to * @return bool True if role was granted */ - function _grantRole(bytes32 role, address account) public virtual returns (bool) { + function _grantRole(bytes32 role, address account) internal virtual returns (bool) { if (!hasRole(role, account)) { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); $._roles[role].hasRole[account] = true; @@ -170,7 +150,7 @@ abstract contract OzAccessControl is AccessManager { * @param account The account to revoke the role from * @return bool True if role was revoked */ - function _revokeRole(bytes32 role, address account) public virtual returns (bool) { + function _revokeRole(bytes32 role, address account) internal virtual returns (bool) { if (hasRole(role, account)) { OzAccessControlStorage storage $ = _getOzAccessControlStorage(); $._roles[role].hasRole[account] = false; diff --git a/src/extensions/managers/capture-timestamps/EpochCapture.sol b/src/extensions/managers/capture-timestamps/EpochCapture.sol index 0fd9a8d..4eed69a 100644 --- a/src/extensions/managers/capture-timestamps/EpochCapture.sol +++ b/src/extensions/managers/capture-timestamps/EpochCapture.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.25; import {CaptureTimestampManager} from "../../../managers/extendable/CaptureTimestampManager.sol"; +import {IEpochCapture} from "../../../interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol"; /** * @title EpochCapture @@ -9,7 +10,7 @@ import {CaptureTimestampManager} from "../../../managers/extendable/CaptureTimes * @dev Implements CaptureTimestampManager with epoch-based timestamp capture * @dev Epochs are fixed time periods starting from a base timestamp */ -abstract contract EpochCapture is CaptureTimestampManager { +abstract contract EpochCapture is CaptureTimestampManager, IEpochCapture { uint64 public constant EpochCapture_VERSION = 1; struct EpochCaptureStorage { @@ -40,10 +41,8 @@ abstract contract EpochCapture is CaptureTimestampManager { $.startTimestamp = _now(); } - /* - * @notice Returns the start timestamp for a given epoch. - * @param epoch The epoch number. - * @return The start timestamp. + /** + * @inheritdoc IEpochCapture */ function getEpochStart( uint48 epoch @@ -52,9 +51,8 @@ abstract contract EpochCapture is CaptureTimestampManager { return $.startTimestamp + epoch * $.epochDuration; } - /* - * @notice Returns the current epoch. - * @return The current epoch. + /** + * @inheritdoc IEpochCapture */ function getCurrentEpoch() public view returns (uint48) { EpochCaptureStorage storage $ = _getEpochCaptureStorage(); diff --git a/src/extensions/managers/sigs/ECDSASig.sol b/src/extensions/managers/sigs/ECDSASig.sol index 67941d8..6efc7c9 100644 --- a/src/extensions/managers/sigs/ECDSASig.sol +++ b/src/extensions/managers/sigs/ECDSASig.sol @@ -3,13 +3,14 @@ pragma solidity ^0.8.25; import {SigManager} from "../../../managers/extendable/SigManager.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {IECDSASig} from "../../../interfaces/extensions/managers/sigs/IECDSASig.sol"; /** * @title ECDSASig * @notice Contract for verifying ECDSA signatures against operator keys * @dev Implements SigManager interface using OpenZeppelin's ECDSA library */ -abstract contract ECDSASig is SigManager { +abstract contract ECDSASig is SigManager, IECDSASig { uint64 public constant ECDSASig_VERSION = 1; using ECDSA for bytes32; @@ -26,7 +27,7 @@ abstract contract ECDSASig is SigManager { address operator, bytes memory key_, bytes memory signature - ) public pure override returns (bool) { + ) internal pure override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); bytes32 hash = keccak256(abi.encodePacked(operator, key)); address signer = recover(hash, signature); @@ -35,11 +36,7 @@ abstract contract ECDSASig is SigManager { } /** - * @notice Recovers the signer address from a hash and signature - * @param hash The message hash that was signed - * @param signature The ECDSA signature - * @return The address that created the signature - * @dev Wrapper around OpenZeppelin's ECDSA.recover + * @inheritdoc IECDSASig */ function recover(bytes32 hash, bytes memory signature) public pure returns (address) { return hash.recover(signature); diff --git a/src/extensions/managers/sigs/EdDSASig.sol b/src/extensions/managers/sigs/EdDSASig.sol index 9dee07b..04cad32 100644 --- a/src/extensions/managers/sigs/EdDSASig.sol +++ b/src/extensions/managers/sigs/EdDSASig.sol @@ -3,13 +3,14 @@ pragma solidity ^0.8.25; import {EdDSA} from "../../../libraries/EdDSA.sol"; import {SigManager} from "../../../managers/extendable/SigManager.sol"; +import {IEdDSASig} from "../../../interfaces/extensions/managers/sigs/IEdDSASig.sol"; /** * @title EdDSASig * @notice Contract for verifying EdDSA signatures over Ed25519 against operator keys * @dev Implements SigManager interface using EdDSA signature verification */ -abstract contract EdDSASig is SigManager { +abstract contract EdDSASig is SigManager, IEdDSASig { uint64 public constant EdDSASig_VERSION = 1; /** @@ -25,19 +26,14 @@ abstract contract EdDSASig is SigManager { address operator, bytes memory key_, bytes memory signature - ) public override returns (bool) { + ) internal override returns (bool) { bytes32 key = abi.decode(key_, (bytes32)); bytes memory message = abi.encode(keccak256(abi.encodePacked(operator, key))); return verify(message, signature, key); } /** - * @notice Verifies an Ed25519 signature against a message and public key - * @param message The message that was signed - * @param signature The Ed25519 signature to verify - * @param key The Ed25519 public key compressed to 32 bytes - * @return True if the signature is valid, false otherwise - * @dev Wrapper around Ed25519.verify which handles decompression and curve operations + * @inheritdoc IEdDSASig */ function verify(bytes memory message, bytes memory signature, bytes32 key) public returns (bool) { return EdDSA.verify(message, signature, key); diff --git a/src/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/extensions/operators/ForcePauseSelfRegisterOperators.sol index bad95d3..7796d9c 100644 --- a/src/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -2,13 +2,14 @@ pragma solidity ^0.8.25; import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; +import {IForcePauseSelfRegisterOperators} from "../../interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol"; /** * @title ForcePauseSelfRegisterOperators * @notice Extension of SelfRegisterOperators that allows authorized addresses to forcefully pause operators * @dev Implements force pause functionality and prevents unpausing of force-paused operators */ -abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { +abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators, IForcePauseSelfRegisterOperators { uint64 public constant ForcePauseSelfRegisterOperators_VERSION = 1; struct ForcePauseSelfRegisterOperatorsStorage { @@ -27,13 +28,8 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { } } - error OperatorForcePaused(); - error OperatorVaultForcePaused(); - /** - * @notice Forces an operator to be paused - * @param operator The address of the operator to pause - * @dev Can only be called by authorized addresses (checkAccess modifier) + * @inheritdoc IForcePauseSelfRegisterOperators */ function forcePauseOperator( address operator @@ -45,9 +41,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { } /** - * @notice Forces an operator to be unpaused - * @param operator The address of the operator to unpause - * @dev Can only be called by authorized addresses (checkAccess modifier) + * @inheritdoc IForcePauseSelfRegisterOperators */ function forceUnpauseOperator( address operator @@ -59,10 +53,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { } /** - * @notice Forces a specific operator-vault pair to be paused - * @param operator The address of the operator - * @param vault The address of the vault - * @dev Can only be called by authorized addresses (checkAccess modifier) + * @inheritdoc IForcePauseSelfRegisterOperators */ function forcePauseOperatorVault(address operator, address vault) public checkAccess { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); @@ -72,10 +63,7 @@ abstract contract ForcePauseSelfRegisterOperators is SelfRegisterOperators { } /** - * @notice Forces a specific operator-vault pair to be unpaused - * @param operator The address of the operator - * @param vault The address of the vault - * @dev Can only be called by authorized addresses (checkAccess modifier) + * @inheritdoc IForcePauseSelfRegisterOperators */ function forceUnpauseOperatorVault(address operator, address vault) public checkAccess { ForcePauseSelfRegisterOperatorsStorage storage $ = _getForcePauseStorage(); diff --git a/src/extensions/operators/Operators.sol b/src/extensions/operators/Operators.sol index 9db7487..e9661db 100644 --- a/src/extensions/operators/Operators.sol +++ b/src/extensions/operators/Operators.sol @@ -2,20 +2,18 @@ pragma solidity ^0.8.25; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {IOperators} from "../../interfaces/extensions/operators/IOperators.sol"; /** * @title Operators * @notice Base contract for managing operator registration, keys, and vault relationships * @dev Provides core operator management functionality with hooks for customization */ -abstract contract Operators is BaseMiddleware { +abstract contract Operators is BaseMiddleware, IOperators { uint64 public constant Operators_VERSION = 1; /** - * @notice Registers a new operator with an optional vault association - * @param operator The address of the operator to register - * @param key The operator's public key - * @param vault Optional vault address to associate with the operator + * @inheritdoc IOperators */ function registerOperator(address operator, bytes memory key, address vault) public checkAccess { _beforeRegisterOperator(operator, key, vault); @@ -28,8 +26,7 @@ abstract contract Operators is BaseMiddleware { } /** - * @notice Unregisters an operator - * @param operator The address of the operator to unregister + * @inheritdoc IOperators */ function unregisterOperator( address operator @@ -39,8 +36,7 @@ abstract contract Operators is BaseMiddleware { } /** - * @notice Pauses an operator - * @param operator The address of the operator to pause + * @inheritdoc IOperators */ function pauseOperator( address operator @@ -50,8 +46,7 @@ abstract contract Operators is BaseMiddleware { } /** - * @notice Unpauses an operator - * @param operator The address of the operator to unpause + * @inheritdoc IOperators */ function unpauseOperator( address operator @@ -61,9 +56,7 @@ abstract contract Operators is BaseMiddleware { } /** - * @notice Updates an operator's public key - * @param operator The address of the operator - * @param key The new public key + * @inheritdoc IOperators */ function updateOperatorKey(address operator, bytes memory key) public checkAccess { _beforeUpdateOperatorKey(operator, key); @@ -71,9 +64,7 @@ abstract contract Operators is BaseMiddleware { } /** - * @notice Associates an operator with a vault - * @param operator The address of the operator - * @param vault The address of the vault + * @inheritdoc IOperators */ function registerOperatorVault(address operator, address vault) public checkAccess { require(_isOperatorRegistered(operator), "Operator not registered"); @@ -82,9 +73,7 @@ abstract contract Operators is BaseMiddleware { } /** - * @notice Removes an operator's association with a vault - * @param operator The address of the operator - * @param vault The address of the vault + * @inheritdoc IOperators */ function unregisterOperatorVault(address operator, address vault) public checkAccess { _beforeUnregisterOperatorVault(operator, vault); @@ -92,9 +81,7 @@ abstract contract Operators is BaseMiddleware { } /** - * @notice Pauses an operator's association with a specific vault - * @param operator The address of the operator - * @param vault The address of the vault + * @inheritdoc IOperators */ function pauseOperatorVault(address operator, address vault) public checkAccess { _beforePauseOperatorVault(operator, vault); @@ -102,9 +89,7 @@ abstract contract Operators is BaseMiddleware { } /** - * @notice Unpauses an operator's association with a specific vault - * @param operator The address of the operator - * @param vault The address of the vault + * @inheritdoc IOperators */ function unpauseOperatorVault(address operator, address vault) public checkAccess { _beforeUnpauseOperatorVault(operator, vault); diff --git a/src/extensions/operators/SelfRegisterOperators.sol b/src/extensions/operators/SelfRegisterOperators.sol index 841e6dd..5699763 100644 --- a/src/extensions/operators/SelfRegisterOperators.sol +++ b/src/extensions/operators/SelfRegisterOperators.sol @@ -5,17 +5,16 @@ import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; import {SigManager} from "../../managers/extendable/SigManager.sol"; import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import {ISelfRegisterOperators} from "../../interfaces/extensions/operators/ISelfRegisterOperators.sol"; /** * @title SelfRegisterOperators * @notice Contract for self-registration and management of operators with signature verification * @dev Extends BaseMiddleware, SigManager, and EIP712Upgradeable to provide signature-based operator management */ -abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upgradeable { +abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upgradeable, ISelfRegisterOperators { uint64 public constant SelfRegisterOperators_VERSION = 1; - error InvalidSignature(); - // EIP-712 TypeHash constants bytes32 private constant REGISTER_OPERATOR_TYPEHASH = keccak256("RegisterOperator(address operator,bytes key,address vault,uint256 nonce)"); @@ -66,10 +65,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to self-register with a key and optional vault - * @param key The operator's public key - * @param vault Optional vault address to associate with the operator - * @param signature Signature proving ownership of the key + * @inheritdoc ISelfRegisterOperators */ function registerOperator(bytes memory key, address vault, bytes memory signature) public { _verifyKey(msg.sender, key, signature); @@ -84,12 +80,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Registers an operator on behalf of another address with signature verification - * @param operator The address of the operator to register - * @param key The operator's public key - * @param vault Optional vault address to associate - * @param signature EIP712 signature authorizing registration - * @param keySignature Signature proving ownership of the key + * @inheritdoc ISelfRegisterOperators */ function registerOperator( address operator, @@ -116,7 +107,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to unregister themselves + * @inheritdoc ISelfRegisterOperators */ function unregisterOperator() public { _beforeUnregisterOperator(msg.sender); @@ -124,9 +115,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Unregisters an operator with signature verification - * @param operator The address of the operator to unregister - * @param signature EIP712 signature authorizing unregistration + * @inheritdoc ISelfRegisterOperators */ function unregisterOperator(address operator, bytes memory signature) public { _beforeUnregisterOperator(operator); @@ -138,7 +127,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to pause themselves + * @inheritdoc ISelfRegisterOperators */ function pauseOperator() public { _beforePauseOperator(msg.sender); @@ -146,9 +135,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Pauses an operator with signature verification - * @param operator The address of the operator to pause - * @param signature EIP712 signature authorizing pause + * @inheritdoc ISelfRegisterOperators */ function pauseOperator(address operator, bytes memory signature) public { _beforePauseOperator(operator); @@ -160,7 +147,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to unpause themselves + * @inheritdoc ISelfRegisterOperators */ function unpauseOperator() public { _beforeUnpauseOperator(msg.sender); @@ -168,9 +155,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Unpauses an operator with signature verification - * @param operator The address of the operator to unpause - * @param signature EIP712 signature authorizing unpause + * @inheritdoc ISelfRegisterOperators */ function unpauseOperator(address operator, bytes memory signature) public { _beforeUnpauseOperator(operator); @@ -182,9 +167,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to update their own key - * @param key The new public key - * @param signature Signature proving ownership of the key + * @inheritdoc ISelfRegisterOperators */ function updateOperatorKey(bytes memory key, bytes memory signature) public { _verifyKey(msg.sender, key, signature); @@ -193,11 +176,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Updates an operator's key with signature verification - * @param operator The address of the operator - * @param key The new public key - * @param signature EIP712 signature authorizing key update - * @param keySignature Signature proving ownership of the new key + * @inheritdoc ISelfRegisterOperators */ function updateOperatorKey( address operator, @@ -217,8 +196,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to register a vault association - * @param vault The address of the vault to associate + * @inheritdoc ISelfRegisterOperators */ function registerOperatorVault( address vault @@ -229,10 +207,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Registers a vault association with signature verification - * @param operator The address of the operator - * @param vault The address of the vault - * @param signature EIP712 signature authorizing vault registration + * @inheritdoc ISelfRegisterOperators */ function registerOperatorVault(address operator, address vault, bytes memory signature) public { require(_isOperatorRegistered(operator), "Operator not registered"); @@ -247,8 +222,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to unregister a vault association - * @param vault The address of the vault to unregister + * @inheritdoc ISelfRegisterOperators */ function unregisterOperatorVault( address vault @@ -258,10 +232,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Unregisters a vault association with signature verification - * @param operator The address of the operator - * @param vault The address of the vault - * @param signature EIP712 signature authorizing vault unregistration + * @inheritdoc ISelfRegisterOperators */ function unregisterOperatorVault(address operator, address vault, bytes memory signature) public { _beforeUnregisterOperatorVault(operator, vault); @@ -275,8 +246,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to pause a vault association - * @param vault The address of the vault to pause + * @inheritdoc ISelfRegisterOperators */ function pauseOperatorVault( address vault @@ -286,10 +256,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Pauses a vault association with signature verification - * @param operator The address of the operator - * @param vault The address of the vault - * @param signature EIP712 signature authorizing vault pause + * @inheritdoc ISelfRegisterOperators */ function pauseOperatorVault(address operator, address vault, bytes memory signature) public { _beforePauseOperatorVault(operator, vault); @@ -303,8 +270,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Allows an operator to unpause a vault association - * @param vault The address of the vault to unpause + * @inheritdoc ISelfRegisterOperators */ function unpauseOperatorVault( address vault @@ -314,10 +280,7 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg } /** - * @notice Unpauses a vault association with signature verification - * @param operator The address of the operator - * @param vault The address of the vault - * @param signature EIP712 signature authorizing vault unpause + * @inheritdoc ISelfRegisterOperators */ function unpauseOperatorVault(address operator, address vault, bytes memory signature) public { _beforeUnpauseOperatorVault(operator, vault); diff --git a/src/interfaces/IBaseMiddleware.sol b/src/interfaces/IBaseMiddlewareReader.sol similarity index 99% rename from src/interfaces/IBaseMiddleware.sol rename to src/interfaces/IBaseMiddlewareReader.sol index d8c2dcb..9139eba 100644 --- a/src/interfaces/IBaseMiddleware.sol +++ b/src/interfaces/IBaseMiddlewareReader.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -interface IBaseMiddleware { +interface IBaseMiddlewareReader { function getCaptureTimestamp() external view returns (uint48 timestamp); function stakeToPower(address vault, uint256 stake) external view returns (uint256 power); diff --git a/src/interfaces/extensions/ISharedVaults.sol b/src/interfaces/extensions/ISharedVaults.sol new file mode 100644 index 0000000..537654d --- /dev/null +++ b/src/interfaces/extensions/ISharedVaults.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title ISharedVaults + * @notice Interface for managing shared vaults that can be used by multiple operators + */ +interface ISharedVaults { + /** + * @notice Registers a new shared vault + * @param sharedVault The address of the vault to register + */ + function registerSharedVault(address sharedVault) external; + + /** + * @notice Pauses a shared vault + * @param sharedVault The address of the vault to pause + */ + function pauseSharedVault(address sharedVault) external; + + /** + * @notice Unpauses a shared vault + * @param sharedVault The address of the vault to unpause + */ + function unpauseSharedVault(address sharedVault) external; + + /** + * @notice Unregisters a shared vault + * @param sharedVault The address of the vault to unregister + */ + function unregisterSharedVault(address sharedVault) external; +} diff --git a/src/interfaces/extensions/ISubnetworks.sol b/src/interfaces/extensions/ISubnetworks.sol new file mode 100644 index 0000000..aea95fe --- /dev/null +++ b/src/interfaces/extensions/ISubnetworks.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title ISubnetworks + * @notice Interface for managing subnetworks that can be registered and controlled + */ +interface ISubnetworks { + /** + * @notice Registers a new subnetwork + * @param subnetwork The ID of the subnetwork to register + */ + function registerSubnetwork(uint96 subnetwork) external; + + /** + * @notice Pauses a subnetwork + * @param subnetwork The ID of the subnetwork to pause + */ + function pauseSubnetwork(uint96 subnetwork) external; + + /** + * @notice Unpauses a subnetwork + * @param subnetwork The ID of the subnetwork to unpause + */ + function unpauseSubnetwork(uint96 subnetwork) external; + + /** + * @notice Unregisters a subnetwork + * @param subnetwork The ID of the subnetwork to unregister + */ + function unregisterSubnetwork(uint96 subnetwork) external; +} diff --git a/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol b/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol new file mode 100644 index 0000000..3d0a5ed --- /dev/null +++ b/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title IOwnableAccessManager + * @notice Interface for a middleware extension that restricts access to a single owner address + */ +interface IOwnableAccessManager { + /** + * @notice Error thrown when a non-owner address attempts to call a restricted function + * @param sender The address that attempted the call + */ + error OnlyOwnerCanCall(address sender); + + /** + * @notice Error thrown when trying to set an invalid owner address + * @param owner The invalid owner address + */ + error InvalidOwner(address owner); + + /** + * @notice Gets the current owner address + * @return The owner address + */ + function owner() external view returns (address); + + /** + * @notice Updates the owner address + * @param owner_ The new owner address + * @dev Can only be called by the current owner + */ + function setOwner(address owner_) external; +} diff --git a/src/interfaces/extensions/managers/access/IOzAccessControl.sol b/src/interfaces/extensions/managers/access/IOzAccessControl.sol new file mode 100644 index 0000000..e6df52d --- /dev/null +++ b/src/interfaces/extensions/managers/access/IOzAccessControl.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title IOzAccessControl + * @notice Interface for a middleware extension that implements role-based access control + */ +interface IOzAccessControl { + error AccessControlUnauthorizedAccount(address account, bytes32 role); + error AccessControlBadConfirmation(); + + event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); + event SelectorRoleSet(bytes4 indexed selector, bytes32 indexed role); + + /** + * @notice Returns true if account has been granted role + * @param role The role to check + * @param account The account to check + * @return bool True if account has role + */ + function hasRole(bytes32 role, address account) external view returns (bool); + + /** + * @notice Returns the admin role that controls the specified role + * @param role The role to get the admin for + * @return bytes32 The admin role + */ + function getRoleAdmin(bytes32 role) external view returns (bytes32); + + /** + * @notice Returns the role required for a function selector + * @param selector The function selector + * @return bytes32 The required role + */ + function getRole(bytes4 selector) external view returns (bytes32); + + /** + * @notice Grants role to account if caller has admin role + * @param role The role to grant + * @param account The account to grant the role to + */ + function grantRole(bytes32 role, address account) external; + + /** + * @notice Revokes role from account if caller has admin role + * @param role The role to revoke + * @param account The account to revoke the role from + */ + function revokeRole(bytes32 role, address account) external; + + /** + * @notice Allows an account to renounce a role they have + * @param role The role to renounce + * @param callerConfirmation Address of the caller for confirmation + */ + function renounceRole(bytes32 role, address callerConfirmation) external; +} diff --git a/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol b/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol new file mode 100644 index 0000000..5e97b57 --- /dev/null +++ b/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title IEpochCapture + * @notice Interface for a middleware extension that captures timestamps based on epochs + * @dev Implements timestamp capture using fixed time periods (epochs) from a base timestamp + */ +interface IEpochCapture { + /** + * @notice Returns the start timestamp for a given epoch + * @param epoch The epoch number + * @return The start timestamp + */ + function getEpochStart(uint48 epoch) external view returns (uint48); + + /** + * @notice Returns the current epoch number + * @return The current epoch + */ + function getCurrentEpoch() external view returns (uint48); +} diff --git a/src/interfaces/extensions/managers/sigs/IECDSASig.sol b/src/interfaces/extensions/managers/sigs/IECDSASig.sol new file mode 100644 index 0000000..2e84661 --- /dev/null +++ b/src/interfaces/extensions/managers/sigs/IECDSASig.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title IECDSASig + * @notice Interface for verifying ECDSA signatures against operator keys + * @dev Extends ISigManager interface for ECDSA signature verification + */ +interface IECDSASig { + /** + * @notice Recovers the signer address from a hash and signature + * @param hash The message hash that was signed + * @param signature The ECDSA signature + * @return The address that created the signature + * @dev Wrapper around OpenZeppelin's ECDSA.recover + */ + function recover(bytes32 hash, bytes memory signature) external pure returns (address); +} diff --git a/src/interfaces/extensions/managers/sigs/IEdDSASig.sol b/src/interfaces/extensions/managers/sigs/IEdDSASig.sol new file mode 100644 index 0000000..bffc7b3 --- /dev/null +++ b/src/interfaces/extensions/managers/sigs/IEdDSASig.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title IEdDSASig + * @notice Interface for verifying EdDSA signatures over Ed25519 against operator keys + * @dev Extends ISigManager interface for EdDSA signature verification + */ +interface IEdDSASig { + /** + * @notice Verifies an Ed25519 signature against a message and public key + * @param message The message that was signed + * @param signature The Ed25519 signature to verify + * @param key The Ed25519 public key compressed to 32 bytes + * @return True if the signature is valid, false otherwise + * @dev Wrapper around Ed25519.verify which handles decompression and curve operations + */ + function verify(bytes memory message, bytes memory signature, bytes32 key) external returns (bool); +} \ No newline at end of file diff --git a/src/interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol b/src/interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol new file mode 100644 index 0000000..c55a155 --- /dev/null +++ b/src/interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {ISelfRegisterOperators} from "./ISelfRegisterOperators.sol"; + +/** + * @title IForcePauseSelfRegisterOperators + * @notice Interface for force pausing operators and operator-vault pairs + */ +interface IForcePauseSelfRegisterOperators is ISelfRegisterOperators { + error OperatorForcePaused(); + error OperatorVaultForcePaused(); + + /** + * @notice Forces an operator to be paused + * @param operator The address of the operator to pause + */ + function forcePauseOperator(address operator) external; + + /** + * @notice Forces an operator to be unpaused + * @param operator The address of the operator to unpause + */ + function forceUnpauseOperator(address operator) external; + + /** + * @notice Forces a specific operator-vault pair to be paused + * @param operator The address of the operator + * @param vault The address of the vault + */ + function forcePauseOperatorVault(address operator, address vault) external; + + /** + * @notice Forces a specific operator-vault pair to be unpaused + * @param operator The address of the operator + * @param vault The address of the vault + */ + function forceUnpauseOperatorVault(address operator, address vault) external; +} diff --git a/src/interfaces/extensions/operators/IOperators.sol b/src/interfaces/extensions/operators/IOperators.sol new file mode 100644 index 0000000..85ff56c --- /dev/null +++ b/src/interfaces/extensions/operators/IOperators.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title IOperators + * @notice Interface for managing operator registration, keys, and vault relationships + */ +interface IOperators { + /** + * @notice Registers a new operator with an optional vault association + * @param operator The address of the operator to register + * @param key The operator's public key + * @param vault Optional vault address to associate with the operator + */ + function registerOperator(address operator, bytes memory key, address vault) external; + + /** + * @notice Unregisters an operator + * @param operator The address of the operator to unregister + */ + function unregisterOperator(address operator) external; + + /** + * @notice Pauses an operator + * @param operator The address of the operator to pause + */ + function pauseOperator(address operator) external; + + /** + * @notice Unpauses an operator + * @param operator The address of the operator to unpause + */ + function unpauseOperator(address operator) external; + + /** + * @notice Updates an operator's public key + * @param operator The address of the operator + * @param key The new public key + */ + function updateOperatorKey(address operator, bytes memory key) external; + + /** + * @notice Associates an operator with a vault + * @param operator The address of the operator + * @param vault The address of the vault + */ + function registerOperatorVault(address operator, address vault) external; + + /** + * @notice Removes an operator's association with a vault + * @param operator The address of the operator + * @param vault The address of the vault + */ + function unregisterOperatorVault(address operator, address vault) external; + + /** + * @notice Pauses an operator's association with a specific vault + * @param operator The address of the operator + * @param vault The address of the vault + */ + function pauseOperatorVault(address operator, address vault) external; + + /** + * @notice Unpauses an operator's association with a specific vault + * @param operator The address of the operator + * @param vault The address of the vault + */ + function unpauseOperatorVault(address operator, address vault) external; +} diff --git a/src/interfaces/extensions/operators/ISelfRegisterOperators.sol b/src/interfaces/extensions/operators/ISelfRegisterOperators.sol new file mode 100644 index 0000000..ae0943d --- /dev/null +++ b/src/interfaces/extensions/operators/ISelfRegisterOperators.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +/** + * @title ISelfRegisterOperators + * @notice Interface for self-registration and management of operators with signature verification + */ +interface ISelfRegisterOperators { + error InvalidSignature(); + + /** + * @notice Returns the nonce for an operator address + * @param operator The operator address to check + * @return The current nonce value + */ + function nonces(address operator) external view returns (uint256); + + /** + * @notice Allows an operator to self-register with a key and optional vault + * @param key The operator's public key + * @param vault Optional vault address to associate with the operator + * @param signature Signature proving ownership of the key + */ + function registerOperator(bytes memory key, address vault, bytes memory signature) external; + + /** + * @notice Registers an operator on behalf of another address with signature verification + * @param operator The address of the operator to register + * @param key The operator's public key + * @param vault Optional vault address to associate + * @param signature EIP712 signature authorizing registration + * @param keySignature Signature proving ownership of the key + */ + function registerOperator( + address operator, + bytes memory key, + address vault, + bytes memory signature, + bytes memory keySignature + ) external; + + /** + * @notice Allows an operator to unregister themselves + */ + function unregisterOperator() external; + + /** + * @notice Unregisters an operator with signature verification + * @param operator The address of the operator to unregister + * @param signature EIP712 signature authorizing unregistration + */ + function unregisterOperator(address operator, bytes memory signature) external; + + /** + * @notice Allows an operator to pause themselves + */ + function pauseOperator() external; + + /** + * @notice Pauses an operator with signature verification + * @param operator The address of the operator to pause + * @param signature EIP712 signature authorizing pause + */ + function pauseOperator(address operator, bytes memory signature) external; + + /** + * @notice Allows an operator to unpause themselves + */ + function unpauseOperator() external; + + /** + * @notice Unpauses an operator with signature verification + * @param operator The address of the operator to unpause + * @param signature EIP712 signature authorizing unpause + */ + function unpauseOperator(address operator, bytes memory signature) external; + + /** + * @notice Allows an operator to update their own key + * @param key The new public key + * @param signature Signature proving ownership of the key + */ + function updateOperatorKey(bytes memory key, bytes memory signature) external; + + /** + * @notice Updates an operator's key with signature verification + * @param operator The address of the operator + * @param key The new public key + * @param signature EIP712 signature authorizing key update + * @param keySignature Signature proving ownership of the new key + */ + function updateOperatorKey( + address operator, + bytes memory key, + bytes memory signature, + bytes memory keySignature + ) external; + + /** + * @notice Allows an operator to register a vault association + * @param vault The address of the vault to associate + */ + function registerOperatorVault(address vault) external; + + /** + * @notice Registers a vault association with signature verification + * @param operator The address of the operator + * @param vault The address of the vault + * @param signature EIP712 signature authorizing vault registration + */ + function registerOperatorVault(address operator, address vault, bytes memory signature) external; + + /** + * @notice Allows an operator to unregister a vault association + * @param vault The address of the vault to unregister + */ + function unregisterOperatorVault(address vault) external; + + /** + * @notice Unregisters a vault association with signature verification + * @param operator The address of the operator + * @param vault The address of the vault + * @param signature EIP712 signature authorizing vault unregistration + */ + function unregisterOperatorVault(address operator, address vault, bytes memory signature) external; + + /** + * @notice Allows an operator to pause a vault association + * @param vault The address of the vault to pause + */ + function pauseOperatorVault(address vault) external; + + /** + * @notice Pauses a vault association with signature verification + * @param operator The address of the operator + * @param vault The address of the vault + * @param signature EIP712 signature authorizing vault pause + */ + function pauseOperatorVault(address operator, address vault, bytes memory signature) external; + + /** + * @notice Allows an operator to unpause a vault association + * @param vault The address of the vault to unpause + */ + function unpauseOperatorVault(address vault) external; + + /** + * @notice Unpauses a vault association with signature verification + * @param operator The address of the operator + * @param vault The address of the vault + * @param signature EIP712 signature authorizing vault unpause + */ + function unpauseOperatorVault(address operator, address vault, bytes memory signature) external; +} diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index 9344c21..9b89612 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -4,14 +4,14 @@ pragma solidity ^0.8.25; import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; import {IOptInService} from "@symbiotic/interfaces/service/IOptInService.sol"; -import {NetworkManager} from "./NetworkManager.sol"; -import {SlashingWindowManager} from "./SlashingWindowManager.sol"; +import {NetworkStorage} from "./storages/NetworkStorage.sol"; +import {SlashingWindowStorage} from "./storages/SlashingWindowStorage.sol"; import {CaptureTimestampManager} from "./extendable/CaptureTimestampManager.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; -abstract contract OperatorManager is NetworkManager, SlashingWindowManager, CaptureTimestampManager { +abstract contract OperatorManager is NetworkStorage, SlashingWindowStorage, CaptureTimestampManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; error NotOperator(); diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index d8c3f34..07666bc 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -15,8 +15,8 @@ import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap import {StakePowerManager} from "./extendable/StakePowerManager.sol"; import {CaptureTimestampManager} from "./extendable/CaptureTimestampManager.sol"; -import {NetworkManager} from "./NetworkManager.sol"; -import {SlashingWindowManager} from "./SlashingWindowManager.sol"; +import {NetworkStorage} from "./storages/NetworkStorage.sol"; +import {SlashingWindowStorage} from "./storages/SlashingWindowStorage.sol"; import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; @@ -25,7 +25,7 @@ import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; * @notice Abstract contract for managing vaults and their relationships with operators and subnetworks * @dev Extends BaseManager and provides functionality for registering, pausing, and managing vaults */ -abstract contract VaultManager is NetworkManager, SlashingWindowManager, CaptureTimestampManager, StakePowerManager { +abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, CaptureTimestampManager, StakePowerManager { using EnumerableMap for EnumerableMap.AddressToUintMap; using EnumerableMap for EnumerableMap.AddressToAddressMap; using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; @@ -67,9 +67,17 @@ abstract contract VaultManager is NetworkManager, SlashingWindowManager, Capture uint256 response; } - uint64 internal constant INSTANT_SLASHER_TYPE = 0; // Constant representing the instant slasher type - uint64 internal constant VETO_SLASHER_TYPE = 1; // Constant representing the veto slasher type - uint64 internal constant OPERATOR_SPECIFIC_DELEGATOR_TYPE = 2; + enum SlasherType { + INSTANT, // Instant slasher type + VETO // Veto slasher type + } + + enum DelegatorType { + FULL_RESTAKE, + NETWORK_RESTAKE, + OPERATOR_SPECIFIC + } + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.VaultManager")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant VaultManagerStorageLocation = 0x485f0695561726d087d0cb5cf546efed37ef61dfced21455f1ba7eb5e5b3db00; @@ -631,9 +639,9 @@ abstract contract VaultManager is NetworkManager, SlashingWindowManager, Capture resp.vault = vault; resp.slasherType = slasherType; resp.subnetwork = subnetwork; - if (slasherType == INSTANT_SLASHER_TYPE) { + if (slasherType == uint64(SlasherType.INSTANT)) { resp.response = ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, hints); - } else if (slasherType == VETO_SLASHER_TYPE) { + } else if (slasherType == uint64(SlasherType.VETO)) { resp.response = IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, hints); } else { revert UnknownSlasherType(); @@ -654,7 +662,7 @@ abstract contract VaultManager is NetworkManager, SlashingWindowManager, Capture ) internal returns (uint256 slashedAmount) { address slasher = IVault(vault).slasher(); uint64 slasherType = IEntity(slasher).TYPE(); - if (slasherType != VETO_SLASHER_TYPE) { + if (slasherType != uint64(SlasherType.VETO)) { revert NonVetoSlasher(); } @@ -684,7 +692,7 @@ abstract contract VaultManager is NetworkManager, SlashingWindowManager, Capture uint48 vaultEpoch = IVault(vault).epochDuration(); address slasher = IVault(vault).slasher(); - if (slasher != address(0) && IEntity(slasher).TYPE() == VETO_SLASHER_TYPE) { + if (slasher != address(0) && IEntity(slasher).TYPE() == uint64(SlasherType.VETO)) { vaultEpoch -= IVetoSlasher(slasher).vetoDuration(); } @@ -696,7 +704,7 @@ abstract contract VaultManager is NetworkManager, SlashingWindowManager, Capture function _validateOperatorVault(address operator, address vault) internal view { address delegator = IVault(vault).delegator(); if ( - IEntity(delegator).TYPE() != OPERATOR_SPECIFIC_DELEGATOR_TYPE + IEntity(delegator).TYPE() != uint64(DelegatorType.OPERATOR_SPECIFIC) || IOperatorSpecificDelegator(delegator).operator() != operator ) { revert NotOperatorSpecificVault(); diff --git a/src/managers/extendable/KeyManager.sol b/src/managers/extendable/KeyManager.sol index 813b7cd..f71b305 100644 --- a/src/managers/extendable/KeyManager.sol +++ b/src/managers/extendable/KeyManager.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import {SlashingWindowManager} from "../SlashingWindowManager.sol"; +import {SlashingWindowStorage} from "../storages/SlashingWindowStorage.sol"; import {CaptureTimestampManager} from "./CaptureTimestampManager.sol"; /** * @title KeyManager * @notice Abstract contract for managing keys */ -abstract contract KeyManager is SlashingWindowManager, CaptureTimestampManager { +abstract contract KeyManager is SlashingWindowStorage, CaptureTimestampManager { /** * @notice Updates the key associated with an operator * @param operator The address of the operator diff --git a/src/managers/extendable/SigManager.sol b/src/managers/extendable/SigManager.sol index 79a270f..c3ec014 100644 --- a/src/managers/extendable/SigManager.sol +++ b/src/managers/extendable/SigManager.sol @@ -15,5 +15,5 @@ abstract contract SigManager is Initializable { address operator, bytes memory key_, bytes memory signature - ) public virtual returns (bool); + ) internal virtual returns (bool); } diff --git a/src/managers/NetworkManager.sol b/src/managers/storages/NetworkStorage.sol similarity index 70% rename from src/managers/NetworkManager.sol rename to src/managers/storages/NetworkStorage.sol index 5a63c64..b1df46b 100644 --- a/src/managers/NetworkManager.sol +++ b/src/managers/storages/NetworkStorage.sol @@ -3,20 +3,20 @@ pragma solidity ^0.8.25; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -abstract contract NetworkManager is Initializable { - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.NetworkManager")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant NetworkManagerStorageLocation = +abstract contract NetworkStorage is Initializable { + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.NetworkStorage")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant NetworkStorageLocation = 0x779150488f5e984d1f840ba606e388ada6c73b44f261274c3595c61a30023e00; /** * @notice Initializes the NetworkManager contract * @param network The address of the network */ - function __NetworkManager_init_private( + function __NetworkStorage_init_private( address network ) internal onlyInitializing { assembly { - sstore(NetworkManagerStorageLocation, network) + sstore(NetworkStorageLocation, network) } } @@ -27,7 +27,7 @@ abstract contract NetworkManager is Initializable { function _NETWORK() internal view returns (address) { address network; assembly { - network := sload(NetworkManagerStorageLocation) + network := sload(NetworkStorageLocation) } return network; } diff --git a/src/managers/SlashingWindowManager.sol b/src/managers/storages/SlashingWindowStorage.sol similarity index 59% rename from src/managers/SlashingWindowManager.sol rename to src/managers/storages/SlashingWindowStorage.sol index 36e0903..8a93350 100644 --- a/src/managers/SlashingWindowManager.sol +++ b/src/managers/storages/SlashingWindowStorage.sol @@ -3,23 +3,23 @@ pragma solidity ^0.8.25; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -abstract contract SlashingWindowManager is Initializable { - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.SlashingWindowManager")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant SlashingWindowManagerStorageLocation = +abstract contract SlashingWindowStorage is Initializable { + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.SlashingWindowStorage")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant SlashingWindowStorageLocation = 0x52becd5b30d67421b1f63b9d90d513daf82b3973912d3edfdac9468c1743c000; - function __SlashingWindowManager_init_private( + function __SlashingWindowStorage_init_private( uint48 _slashingWindow ) internal onlyInitializing { assembly { - sstore(SlashingWindowManagerStorageLocation, _slashingWindow) + sstore(SlashingWindowStorageLocation, _slashingWindow) } } function _SLASHING_WINDOW() internal view returns (uint48) { uint48 slashingWindow; assembly { - slashingWindow := sload(SlashingWindowManagerStorageLocation) + slashingWindow := sload(SlashingWindowStorageLocation) } return slashingWindow; } diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index aec105c..905b5e6 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -19,6 +19,9 @@ import {KeyManager} from "../managers/extendable/KeyManager.sol"; * management capabilities that can be extended with additional functionality. */ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager, KeyManager { + // This constant aggregates changes of all not extendable managers + uint64 public constant BaseMiddleware_VERSION = 1; + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseMiddleware")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant ReadHelperStorageLocation = 0xfd87879bc98f37af7578af722aecfbe5843e5ad354da2d1e70cb5157c4ec8800; @@ -31,8 +34,8 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager address operatorNetOptin, address readHelper ) internal onlyInitializing { - __NetworkManager_init_private(network); - __SlashingWindowManager_init_private(slashingWindow); + __NetworkStorage_init_private(network); + __SlashingWindowStorage_init_private(slashingWindow); __VaultManager_init_private(vaultRegistry); __OperatorManager_init_private(operatorRegistry, operatorNetOptin); assembly { diff --git a/test/OperatorsRegistration.t.sol b/test/OperatorsRegistration.t.sol index 1e5d86e..8b2cdc2 100644 --- a/test/OperatorsRegistration.t.sol +++ b/test/OperatorsRegistration.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.25; import {POCBaseTest} from "@symbiotic-test/POCBase.t.sol"; import {SimplePosMiddleware} from "../src/examples/simple-pos-network/SimplePosMiddleware.sol"; -import {IBaseMiddleware} from "../src/interfaces/IBaseMiddleware.sol"; +import {IBaseMiddlewareReader} from "../src/interfaces/IBaseMiddlewareReader.sol"; //import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; //import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; @@ -59,7 +59,7 @@ contract OperatorsRegistrationTest is POCBaseTest { function testOperators() public { address operator = address(0x1337); bytes memory key = hex"0000000000000000000000000000000000000000000000000000000000000005"; - uint256 operatorsLength = IBaseMiddleware(address(middleware)).operatorsLength(); + uint256 operatorsLength = IBaseMiddlewareReader(address(middleware)).operatorsLength(); assertEq(operatorsLength, 0, "Operators length should be 0"); // can't register without registration @@ -80,9 +80,9 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.registerOperator(operator, key, address(0)); - (address op, uint48 s, uint48 f) = IBaseMiddleware(address(middleware)).operatorWithTimesAt(0); + (address op, uint48 s, uint48 f) = IBaseMiddlewareReader(address(middleware)).operatorWithTimesAt(0); - operatorsLength = IBaseMiddleware(address(middleware)).operatorsLength(); + operatorsLength = IBaseMiddlewareReader(address(middleware)).operatorsLength(); assertEq(operatorsLength, 1, "Operators length should be 1"); // can't register twice @@ -90,10 +90,10 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.registerOperator(operator, key, address(0)); // activates on next epoch - address[] memory operators = IBaseMiddleware(address(middleware)).activeOperators(); + address[] memory operators = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(operators.length, 0, "1 Active operators length should be 0"); skipEpoch(); - operators = IBaseMiddleware(address(middleware)).activeOperators(); + operators = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(operators.length, 1, "2 Active operators length should be 1"); // pause @@ -104,7 +104,7 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.pauseOperator(operator); // pause applies on next epoch - operators = IBaseMiddleware(address(middleware)).activeOperators(); + operators = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(operators.length, 1, "3 Active operators length should be 1"); // can't unpause right now, minumum one epoch before immutable period passed @@ -113,20 +113,20 @@ contract OperatorsRegistrationTest is POCBaseTest { skipImmutablePeriod(); skipImmutablePeriod(); - operators = IBaseMiddleware(address(middleware)).activeOperators(); + operators = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(operators.length, 0, "4 Active operators length should be 0"); - (op, s, f) = IBaseMiddleware(address(middleware)).operatorWithTimesAt(0); + (op, s, f) = IBaseMiddlewareReader(address(middleware)).operatorWithTimesAt(0); // unpause middleware.unpauseOperator(operator); - (op, s, f) = IBaseMiddleware(address(middleware)).operatorWithTimesAt(0); + (op, s, f) = IBaseMiddlewareReader(address(middleware)).operatorWithTimesAt(0); // unpause applies on next epoch - operators = IBaseMiddleware(address(middleware)).activeOperators(); + operators = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(operators.length, 0, "5 Active operators length should be 0"); skipEpoch(); - operators = IBaseMiddleware(address(middleware)).activeOperators(); + operators = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(operators.length, 1, "6 Active operators length should be 1"); // pause and unregister @@ -141,7 +141,7 @@ contract OperatorsRegistrationTest is POCBaseTest { skipEpoch(); middleware.unregisterOperator(operator); - operatorsLength = IBaseMiddleware(address(middleware)).operatorsLength(); + operatorsLength = IBaseMiddlewareReader(address(middleware)).operatorsLength(); assertEq(operatorsLength, 0, "7 Operators length should be 0"); } @@ -170,41 +170,41 @@ contract OperatorsRegistrationTest is POCBaseTest { skipEpoch(); // Verify all operators are active - address[] memory activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + address[] memory activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 3, "Should have 3 active operators"); // Test complex pause/unpause sequence // Pause operator 0 middleware.pauseOperator(operators[0]); skipEpoch(); - activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should have 2 active operators after pause"); // Pause operator 1 middleware.pauseOperator(operators[1]); skipEpoch(); - activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 1, "Should have 1 active operator after second pause"); // Wait for immutable period and try to unpause operator 0 skipImmutablePeriod(); middleware.unpauseOperator(operators[0]); skipEpoch(); - activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should have 2 active operators after unpause"); // Test operator was active at specific timestamps - uint48 currentTimestamp = IBaseMiddleware(address(middleware)).getCaptureTimestamp(); + uint48 currentTimestamp = IBaseMiddlewareReader(address(middleware)).getCaptureTimestamp(); assertTrue( - IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[0]), + IBaseMiddlewareReader(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[0]), "Operator 0 should be active" ); assertFalse( - IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[1]), + IBaseMiddlewareReader(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[1]), "Operator 1 should be inactive" ); assertTrue( - IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[2]), + IBaseMiddlewareReader(address(middleware)).operatorWasActiveAt(currentTimestamp, operators[2]), "Operator 2 should be active" ); @@ -216,8 +216,8 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.unregisterOperator(operators[1]); // Should succeed - operator is paused // Verify final state - assertEq(IBaseMiddleware(address(middleware)).operatorsLength(), 2, "Should have 2 operators remaining"); - activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + assertEq(IBaseMiddlewareReader(address(middleware)).operatorsLength(), 2, "Should have 2 operators remaining"); + activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should still have 2 active operators"); } @@ -235,13 +235,13 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate operator skipEpoch(); - assertEq(IBaseMiddleware(address(middleware)).activeOperators().length, 1, "Should have 1 active operator"); + assertEq(IBaseMiddlewareReader(address(middleware)).activeOperators().length, 1, "Should have 1 active operator"); // Pause operator middleware.pauseOperator(operator); skipEpoch(); assertEq( - IBaseMiddleware(address(middleware)).activeOperators().length, + IBaseMiddlewareReader(address(middleware)).activeOperators().length, 0, "Should have 0 active operators after pause" ); @@ -249,7 +249,7 @@ contract OperatorsRegistrationTest is POCBaseTest { // Wait for immutable period and unregister skipImmutablePeriod(); middleware.unregisterOperator(operator); - assertEq(IBaseMiddleware(address(middleware)).operatorsLength(), 0, "Should have 0 operators after unregister"); + assertEq(IBaseMiddlewareReader(address(middleware)).operatorsLength(), 0, "Should have 0 operators after unregister"); // Register same operator again bytes memory keyNew = hex"0000000000000000000000000000000000000000000000000000000000001112"; @@ -258,13 +258,13 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate operator skipEpoch(); assertEq( - IBaseMiddleware(address(middleware)).activeOperators().length, + IBaseMiddlewareReader(address(middleware)).activeOperators().length, 1, "Should have 1 active operator after reregistration" ); assertTrue( - IBaseMiddleware(address(middleware)).operatorWasActiveAt( - IBaseMiddleware(address(middleware)).getCaptureTimestamp(), operator + IBaseMiddlewareReader(address(middleware)).operatorWasActiveAt( + IBaseMiddlewareReader(address(middleware)).getCaptureTimestamp(), operator ), "Operator should be active after reregistration" ); @@ -284,7 +284,7 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.registerOperator(operator, key, address(0)); vm.warp(middleware.getEpochStart(1) - 1); assertEq( - IBaseMiddleware(address(middleware)).activeOperators().length, + IBaseMiddlewareReader(address(middleware)).activeOperators().length, 0, "Should have no active operators before epoch" ); @@ -292,7 +292,7 @@ contract OperatorsRegistrationTest is POCBaseTest { // Check right at epoch boundary vm.warp(middleware.getEpochStart(1)); assertEq( - IBaseMiddleware(address(middleware)).activeOperators().length, + IBaseMiddlewareReader(address(middleware)).activeOperators().length, 1, "Should have 1 active operator at epoch start" ); @@ -317,14 +317,14 @@ contract OperatorsRegistrationTest is POCBaseTest { vm.warp(currentEpochStart - 1); middleware.pauseOperator(operator); assertTrue( - IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentEpochStart - 1, operator), + IBaseMiddlewareReader(address(middleware)).operatorWasActiveAt(currentEpochStart - 1, operator), "Operator should be active before pause" ); // Check status at capture timestamp vm.warp(currentEpochStart); assertFalse( - IBaseMiddleware(address(middleware)).operatorWasActiveAt(currentEpochStart, operator), + IBaseMiddlewareReader(address(middleware)).operatorWasActiveAt(currentEpochStart, operator), "Operator should be inactive at capture" ); @@ -376,13 +376,13 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.registerOperator(operator2, key2, address(0)); // At this point, operator1 should be active, operator2 pending - address[] memory activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + address[] memory activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 1, "Should have 1 active operator"); assertEq(activeOps[0], operator1, "Active operator should be operator1"); // Skip epoch to activate operator2 skipEpoch(); - activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should have 2 active operators"); // Pause operator1, wait partial immutable period, pause operator2 @@ -403,13 +403,13 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate both operators skipEpoch(); - activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 2, "Should have 2 active operators again"); // Test historical activity - uint48 timestamp = IBaseMiddleware(address(middleware)).getCaptureTimestamp(); - assertTrue(IBaseMiddleware(address(middleware)).operatorWasActiveAt(timestamp, operator1)); - assertTrue(IBaseMiddleware(address(middleware)).operatorWasActiveAt(timestamp, operator2)); + uint48 timestamp = IBaseMiddlewareReader(address(middleware)).getCaptureTimestamp(); + assertTrue(IBaseMiddlewareReader(address(middleware)).operatorWasActiveAt(timestamp, operator1)); + assertTrue(IBaseMiddlewareReader(address(middleware)).operatorWasActiveAt(timestamp, operator2)); // Pause both operators again but unregister at different times middleware.pauseOperator(operator1); @@ -424,8 +424,8 @@ contract OperatorsRegistrationTest is POCBaseTest { middleware.unregisterOperator(operator2); // Verify final state - assertEq(IBaseMiddleware(address(middleware)).operatorsLength(), 0, "Should have no operators"); - activeOps = IBaseMiddleware(address(middleware)).activeOperators(); + assertEq(IBaseMiddlewareReader(address(middleware)).operatorsLength(), 0, "Should have no operators"); + activeOps = IBaseMiddlewareReader(address(middleware)).activeOperators(); assertEq(activeOps.length, 0, "Should have no active operators"); } diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol index fb37df1..b147041 100644 --- a/test/SigTests.t.sol +++ b/test/SigTests.t.sol @@ -8,7 +8,7 @@ import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; import {IVaultConfigurator} from "@symbiotic/interfaces/IVaultConfigurator.sol"; import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {IBaseMiddleware} from "../src/interfaces/IBaseMiddleware.sol"; +import {IBaseMiddlewareReader} from "../src/interfaces/IBaseMiddlewareReader.sol"; import {BaseMiddlewareReader} from "../src/middleware/BaseMiddlewareReader.sol"; import {SelfRegisterMiddleware} from "../src/examples/self-register-network/SelfRegisterMiddleware.sol"; @@ -87,13 +87,13 @@ contract SigTests is POCBaseTest { ed25519Middleware.registerOperator(abi.encode(key), address(vaultEd), signature); // Verify operator is registered correctly - assertTrue(IBaseMiddleware(address(ed25519Middleware)).isOperatorRegistered(ed25519Operator)); + assertTrue(IBaseMiddlewareReader(address(ed25519Middleware)).isOperatorRegistered(ed25519Operator)); assertEq( - abi.decode(IBaseMiddleware(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), bytes32(0) + abi.decode(IBaseMiddlewareReader(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), bytes32(0) ); vm.warp(block.timestamp + 2); - assertEq(abi.decode(IBaseMiddleware(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), key); + assertEq(abi.decode(IBaseMiddlewareReader(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), key); } function testEd25519RegisterOperatorInvalidSignature() public { @@ -139,11 +139,11 @@ contract SigTests is POCBaseTest { middleware.registerOperator(abi.encode(operatorPublicKey), address(vault), signature); // Verify operator is registered correctly - assertTrue(IBaseMiddleware(address(middleware)).isOperatorRegistered(operator)); + assertTrue(IBaseMiddlewareReader(address(middleware)).isOperatorRegistered(operator)); - assertEq(abi.decode(IBaseMiddleware(address(middleware)).operatorKey(operator), (bytes32)), bytes32(0)); + assertEq(abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), bytes32(0)); vm.warp(vm.getBlockTimestamp() + 100); - assertEq(abi.decode(IBaseMiddleware(address(middleware)).operatorKey(operator), (bytes32)), operatorPublicKey); + assertEq(abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), operatorPublicKey); } function testSelxfRegisterOperatorInvalidSignature() public { From 52567e011961ccaa6a83d1542aa5d227617c417e Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 6 Dec 2024 18:24:26 +0400 Subject: [PATCH 096/115] chore: lint --- .../managers/access/OzAccessControl.sol | 2 +- .../ForcePauseSelfRegisterOperators.sol | 3 ++- src/interfaces/extensions/ISharedVaults.sol | 16 +++++++++++---- src/interfaces/extensions/ISubnetworks.sol | 16 +++++++++++---- .../managers/access/IOwnableAccessManager.sol | 4 +++- .../managers/access/IOzAccessControl.sol | 8 ++++++-- .../capture-timestamps/IEpochCapture.sol | 4 +++- .../extensions/managers/sigs/IEdDSASig.sol | 2 +- .../IForcePauseSelfRegisterOperators.sol | 8 ++++++-- .../extensions/operators/IOperators.sol | 12 ++++++++--- .../operators/ISelfRegisterOperators.sol | 20 ++++++++++++++----- src/managers/VaultManager.sol | 1 + src/managers/storages/NetworkStorage.sol | 3 +-- test/OperatorsRegistration.t.sol | 8 ++++++-- test/SigTests.t.sol | 11 +++++++--- 15 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/extensions/managers/access/OzAccessControl.sol b/src/extensions/managers/access/OzAccessControl.sol index 05bb9c9..bb832ba 100644 --- a/src/extensions/managers/access/OzAccessControl.sol +++ b/src/extensions/managers/access/OzAccessControl.sol @@ -34,7 +34,7 @@ abstract contract OzAccessControl is AccessManager, IOzAccessControl { $.slot := OzAccessControlStorageLocation } } - + /** * @notice Initializes the contract with a default admin * @param defaultAdmin The address to set as the default admin diff --git a/src/extensions/operators/ForcePauseSelfRegisterOperators.sol b/src/extensions/operators/ForcePauseSelfRegisterOperators.sol index 7796d9c..814a716 100644 --- a/src/extensions/operators/ForcePauseSelfRegisterOperators.sol +++ b/src/extensions/operators/ForcePauseSelfRegisterOperators.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.25; import {SelfRegisterOperators} from "./SelfRegisterOperators.sol"; -import {IForcePauseSelfRegisterOperators} from "../../interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol"; +import {IForcePauseSelfRegisterOperators} from + "../../interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol"; /** * @title ForcePauseSelfRegisterOperators diff --git a/src/interfaces/extensions/ISharedVaults.sol b/src/interfaces/extensions/ISharedVaults.sol index 537654d..612669c 100644 --- a/src/interfaces/extensions/ISharedVaults.sol +++ b/src/interfaces/extensions/ISharedVaults.sol @@ -10,23 +10,31 @@ interface ISharedVaults { * @notice Registers a new shared vault * @param sharedVault The address of the vault to register */ - function registerSharedVault(address sharedVault) external; + function registerSharedVault( + address sharedVault + ) external; /** * @notice Pauses a shared vault * @param sharedVault The address of the vault to pause */ - function pauseSharedVault(address sharedVault) external; + function pauseSharedVault( + address sharedVault + ) external; /** * @notice Unpauses a shared vault * @param sharedVault The address of the vault to unpause */ - function unpauseSharedVault(address sharedVault) external; + function unpauseSharedVault( + address sharedVault + ) external; /** * @notice Unregisters a shared vault * @param sharedVault The address of the vault to unregister */ - function unregisterSharedVault(address sharedVault) external; + function unregisterSharedVault( + address sharedVault + ) external; } diff --git a/src/interfaces/extensions/ISubnetworks.sol b/src/interfaces/extensions/ISubnetworks.sol index aea95fe..024a467 100644 --- a/src/interfaces/extensions/ISubnetworks.sol +++ b/src/interfaces/extensions/ISubnetworks.sol @@ -10,23 +10,31 @@ interface ISubnetworks { * @notice Registers a new subnetwork * @param subnetwork The ID of the subnetwork to register */ - function registerSubnetwork(uint96 subnetwork) external; + function registerSubnetwork( + uint96 subnetwork + ) external; /** * @notice Pauses a subnetwork * @param subnetwork The ID of the subnetwork to pause */ - function pauseSubnetwork(uint96 subnetwork) external; + function pauseSubnetwork( + uint96 subnetwork + ) external; /** * @notice Unpauses a subnetwork * @param subnetwork The ID of the subnetwork to unpause */ - function unpauseSubnetwork(uint96 subnetwork) external; + function unpauseSubnetwork( + uint96 subnetwork + ) external; /** * @notice Unregisters a subnetwork * @param subnetwork The ID of the subnetwork to unregister */ - function unregisterSubnetwork(uint96 subnetwork) external; + function unregisterSubnetwork( + uint96 subnetwork + ) external; } diff --git a/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol b/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol index 3d0a5ed..5ac3f70 100644 --- a/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol +++ b/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol @@ -29,5 +29,7 @@ interface IOwnableAccessManager { * @param owner_ The new owner address * @dev Can only be called by the current owner */ - function setOwner(address owner_) external; + function setOwner( + address owner_ + ) external; } diff --git a/src/interfaces/extensions/managers/access/IOzAccessControl.sol b/src/interfaces/extensions/managers/access/IOzAccessControl.sol index e6df52d..833af62 100644 --- a/src/interfaces/extensions/managers/access/IOzAccessControl.sol +++ b/src/interfaces/extensions/managers/access/IOzAccessControl.sol @@ -27,14 +27,18 @@ interface IOzAccessControl { * @param role The role to get the admin for * @return bytes32 The admin role */ - function getRoleAdmin(bytes32 role) external view returns (bytes32); + function getRoleAdmin( + bytes32 role + ) external view returns (bytes32); /** * @notice Returns the role required for a function selector * @param selector The function selector * @return bytes32 The required role */ - function getRole(bytes4 selector) external view returns (bytes32); + function getRole( + bytes4 selector + ) external view returns (bytes32); /** * @notice Grants role to account if caller has admin role diff --git a/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol b/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol index 5e97b57..8c97d12 100644 --- a/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol +++ b/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol @@ -12,7 +12,9 @@ interface IEpochCapture { * @param epoch The epoch number * @return The start timestamp */ - function getEpochStart(uint48 epoch) external view returns (uint48); + function getEpochStart( + uint48 epoch + ) external view returns (uint48); /** * @notice Returns the current epoch number diff --git a/src/interfaces/extensions/managers/sigs/IEdDSASig.sol b/src/interfaces/extensions/managers/sigs/IEdDSASig.sol index bffc7b3..1826eda 100644 --- a/src/interfaces/extensions/managers/sigs/IEdDSASig.sol +++ b/src/interfaces/extensions/managers/sigs/IEdDSASig.sol @@ -16,4 +16,4 @@ interface IEdDSASig { * @dev Wrapper around Ed25519.verify which handles decompression and curve operations */ function verify(bytes memory message, bytes memory signature, bytes32 key) external returns (bool); -} \ No newline at end of file +} diff --git a/src/interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol b/src/interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol index c55a155..1a09b5f 100644 --- a/src/interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol +++ b/src/interfaces/extensions/operators/IForcePauseSelfRegisterOperators.sol @@ -15,13 +15,17 @@ interface IForcePauseSelfRegisterOperators is ISelfRegisterOperators { * @notice Forces an operator to be paused * @param operator The address of the operator to pause */ - function forcePauseOperator(address operator) external; + function forcePauseOperator( + address operator + ) external; /** * @notice Forces an operator to be unpaused * @param operator The address of the operator to unpause */ - function forceUnpauseOperator(address operator) external; + function forceUnpauseOperator( + address operator + ) external; /** * @notice Forces a specific operator-vault pair to be paused diff --git a/src/interfaces/extensions/operators/IOperators.sol b/src/interfaces/extensions/operators/IOperators.sol index 85ff56c..81c5a30 100644 --- a/src/interfaces/extensions/operators/IOperators.sol +++ b/src/interfaces/extensions/operators/IOperators.sol @@ -18,19 +18,25 @@ interface IOperators { * @notice Unregisters an operator * @param operator The address of the operator to unregister */ - function unregisterOperator(address operator) external; + function unregisterOperator( + address operator + ) external; /** * @notice Pauses an operator * @param operator The address of the operator to pause */ - function pauseOperator(address operator) external; + function pauseOperator( + address operator + ) external; /** * @notice Unpauses an operator * @param operator The address of the operator to unpause */ - function unpauseOperator(address operator) external; + function unpauseOperator( + address operator + ) external; /** * @notice Updates an operator's public key diff --git a/src/interfaces/extensions/operators/ISelfRegisterOperators.sol b/src/interfaces/extensions/operators/ISelfRegisterOperators.sol index ae0943d..9a6fe8b 100644 --- a/src/interfaces/extensions/operators/ISelfRegisterOperators.sol +++ b/src/interfaces/extensions/operators/ISelfRegisterOperators.sol @@ -13,7 +13,9 @@ interface ISelfRegisterOperators { * @param operator The operator address to check * @return The current nonce value */ - function nonces(address operator) external view returns (uint256); + function nonces( + address operator + ) external view returns (uint256); /** * @notice Allows an operator to self-register with a key and optional vault @@ -100,7 +102,9 @@ interface ISelfRegisterOperators { * @notice Allows an operator to register a vault association * @param vault The address of the vault to associate */ - function registerOperatorVault(address vault) external; + function registerOperatorVault( + address vault + ) external; /** * @notice Registers a vault association with signature verification @@ -114,7 +118,9 @@ interface ISelfRegisterOperators { * @notice Allows an operator to unregister a vault association * @param vault The address of the vault to unregister */ - function unregisterOperatorVault(address vault) external; + function unregisterOperatorVault( + address vault + ) external; /** * @notice Unregisters a vault association with signature verification @@ -128,7 +134,9 @@ interface ISelfRegisterOperators { * @notice Allows an operator to pause a vault association * @param vault The address of the vault to pause */ - function pauseOperatorVault(address vault) external; + function pauseOperatorVault( + address vault + ) external; /** * @notice Pauses a vault association with signature verification @@ -142,7 +150,9 @@ interface ISelfRegisterOperators { * @notice Allows an operator to unpause a vault association * @param vault The address of the vault to unpause */ - function unpauseOperatorVault(address vault) external; + function unpauseOperatorVault( + address vault + ) external; /** * @notice Unpauses a vault association with signature verification diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 07666bc..120e22f 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -70,6 +70,7 @@ abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, Capture enum SlasherType { INSTANT, // Instant slasher type VETO // Veto slasher type + } enum DelegatorType { diff --git a/src/managers/storages/NetworkStorage.sol b/src/managers/storages/NetworkStorage.sol index b1df46b..46cdb01 100644 --- a/src/managers/storages/NetworkStorage.sol +++ b/src/managers/storages/NetworkStorage.sol @@ -5,8 +5,7 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini abstract contract NetworkStorage is Initializable { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.NetworkStorage")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant NetworkStorageLocation = - 0x779150488f5e984d1f840ba606e388ada6c73b44f261274c3595c61a30023e00; + bytes32 private constant NetworkStorageLocation = 0x779150488f5e984d1f840ba606e388ada6c73b44f261274c3595c61a30023e00; /** * @notice Initializes the NetworkManager contract diff --git a/test/OperatorsRegistration.t.sol b/test/OperatorsRegistration.t.sol index 8b2cdc2..549bd28 100644 --- a/test/OperatorsRegistration.t.sol +++ b/test/OperatorsRegistration.t.sol @@ -235,7 +235,9 @@ contract OperatorsRegistrationTest is POCBaseTest { // Skip epoch to activate operator skipEpoch(); - assertEq(IBaseMiddlewareReader(address(middleware)).activeOperators().length, 1, "Should have 1 active operator"); + assertEq( + IBaseMiddlewareReader(address(middleware)).activeOperators().length, 1, "Should have 1 active operator" + ); // Pause operator middleware.pauseOperator(operator); @@ -249,7 +251,9 @@ contract OperatorsRegistrationTest is POCBaseTest { // Wait for immutable period and unregister skipImmutablePeriod(); middleware.unregisterOperator(operator); - assertEq(IBaseMiddlewareReader(address(middleware)).operatorsLength(), 0, "Should have 0 operators after unregister"); + assertEq( + IBaseMiddlewareReader(address(middleware)).operatorsLength(), 0, "Should have 0 operators after unregister" + ); // Register same operator again bytes memory keyNew = hex"0000000000000000000000000000000000000000000000000000000000001112"; diff --git a/test/SigTests.t.sol b/test/SigTests.t.sol index b147041..072b867 100644 --- a/test/SigTests.t.sol +++ b/test/SigTests.t.sol @@ -90,10 +90,13 @@ contract SigTests is POCBaseTest { assertTrue(IBaseMiddlewareReader(address(ed25519Middleware)).isOperatorRegistered(ed25519Operator)); assertEq( - abi.decode(IBaseMiddlewareReader(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), bytes32(0) + abi.decode(IBaseMiddlewareReader(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), + bytes32(0) ); vm.warp(block.timestamp + 2); - assertEq(abi.decode(IBaseMiddlewareReader(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), key); + assertEq( + abi.decode(IBaseMiddlewareReader(address(ed25519Middleware)).operatorKey(ed25519Operator), (bytes32)), key + ); } function testEd25519RegisterOperatorInvalidSignature() public { @@ -143,7 +146,9 @@ contract SigTests is POCBaseTest { assertEq(abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), bytes32(0)); vm.warp(vm.getBlockTimestamp() + 100); - assertEq(abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), operatorPublicKey); + assertEq( + abi.decode(IBaseMiddlewareReader(address(middleware)).operatorKey(operator), (bytes32)), operatorPublicKey + ); } function testSelxfRegisterOperatorInvalidSignature() public { From 623be0a85b7c958a7e6bd44c3c7a38457fc3e534 Mon Sep 17 00:00:00 2001 From: alrxy Date: Fri, 6 Dec 2024 18:26:28 +0400 Subject: [PATCH 097/115] chore: wip and under audits mention --- README.md | 57 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index c0ff681..8a9959d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# Middleware Development Guide +# Middleware Development Guide [**!!!WIP!!!**] + +**Warning: The SDK is a work in progress and is currently under audits. Use with caution.** This repository provides a framework for developing middleware in a modular and extensible way. It leverages various base contracts and extensions to handle key functionalities such as operator management, access control, key storage, timestamp capturing and stake to power calculation. @@ -10,7 +12,7 @@ This repository provides a framework for developing middleware in a modular and - **Operators**: Manages operator registration and operator's vault. - - **KeyStorage**: Manages operator keys. Variants include `KeyStorage256`, `KeyStorageBytes`, and `NoKeyStorage`. + - **KeyManager**: Manages operator keys. Variants include `KeyManager256`, `KeyManagerBytes`, and `NoKeyManager`. - **AccessManager**: Controls access to restricted functions. Implementations include `OwnableAccessManager`, `OzAccessControl`, `OzAccessManaged`, and `NoAccessManager`. @@ -86,15 +88,12 @@ Features: To develop your middleware: -1. **Inherit from `BaseMiddleware`**: This provides access to core functionalities. - -2. **Choose Extensions**: Based on your requirements, include extensions for operator management, key storage, access control, and timestamp capturing. +1. **Choose Extensions**: Based on your requirements, include extensions for operator management, key storage, access control, and timestamp capturing. -3. **Initialize Properly**: Ensure all inherited contracts are properly initialized: - - - Use the `initializer` modifier on your initialization function +2. **Initialize Properly**: Ensure all inherited contracts are properly initialized: + - Write an initialization function with the `initializer` modifier - Call `_disableInitializers()` in the constructor for upgradeable contracts - - Initialize all inherited contracts in the correct order + - Initialize `BaseMiddleware` and extensions in the correct order - Pass required parameters to each contract's initialization function - Follow initialization order from most base to most derived contract - Note: If your contract is not upgradeable, initialization can be done directly in the constructor: @@ -105,9 +104,10 @@ To develop your middleware: address vaultRegistry, address operatorRegistry, address operatorNetOptIn, + address readHelper, address admin ) { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, admin); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, readHelper, admin); } ``` - Example initialization pattern: @@ -118,39 +118,43 @@ To develop your middleware: address vaultRegistry, address operatorRegistry, address operatorNetOptIn, + address readHelper, address admin ) public initializer { - __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, readHelper); __OzAccessManaged_init(admin); __AdditionalExtension_init(); } ``` -4. **Implement Required Functions**: Override functions as needed to implement your middleware's logic. +3. **Implement Required Functions** (Optional): Override functions as needed to implement your middleware's logic. Additionally, implement your own functions to extend the middleware's capabilities. ## Example: Creating a Custom Middleware ```solidity -contract MyCustomMiddleware is BaseMiddleware, Operators, KeyStorage256, OwnableAccessManager, TimestampCapture { +contract MyCustomMiddleware is BaseMiddleware, Operators, KeyStorage256, OwnableAccessManager { uint64 public constant MyCustomMiddleware_VERSION = 1; - function initialize( - address network, - uint48 slashingWindow, - address vaultRegistry, - address operatorRegistry, - address operatorNetOptIn, - address owner - ) public initializer { - super.initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn); - __OwnableAccessManaged_init(owner); + /** + * @notice Override getCaptureTimestamp to provide custom timestamp logic + * @return timestamp The current block timestamp + */ + function getCaptureTimestamp() public view override returns (uint48 timestamp) { + return uint48(block.timestamp); } - // Additional implementation... + /** + * @notice Custom function to calculate the square of a given number + * @param number The number to be squared + * @return result The square of the given number + */ + function calculateSquare(uint256 number) public pure returns (uint256 result) { + return number * number; + } } ``` -5. **Configure OzAccessControl Roles**: When using OzAccessControl, set up roles and permissions: +4. **Configure Access Control** (Optional): When using access control extensions, set up roles and permissions: ```solidity // Define role identifiers as constants @@ -192,8 +196,9 @@ contract MyCustomMiddleware is BaseMiddleware, Operators, KeyStorage256, Ownable 2. Setting role admins with `_setRoleAdmin(bytes32 role, bytes32 adminRole)` 3. Assigning roles to function selectors via `_setSelectorRole(bytes4 selector, bytes32 role)` - `OzAccessManaged`: Wraps OpenZeppelin's AccessManaged contract to integrate with external access control systems + - `OzAccessManaged`: Wraps OpenZeppelin's AccessManaged contract to integrate with external access control systems. This allows for more complex access control scenarios where permissions are managed externally, providing flexibility and scalability in managing roles and permissions. -- **Key Storage**: Select a `KeyStorage` implementation that fits your key requirements. Use `KeyStorage256` for 256-bit keys, `KeyStorageBytes` for arbitrary-length keys, or `NoKeyStorage` if keys are not needed. +- **Key Manager**: Choose a `KeyManager` implementation that suits your key management needs. Use `KeyManager256` for managing 256-bit keys, `KeyManagerBytes` for handling arbitrary-length keys, or `NoKeyManager` if key management is not required. This framework provides flexibility in building middleware by allowing you to mix and match various extensions based on your requirements. By following the modular approach and best practices outlined, you can develop robust middleware solutions that integrate seamlessly with the network. From 3a3066502991b089383cf9889f6f3da305c5aa6d Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:02:04 +0400 Subject: [PATCH 098/115] fix: statemind-Unable to unregister operator's key --- src/libraries/PauseableEnumerableSet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/PauseableEnumerableSet.sol b/src/libraries/PauseableEnumerableSet.sol index 9b05215..d086d7f 100644 --- a/src/libraries/PauseableEnumerableSet.sol +++ b/src/libraries/PauseableEnumerableSet.sol @@ -142,7 +142,7 @@ library PauseableEnumerableSet { uint48 timestamp, uint48 immutablePeriod ) internal view returns (bool) { - return self.enabled != 0 && self.disabled == 0 && self.disabled + immutablePeriod > timestamp; + return self.enabled == 0 && self.disabled != 0 && self.disabled + immutablePeriod <= timestamp; } /** From d3aa3407de34cf11298ecdbdbdf6a553389fed66 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:04:31 +0400 Subject: [PATCH 099/115] fix: statemind-Extra memory value calls in SimplePosMiddleware._slash() and SqrtTaskMiddleware.slash() --- .../simple-pos-network/SimplePosMiddleware.sol | 10 ++++++---- src/examples/sqrt-task-network/SqrtTaskMiddleware.sol | 5 +++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 4435c35..e2473c3 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -146,19 +146,21 @@ contract SimplePosMiddleware is params.totalPower = _getOperatorPowerAt(params.epochStart, params.operator); params.vaults = _activeVaultsAt(params.epochStart, params.operator); params.subnetworks = _activeSubnetworksAt(params.epochStart); + uint256 vaultsLength = params.vaults.length; + uint256 subnetworksLength = params.subnetworks.length; // Validate hints lengths upfront - if (stakeHints.length != slashHints.length || stakeHints.length != params.vaults.length) { + if (stakeHints.length != slashHints.length || stakeHints.length != vaultsLength) { revert InvalidHints(); } - for (uint256 i; i < params.vaults.length; ++i) { - if (stakeHints[i].length != params.subnetworks.length) { + for (uint256 i; i < vaultsLength; ++i) { + if (stakeHints[i].length != subnetworksLength) { revert InvalidHints(); } address vault = params.vaults[i]; - for (uint256 j; j < params.subnetworks.length; ++j) { + for (uint256 j; j < subnetworksLength; ++j) { bytes32 subnetwork = _NETWORK().subnetwork(uint96(params.subnetworks[j])); uint256 stake = IBaseDelegator(IVault(vault).delegator()).stakeAt( subnetwork, params.operator, params.epochStart, stakeHints[i][j] diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 9fe53f1..b5868a9 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -145,13 +145,14 @@ contract SqrtTaskMiddleware is function _slash(uint256 taskIndex, bytes[] calldata stakeHints, bytes[] calldata slashHints) private { Task storage task = tasks[taskIndex]; address[] memory vaults = _activeVaultsAt(task.captureTimestamp, task.operator); + uint256 vaultsLength = vaults.length; - if (stakeHints.length != slashHints.length || stakeHints.length != vaults.length) { + if (stakeHints.length != slashHints.length || stakeHints.length != vaultsLength) { revert InvalidHints(); } bytes32 subnetwork = _NETWORK().subnetwork(0); - for (uint256 i; i < vaults.length; ++i) { + for (uint256 i; i < vaultsLength; ++i) { address vault = vaults[i]; uint256 slashAmount = IBaseDelegator(IVault(vault).delegator()).stakeAt( subnetwork, task.operator, task.captureTimestamp, stakeHints[i] From 4cfbacd50c9e29e465b4e002d6225d0e6cca3064 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:15:45 +0400 Subject: [PATCH 100/115] fix: statemind-SimplePosMiddleware gas optimizations --- .../SimplePosMiddleware.sol | 9 ++---- src/interfaces/IBaseMiddlewareReader.sol | 13 ++++++++ src/managers/VaultManager.sol | 32 +++++++++++++++++++ src/middleware/BaseMiddlewareReader.sol | 17 ++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index e2473c3..760d68a 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -141,11 +141,11 @@ contract SimplePosMiddleware is params.epochStart = getEpochStart(epoch); params.operator = operatorByKey(abi.encode(key)); - _checkCanSlash(epoch, key); + _checkCanSlash(params.epochStart, key, params.operator); - params.totalPower = _getOperatorPowerAt(params.epochStart, params.operator); params.vaults = _activeVaultsAt(params.epochStart, params.operator); params.subnetworks = _activeSubnetworksAt(params.epochStart); + params.totalPower = _getOperatorPower(params.operator, params.vaults, params.subnetworks); uint256 vaultsLength = params.vaults.length; uint256 subnetworksLength = params.subnetworks.length; @@ -176,10 +176,7 @@ contract SimplePosMiddleware is } } - function _checkCanSlash(uint48 epoch, bytes32 key) internal view { - address operator = operatorByKey(abi.encode(key)); // Get the operator associated with the key - uint48 epochStart = getEpochStart(epoch); // Get the start timestamp for the epoch - + function _checkCanSlash(uint48 epochStart, bytes32 key, address operator) internal view { if (operator == address(0)) { revert NotExistKeySlash(); // Revert if the operator does not exist } diff --git a/src/interfaces/IBaseMiddlewareReader.sol b/src/interfaces/IBaseMiddlewareReader.sol index 9139eba..4914430 100644 --- a/src/interfaces/IBaseMiddlewareReader.sol +++ b/src/interfaces/IBaseMiddlewareReader.sol @@ -115,6 +115,19 @@ interface IBaseMiddlewareReader { function getOperatorPowerAt(uint48 timestamp, address operator) external view returns (uint256); + function getOperatorPower( + address operator, + address[] memory vaults, + uint160[] memory subnetworks + ) external view returns (uint256); + + function getOperatorPowerAt( + uint48 timestamp, + address operator, + address[] memory vaults, + uint160[] memory subnetworks + ) external view returns (uint256); + function totalPower( address[] memory operators ) external view returns (uint256); diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 120e22f..478f35d 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -441,6 +441,38 @@ abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, Capture address[] memory vaults = _activeVaultsAt(timestamp, operator); uint160[] memory subnetworks = _activeSubnetworksAt(timestamp); + return _getOperatorPower(operator, vaults, subnetworks); + } + + /** + * @notice Gets the total power amount for an operator across all vaults and subnetworks + * @param operator The operator address + * @param vaults The list of vault addresses + * @param subnetworks The list of subnetwork identifiers + * @return power The total power amount + */ + function _getOperatorPower( + address operator, + address[] memory vaults, + uint160[] memory subnetworks + ) internal view returns (uint256 power) { + return _getOperatorPowerAt(getCaptureTimestamp(), operator, vaults, subnetworks); + } + + /** + * @notice Gets the total power amount for an operator across all vaults and subnetworks at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @param vaults The list of vault addresses + * @param subnetworks The list of subnetwork identifiers + * @return power The total power amount at the timestamp + */ + function _getOperatorPowerAt( + uint48 timestamp, + address operator, + address[] memory vaults, + uint160[] memory subnetworks + ) internal view returns (uint256 power) { for (uint256 i; i < vaults.length; ++i) { address vault = vaults[i]; for (uint256 j; j < subnetworks.length; ++j) { diff --git a/src/middleware/BaseMiddlewareReader.sol b/src/middleware/BaseMiddlewareReader.sol index e0a25e2..33231fe 100644 --- a/src/middleware/BaseMiddlewareReader.sol +++ b/src/middleware/BaseMiddlewareReader.sol @@ -189,6 +189,23 @@ contract BaseMiddlewareReader is BaseMiddleware, NoAccessManager, NoKeyManager { return _getOperatorPowerAt(timestamp, operator); } + function getOperatorPower( + address operator, + address[] memory vaults, + uint160[] memory subnetworks + ) external view returns (uint256) { + return _getOperatorPower(operator, vaults, subnetworks); + } + + function getOperatorPowerAt( + uint48 timestamp, + address operator, + address[] memory vaults, + uint160[] memory subnetworks + ) external view returns (uint256) { + return _getOperatorPowerAt(timestamp, operator, vaults, subnetworks); + } + function totalPower( address[] memory operators ) external view returns (uint256) { From 838b0e6fa621ae6f05663125b31cce643a59038a Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:18:02 +0400 Subject: [PATCH 101/115] fix: statemind-Redundant module in SqrtTaskMiddleware --- src/extensions/managers/keys/NoKeyManager.sol | 20 +++++-------------- src/middleware/BaseMiddlewareReader.sol | 2 +- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/extensions/managers/keys/NoKeyManager.sol b/src/extensions/managers/keys/NoKeyManager.sol index 87b311f..4c75ebb 100644 --- a/src/extensions/managers/keys/NoKeyManager.sol +++ b/src/extensions/managers/keys/NoKeyManager.sol @@ -6,13 +6,11 @@ import {KeyManager} from "../../../managers/extendable/KeyManager.sol"; /** * @title NoKeyManager * @notice A middleware extension that provides no key storage functionality - * @dev Implements KeyManager and always reverts on key operations + * @dev This contract implements the KeyManager interface but does not provide any key storage functionality. */ abstract contract NoKeyManager is KeyManager { uint64 public constant NoKeyManager_VERSION = 1; - error KeyManagerDisabled(); - /** * @notice Gets the operator address associated with a key * @param key The key to lookup (unused) @@ -20,9 +18,7 @@ abstract contract NoKeyManager is KeyManager { */ function operatorByKey( bytes memory key - ) public pure override returns (address) { - revert KeyManagerDisabled(); - } + ) public pure override returns (address) {} /** * @notice Gets an operator's active key @@ -31,9 +27,7 @@ abstract contract NoKeyManager is KeyManager { */ function operatorKey( address operator - ) public pure override returns (bytes memory) { - revert KeyManagerDisabled(); - } + ) public pure override returns (bytes memory) {} /** * @notice Checks if a key was active at a specific timestamp @@ -41,16 +35,12 @@ abstract contract NoKeyManager is KeyManager { * @param key The key to check (unused) * @return Whether key was active (always reverts) */ - function keyWasActiveAt(uint48 timestamp, bytes memory key) public pure override returns (bool) { - revert KeyManagerDisabled(); - } + function keyWasActiveAt(uint48 timestamp, bytes memory key) public pure override returns (bool) {} /** * @notice Updates an operator's key * @param operator The operator address (unused) * @param key The new key (unused) */ - function _updateKey(address operator, bytes memory key) internal virtual override { - revert KeyManagerDisabled(); - } + function _updateKey(address operator, bytes memory key) internal virtual override {} } diff --git a/src/middleware/BaseMiddlewareReader.sol b/src/middleware/BaseMiddlewareReader.sol index 33231fe..ca9fe2e 100644 --- a/src/middleware/BaseMiddlewareReader.sol +++ b/src/middleware/BaseMiddlewareReader.sol @@ -212,7 +212,7 @@ contract BaseMiddlewareReader is BaseMiddleware, NoAccessManager, NoKeyManager { return _totalPower(operators); } - function _getMiddleware() private view returns (address) { + function _getMiddleware() private pure returns (address) { address middleware; assembly { middleware := shr(96, calldataload(sub(calldatasize(), 20))) From 140db6b23a07c8a029dc0954220a8dda9596c129 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:32:06 +0400 Subject: [PATCH 102/115] fix:statemind-Inconsistency between Epoch and Timestamp --- src/extensions/managers/capture-timestamps/EpochCapture.sol | 6 +++++- src/extensions/managers/keys/KeyManager256.sol | 1 - test/OperatorsRegistration.t.sol | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/extensions/managers/capture-timestamps/EpochCapture.sol b/src/extensions/managers/capture-timestamps/EpochCapture.sol index 4eed69a..b61bf4e 100644 --- a/src/extensions/managers/capture-timestamps/EpochCapture.sol +++ b/src/extensions/managers/capture-timestamps/EpochCapture.sol @@ -56,7 +56,11 @@ abstract contract EpochCapture is CaptureTimestampManager, IEpochCapture { */ function getCurrentEpoch() public view returns (uint48) { EpochCaptureStorage storage $ = _getEpochCaptureStorage(); - return (_now() - $.startTimestamp) / $.epochDuration; + if (_now() == $.startTimestamp) { + return 0; + } + + return (_now() - $.startTimestamp - 1) / $.epochDuration; } /* diff --git a/src/extensions/managers/keys/KeyManager256.sol b/src/extensions/managers/keys/KeyManager256.sol index 9de8690..6faddb4 100644 --- a/src/extensions/managers/keys/KeyManager256.sol +++ b/src/extensions/managers/keys/KeyManager256.sol @@ -15,7 +15,6 @@ abstract contract KeyManager256 is KeyManager { using PauseableEnumerableSet for PauseableEnumerableSet.Bytes32Set; error DuplicateKey(); - error KeyAlreadyEnabled(); error MaxDisabledKeysReached(); bytes32 private constant ZERO_BYTES32 = bytes32(0); diff --git a/test/OperatorsRegistration.t.sol b/test/OperatorsRegistration.t.sol index 549bd28..67df381 100644 --- a/test/OperatorsRegistration.t.sol +++ b/test/OperatorsRegistration.t.sol @@ -54,6 +54,8 @@ contract OperatorsRegistrationTest is POCBaseTest { ); _registerNetwork(network, address(middleware)); + + vm.warp(vm.getBlockTimestamp() + 1); } function testOperators() public { @@ -286,7 +288,7 @@ contract OperatorsRegistrationTest is POCBaseTest { // Register operator just before epoch boundary middleware.registerOperator(operator, key, address(0)); - vm.warp(middleware.getEpochStart(1) - 1); + vm.warp(middleware.getEpochStart(1)); assertEq( IBaseMiddlewareReader(address(middleware)).activeOperators().length, 0, @@ -294,7 +296,7 @@ contract OperatorsRegistrationTest is POCBaseTest { ); // Check right at epoch boundary - vm.warp(middleware.getEpochStart(1)); + vm.warp(middleware.getEpochStart(1) + 1); assertEq( IBaseMiddlewareReader(address(middleware)).activeOperators().length, 1, From eebbe5dc62b199dac33bc1d20db68ccf8a65c560 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:35:42 +0400 Subject: [PATCH 103/115] fix:statemind-Zero check for owner --- .../managers/access/OwnableAccessManager.sol | 15 ++++++++++++--- .../managers/access/IOwnableAccessManager.sol | 6 ++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/extensions/managers/access/OwnableAccessManager.sol b/src/extensions/managers/access/OwnableAccessManager.sol index 8b525fa..7d6000e 100644 --- a/src/extensions/managers/access/OwnableAccessManager.sol +++ b/src/extensions/managers/access/OwnableAccessManager.sol @@ -36,6 +36,10 @@ abstract contract OwnableAccessManager is AccessManager, IOwnableAccessManager { function _setOwner( address owner_ ) private { + if (owner_ == address(0)) { + revert InvalidOwner(address(0)); + } + bytes32 location = OwnableAccessManagerStorageLocation; assembly { sstore(location, owner_) @@ -65,9 +69,14 @@ abstract contract OwnableAccessManager is AccessManager, IOwnableAccessManager { function setOwner( address owner_ ) public checkAccess { - if (owner_ == address(0)) { - revert InvalidOwner(address(0)); - } _setOwner(owner_); } + + /** + * @inheritdoc IOwnableAccessManager + */ + function renounceOwnership() public virtual { + _checkAccess(); + _setOwner(address(0)); + } } diff --git a/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol b/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol index 5ac3f70..7d2b224 100644 --- a/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol +++ b/src/interfaces/extensions/managers/access/IOwnableAccessManager.sol @@ -32,4 +32,10 @@ interface IOwnableAccessManager { function setOwner( address owner_ ) external; + + /** + * @notice Renounces the ownership of the contract + * @dev Can only be called by the current owner + */ + function renounceOwnership() external; } From a18cb65026546232addda046b70fe499e019a29b Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:37:55 +0400 Subject: [PATCH 104/115] fix:statemind-Incorrect storage slot location --- src/extensions/managers/keys/KeyManager256.sol | 4 ++-- src/extensions/managers/keys/KeyManagerBytes.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/extensions/managers/keys/KeyManager256.sol b/src/extensions/managers/keys/KeyManager256.sol index 6faddb4..4acd096 100644 --- a/src/extensions/managers/keys/KeyManager256.sol +++ b/src/extensions/managers/keys/KeyManager256.sol @@ -27,9 +27,9 @@ abstract contract KeyManager256 is KeyManager { mapping(bytes32 => address) keyToOperator; } - // keccak256(abi.encode(uint256(keccak256("symbioticfi.storage.KeyManager256")) - 1)) & ~bytes32(uint256(0xff)) + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManager256")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant KeyManager256StorageLocation = - 0x00c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c800; + 0x3da47716e6090d5a5545e03387f4dac112d37cd069a5573bb81de8579bd9dc00; function _getKeyManager256Storage() internal pure returns (KeyManager256Storage storage s) { bytes32 location = KeyManager256StorageLocation; diff --git a/src/extensions/managers/keys/KeyManagerBytes.sol b/src/extensions/managers/keys/KeyManagerBytes.sol index abed3cc..97cff2d 100644 --- a/src/extensions/managers/keys/KeyManagerBytes.sol +++ b/src/extensions/managers/keys/KeyManagerBytes.sol @@ -26,9 +26,9 @@ abstract contract KeyManagerBytes is KeyManager { mapping(bytes => address) _keyToOperator; } - // keccak256(abi.encode(uint256(keccak256("symbioticfi.storage.KeyManagerBytes")) - 1)) & ~bytes32(uint256(0xff)) + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManagerBytes")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant KeyManagerBytesStorageLocation = - 0x00c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c8c5c9c4c3c2c1c0c7c800; + 0x40e4e6d672540d8bc06612820fe8dc1fcfe7e420a91aaea066d48e4af34ab000; function _getKeyManagerBytesStorage() internal pure returns (KeyManagerBytesStorage storage s) { bytes32 location = KeyManagerBytesStorageLocation; From e80e1e3df43451a33e7e61882134a24e600dedd2 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:39:59 +0400 Subject: [PATCH 105/115] fix: statemind-Use custom errors instead of require --- src/extensions/operators/Operators.sol | 4 +++- src/extensions/operators/SelfRegisterOperators.sol | 4 +++- src/interfaces/extensions/operators/IOperators.sol | 2 ++ .../extensions/operators/ISelfRegisterOperators.sol | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/extensions/operators/Operators.sol b/src/extensions/operators/Operators.sol index e9661db..cf71726 100644 --- a/src/extensions/operators/Operators.sol +++ b/src/extensions/operators/Operators.sol @@ -67,7 +67,9 @@ abstract contract Operators is BaseMiddleware, IOperators { * @inheritdoc IOperators */ function registerOperatorVault(address operator, address vault) public checkAccess { - require(_isOperatorRegistered(operator), "Operator not registered"); + if (!_isOperatorRegistered(operator)) { + revert OperatorNotRegistered(); + } _beforeRegisterOperatorVault(operator, vault); _registerOperatorVault(operator, vault); } diff --git a/src/extensions/operators/SelfRegisterOperators.sol b/src/extensions/operators/SelfRegisterOperators.sol index 5699763..8467fe5 100644 --- a/src/extensions/operators/SelfRegisterOperators.sol +++ b/src/extensions/operators/SelfRegisterOperators.sol @@ -210,7 +210,9 @@ abstract contract SelfRegisterOperators is BaseMiddleware, SigManager, EIP712Upg * @inheritdoc ISelfRegisterOperators */ function registerOperatorVault(address operator, address vault, bytes memory signature) public { - require(_isOperatorRegistered(operator), "Operator not registered"); + if (!_isOperatorRegistered(operator)) { + revert OperatorNotRegistered(); + } _beforeRegisterOperatorVault(operator, vault); SelfRegisterOperatorsStorage storage $ = _getSelfRegisterOperatorsStorage(); _verifyEIP712( diff --git a/src/interfaces/extensions/operators/IOperators.sol b/src/interfaces/extensions/operators/IOperators.sol index 81c5a30..1cf4280 100644 --- a/src/interfaces/extensions/operators/IOperators.sol +++ b/src/interfaces/extensions/operators/IOperators.sol @@ -6,6 +6,8 @@ pragma solidity ^0.8.25; * @notice Interface for managing operator registration, keys, and vault relationships */ interface IOperators { + error OperatorNotRegistered(); + /** * @notice Registers a new operator with an optional vault association * @param operator The address of the operator to register diff --git a/src/interfaces/extensions/operators/ISelfRegisterOperators.sol b/src/interfaces/extensions/operators/ISelfRegisterOperators.sol index 9a6fe8b..a103452 100644 --- a/src/interfaces/extensions/operators/ISelfRegisterOperators.sol +++ b/src/interfaces/extensions/operators/ISelfRegisterOperators.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.25; */ interface ISelfRegisterOperators { error InvalidSignature(); + error OperatorNotRegistered(); /** * @notice Returns the nonce for an operator address From 49e456f7a5137daee78f865996f51bd68de6ac29 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:41:31 +0400 Subject: [PATCH 106/115] fix:statemind-Multiple Time.timestamp() calls optimization --- src/extensions/managers/keys/KeyManager256.sol | 11 ++++++----- src/extensions/managers/keys/KeyManagerBytes.sol | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/extensions/managers/keys/KeyManager256.sol b/src/extensions/managers/keys/KeyManager256.sol index 4acd096..d09dc18 100644 --- a/src/extensions/managers/keys/KeyManager256.sol +++ b/src/extensions/managers/keys/KeyManager256.sol @@ -89,6 +89,7 @@ abstract contract KeyManager256 is KeyManager { function _updateKey(address operator, bytes memory key_) internal override { KeyManager256Storage storage s = _getKeyManager256Storage(); bytes32 key = abi.decode(key_, (bytes32)); + uint48 timestamp = _now(); if (s.keyToOperator[key] != address(0)) { revert DuplicateKey(); @@ -103,17 +104,17 @@ abstract contract KeyManager256 is KeyManager { if (s.keys[operator].length() > 0) { // try to remove disabled keys bytes32 prevKey = s.keys[operator].array[0].value; - if (s.keys[operator].checkUnregister(_now(), _SLASHING_WINDOW(), prevKey)) { - s.keys[operator].unregister(_now(), _SLASHING_WINDOW(), prevKey); + if (s.keys[operator].checkUnregister(timestamp, _SLASHING_WINDOW(), prevKey)) { + s.keys[operator].unregister(timestamp, _SLASHING_WINDOW(), prevKey); delete s.keyToOperator[prevKey]; - } else if (s.keys[operator].wasActiveAt(_now(), prevKey)) { - s.keys[operator].pause(_now(), prevKey); + } else if (s.keys[operator].wasActiveAt(timestamp, prevKey)) { + s.keys[operator].pause(timestamp, prevKey); } } if (key != ZERO_BYTES32) { // register the new key - s.keys[operator].register(_now(), key); + s.keys[operator].register(timestamp, key); s.keyToOperator[key] = operator; } } diff --git a/src/extensions/managers/keys/KeyManagerBytes.sol b/src/extensions/managers/keys/KeyManagerBytes.sol index 97cff2d..7de4af9 100644 --- a/src/extensions/managers/keys/KeyManagerBytes.sol +++ b/src/extensions/managers/keys/KeyManagerBytes.sol @@ -87,6 +87,7 @@ abstract contract KeyManagerBytes is KeyManager { function _updateKey(address operator, bytes memory key) internal override { KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); bytes32 keyHash = keccak256(key); + uint48 timestamp = _now(); if ($._keyToOperator[key] != address(0)) { revert DuplicateKey(); @@ -101,17 +102,17 @@ abstract contract KeyManagerBytes is KeyManager { if ($._keys[operator].length() > 0) { // try to remove disabled keys bytes memory prevKey = $._keys[operator].array[0].value; - if ($._keys[operator].checkUnregister(_now(), _SLASHING_WINDOW(), prevKey)) { - $._keys[operator].unregister(_now(), _SLASHING_WINDOW(), prevKey); + if ($._keys[operator].checkUnregister(timestamp, _SLASHING_WINDOW(), prevKey)) { + $._keys[operator].unregister(timestamp, _SLASHING_WINDOW(), prevKey); delete $._keyToOperator[prevKey]; - } else if ($._keys[operator].wasActiveAt(_now(), prevKey)) { - $._keys[operator].pause(_now(), prevKey); + } else if ($._keys[operator].wasActiveAt(timestamp, prevKey)) { + $._keys[operator].pause(timestamp, prevKey); } } if (keyHash != ZERO_BYTES_HASH) { // register the new key - $._keys[operator].register(_now(), key); + $._keys[operator].register(timestamp, key); $._keyToOperator[key] = operator; } } From 43d5462ef05d59b12dd955baa9e3b84a02f78699 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 15:45:56 +0400 Subject: [PATCH 107/115] fix:statemind-Typos --- .../capture-timestamps/EpochCapture.sol | 2 +- .../managers/keys/KeyManager256.sol | 40 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/extensions/managers/capture-timestamps/EpochCapture.sol b/src/extensions/managers/capture-timestamps/EpochCapture.sol index b61bf4e..7efd0bf 100644 --- a/src/extensions/managers/capture-timestamps/EpochCapture.sol +++ b/src/extensions/managers/capture-timestamps/EpochCapture.sol @@ -30,7 +30,7 @@ abstract contract EpochCapture is CaptureTimestampManager, IEpochCapture { } /* - * @notice initalizer of the Epochs contract. + * @notice initializer of the Epochs contract. * @param epochDuration The duration of each epoch. */ function __EpochCapture_init( diff --git a/src/extensions/managers/keys/KeyManager256.sol b/src/extensions/managers/keys/KeyManager256.sol index d09dc18..66408df 100644 --- a/src/extensions/managers/keys/KeyManager256.sol +++ b/src/extensions/managers/keys/KeyManager256.sol @@ -22,9 +22,9 @@ abstract contract KeyManager256 is KeyManager { struct KeyManager256Storage { /// @notice Mapping from operator addresses to their keys - mapping(address => PauseableEnumerableSet.Bytes32Set) keys; + mapping(address => PauseableEnumerableSet.Bytes32Set) _keys; /// @notice Mapping from keys to operator addresses - mapping(bytes32 => address) keyToOperator; + mapping(bytes32 => address) _keyToOperator; } // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.KeyManager256")) - 1)) & ~bytes32(uint256(0xff)) @@ -46,8 +46,8 @@ abstract contract KeyManager256 is KeyManager { function operatorByKey( bytes memory key ) public view override returns (address) { - KeyManager256Storage storage s = _getKeyManager256Storage(); - return s.keyToOperator[abi.decode(key, (bytes32))]; + KeyManager256Storage storage $ = _getKeyManager256Storage(); + return $._keyToOperator[abi.decode(key, (bytes32))]; } /** @@ -58,8 +58,8 @@ abstract contract KeyManager256 is KeyManager { function operatorKey( address operator ) public view override returns (bytes memory) { - KeyManager256Storage storage s = _getKeyManager256Storage(); - bytes32[] memory active = s.keys[operator].getActive(getCaptureTimestamp()); + KeyManager256Storage storage $ = _getKeyManager256Storage(); + bytes32[] memory active = $._keys[operator].getActive(getCaptureTimestamp()); if (active.length == 0) { return abi.encode(ZERO_BYTES32); } @@ -73,9 +73,9 @@ abstract contract KeyManager256 is KeyManager { * @return True if the key was active at the timestamp, false otherwise */ function keyWasActiveAt(uint48 timestamp, bytes memory key_) public view override returns (bool) { - KeyManager256Storage storage s = _getKeyManager256Storage(); + KeyManager256Storage storage $ = _getKeyManager256Storage(); bytes32 key = abi.decode(key_, (bytes32)); - return s.keys[s.keyToOperator[key]].wasActiveAt(timestamp, key); + return $._keys[$._keyToOperator[key]].wasActiveAt(timestamp, key); } /** @@ -87,35 +87,35 @@ abstract contract KeyManager256 is KeyManager { * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key_) internal override { - KeyManager256Storage storage s = _getKeyManager256Storage(); + KeyManager256Storage storage $ = _getKeyManager256Storage(); bytes32 key = abi.decode(key_, (bytes32)); uint48 timestamp = _now(); - if (s.keyToOperator[key] != address(0)) { + if ($._keyToOperator[key] != address(0)) { revert DuplicateKey(); } // check if we have reached the max number of disabled keys // this allow us to limit the number times we can change the key - if (key != ZERO_BYTES32 && s.keys[operator].length() > MAX_DISABLED_KEYS + 1) { + if (key != ZERO_BYTES32 && $._keys[operator].length() > MAX_DISABLED_KEYS + 1) { revert MaxDisabledKeysReached(); } - if (s.keys[operator].length() > 0) { + if ($._keys[operator].length() > 0) { // try to remove disabled keys - bytes32 prevKey = s.keys[operator].array[0].value; - if (s.keys[operator].checkUnregister(timestamp, _SLASHING_WINDOW(), prevKey)) { - s.keys[operator].unregister(timestamp, _SLASHING_WINDOW(), prevKey); - delete s.keyToOperator[prevKey]; - } else if (s.keys[operator].wasActiveAt(timestamp, prevKey)) { - s.keys[operator].pause(timestamp, prevKey); + bytes32 prevKey = $._keys[operator].array[0].value; + if ($._keys[operator].checkUnregister(timestamp, _SLASHING_WINDOW(), prevKey)) { + $._keys[operator].unregister(timestamp, _SLASHING_WINDOW(), prevKey); + delete $._keyToOperator[prevKey]; + } else if ($._keys[operator].wasActiveAt(timestamp, prevKey)) { + $._keys[operator].pause(timestamp, prevKey); } } if (key != ZERO_BYTES32) { // register the new key - s.keys[operator].register(timestamp, key); - s.keyToOperator[key] = operator; + $._keys[operator].register(timestamp, key); + $._keyToOperator[key] = operator; } } } From 1fc8c3260fa9a0382594aace19492719f25d8bd8 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 16:05:44 +0400 Subject: [PATCH 108/115] fix:statemind-Missing Veto Slasher consideration --- .../SimplePosMiddleware.sol | 12 ++++++++ .../sqrt-task-network/SqrtTaskMiddleware.sol | 11 ++++++++ src/managers/VaultManager.sol | 28 ++++++------------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 760d68a..9c51b69 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -8,6 +8,7 @@ import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {BaseMiddleware} from "../../middleware/BaseMiddleware.sol"; +import {VaultManager} from "../../managers/VaultManager.sol"; import {SharedVaults} from "../../extensions/SharedVaults.sol"; import {Operators} from "../../extensions/operators/Operators.sol"; @@ -176,6 +177,17 @@ contract SimplePosMiddleware is } } + function executeSlash( + uint48 epochStart, + address vault, + bytes32 subnetwork, + address operator, + uint256 amount, + bytes memory hints + ) external checkAccess { + _slashVault(epochStart, vault, subnetwork, operator, amount, hints); + } + function _checkCanSlash(uint48 epochStart, bytes32 key, address operator) internal view { if (operator == address(0)) { revert NotExistKeySlash(); // Revert if the operator does not exist diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index b5868a9..301b8bb 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -165,4 +165,15 @@ contract SqrtTaskMiddleware is _slashVault(task.captureTimestamp, vault, subnetwork, task.operator, slashAmount, slashHints[i]); } } + + function executeSlash( + uint48 epochStart, + address vault, + bytes32 subnetwork, + address operator, + uint256 amount, + bytes memory hints + ) external checkAccess { + _slashVault(epochStart, vault, subnetwork, operator, amount, hints); + } } diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol index 478f35d..f219392 100644 --- a/src/managers/VaultManager.sol +++ b/src/managers/VaultManager.sol @@ -53,19 +53,8 @@ abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, Capture EnumerableMap.AddressToAddressMap _vaultOperator; } - /** - * @dev Struct containing information about a slash response - * @param vault The address of the vault being slashed - * @param slasherType The type identifier of the slasher - * @param subnetwork The subnetwork identifier where the slash occurred - * @param response For instant slashing: the slashed amount, for veto slashing: the slash index - */ - struct SlashResponse { - address vault; - uint64 slasherType; - bytes32 subnetwork; - uint256 response; - } + event InstantSlash(address vault, bytes32 subnetwork, uint256 amount); + event VetoSlash(address vault, bytes32 subnetwork, uint256 index); enum SlasherType { INSTANT, // Instant slasher type @@ -640,7 +629,7 @@ abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, Capture * @param operator The operator to slash * @param amount The amount to slash * @param hints Additional data for the slasher - * @return resp A struct containing information about the slash response + * @return response index for veto slashing or amount for instant slashing */ function _slashVault( uint48 timestamp, @@ -649,7 +638,7 @@ abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, Capture address operator, uint256 amount, bytes memory hints - ) internal returns (SlashResponse memory resp) { + ) internal returns (uint256 response) { VaultManagerStorage storage $ = _getVaultManagerStorage(); if (!($._sharedVaults.contains(vault) || $._operatorVaults[operator].contains(vault))) { revert NotOperatorVault(); @@ -669,13 +658,12 @@ abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, Capture } uint64 slasherType = IEntity(slasher).TYPE(); - resp.vault = vault; - resp.slasherType = slasherType; - resp.subnetwork = subnetwork; if (slasherType == uint64(SlasherType.INSTANT)) { - resp.response = ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, hints); + response = ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, hints); + emit InstantSlash(vault, subnetwork, response); } else if (slasherType == uint64(SlasherType.VETO)) { - resp.response = IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, hints); + response = IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, hints); + emit VetoSlash(vault, subnetwork, response); } else { revert UnknownSlasherType(); } From 9deff3fbcf23bca84e87068e0c053afbd9ccb7f8 Mon Sep 17 00:00:00 2001 From: alrxy Date: Mon, 9 Dec 2024 16:32:19 +0400 Subject: [PATCH 109/115] chore: add epoch duration getter --- .../managers/capture-timestamps/EpochCapture.sol | 8 ++++++++ .../managers/capture-timestamps/IEpochCapture.sol | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/extensions/managers/capture-timestamps/EpochCapture.sol b/src/extensions/managers/capture-timestamps/EpochCapture.sol index 7efd0bf..c1e99fd 100644 --- a/src/extensions/managers/capture-timestamps/EpochCapture.sol +++ b/src/extensions/managers/capture-timestamps/EpochCapture.sol @@ -70,4 +70,12 @@ abstract contract EpochCapture is CaptureTimestampManager, IEpochCapture { function getCaptureTimestamp() public view override returns (uint48 timestamp) { return getEpochStart(getCurrentEpoch()); } + + /** + * @inheritdoc IEpochCapture + */ + function getEpochDuration() public view returns (uint48) { + EpochCaptureStorage storage $ = _getEpochCaptureStorage(); + return $.epochDuration; + } } diff --git a/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol b/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol index 8c97d12..3aae0ca 100644 --- a/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol +++ b/src/interfaces/extensions/managers/capture-timestamps/IEpochCapture.sol @@ -21,4 +21,10 @@ interface IEpochCapture { * @return The current epoch */ function getCurrentEpoch() external view returns (uint48); + + /** + * @notice Returns the duration of each epoch + * @return The duration of each epoch + */ + function getEpochDuration() external view returns (uint48); } From 3832990d156a3adec54d8bbebed0d4fb6cccca5f Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 11 Dec 2024 17:08:39 +0400 Subject: [PATCH 110/115] fix: docstrings and reader --- .../SelfRegisterEd25519Middleware.sol | 8 +- .../SelfRegisterMiddleware.sol | 22 +- .../SimplePosMiddleware.sol | 25 +- .../sqrt-task-network/SqrtTaskMiddleware.sol | 8 +- .../managers/access/OzAccessControl.sol | 4 +- .../managers/keys/KeyManager256.sol | 2 - .../managers/keys/KeyManagerBytes.sol | 2 - src/managers/OperatorManager.sol | 20 +- src/managers/VaultManager.sol | 734 ------------------ src/managers/extendable/SigManager.sol | 11 + src/managers/storages/NetworkStorage.sol | 14 + .../storages/SlashingWindowStorage.sol | 23 + src/middleware/BaseMiddleware.sol | 26 +- src/middleware/BaseMiddlewareReader.sol | 213 +++++ 14 files changed, 331 insertions(+), 781 deletions(-) delete mode 100644 src/managers/VaultManager.sol diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index 3607f15..fa86a99 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -35,9 +35,9 @@ contract SelfRegisterEd25519Middleware is address vaultRegistry, address operatorRegistry, address operatorNetOptin, - address readHelper + address reader ) { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); } function initialize( @@ -46,9 +46,9 @@ contract SelfRegisterEd25519Middleware is address vaultRegistry, address operatorRegistry, address operatorNetOptin, - address readHelper + address reader ) internal initializer { - __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); __SelfRegisterOperators_init("SelfRegisterEd25519Middleware"); } } diff --git a/src/examples/self-register-network/SelfRegisterMiddleware.sol b/src/examples/self-register-network/SelfRegisterMiddleware.sol index 38c95bb..a10bea4 100644 --- a/src/examples/self-register-network/SelfRegisterMiddleware.sol +++ b/src/examples/self-register-network/SelfRegisterMiddleware.sol @@ -20,14 +20,14 @@ contract SelfRegisterMiddleware is TimestampCapture, EqualStakePower { - /* - * @notice Constructor for initializing the SelfRegisterMiddleware contract. - * @param network The address of the network. - * @param operatorRegistry The address of the operator registry. - * @param vaultRegistry The address of the vault registry. - * @param operatorNetOptin The address of the operator network opt-in service. - * @param owner The address of the contract owner. + /** + * @notice Constructor for initializing the SelfRegisterMiddleware contract + * @param network The address of the network * @param slashingWindow The duration of the slashing window + * @param vaultRegistry The address of the vault registry + * @param operatorRegistry The address of the operator registry + * @param operatorNetOptin The address of the operator network opt-in service + * @param reader The address of the reader contract used for delegatecall */ constructor( address network, @@ -35,9 +35,9 @@ contract SelfRegisterMiddleware is address vaultRegistry, address operatorRegistry, address operatorNetOptin, - address readHelper + address reader ) { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); } function initialize( @@ -46,9 +46,9 @@ contract SelfRegisterMiddleware is address vaultRegistry, address operatorRegistry, address operatorNetOptIn, - address readHelper + address reader ) internal initializer { - __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, readHelper); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptIn, reader); __SelfRegisterOperators_init("SelfRegisterMiddleware"); } } diff --git a/src/examples/simple-pos-network/SimplePosMiddleware.sol b/src/examples/simple-pos-network/SimplePosMiddleware.sol index 9c51b69..f8da19b 100644 --- a/src/examples/simple-pos-network/SimplePosMiddleware.sol +++ b/src/examples/simple-pos-network/SimplePosMiddleware.sol @@ -45,15 +45,16 @@ contract SimplePosMiddleware is uint160[] subnetworks; } - /* - * @notice Constructor for initializing the SimpleMiddleware contract. - * @param network The address of the network. - * @param operatorRegistry The address of the operator registry. - * @param vaultRegistry The address of the vault registry. - * @param operatorNetOptin The address of the operator network opt-in service. - * @param owner The address of the contract owner. - * @param epochDuration The duration of each epoch. + /** + * @notice Constructor for initializing the SimplePosMiddleware contract + * @param network The address of the network * @param slashingWindow The duration of the slashing window + * @param vaultRegistry The address of the vault registry + * @param operatorRegistry The address of the operator registry + * @param operatorNetOptin The address of the operator network opt-in service + * @param reader The address of the reader contract used for delegatecall + * @param owner The address of the contract owner + * @param epochDuration The duration of each epoch */ constructor( address network, @@ -61,12 +62,12 @@ contract SimplePosMiddleware is address vaultRegistry, address operatorRegistry, address operatorNetOptin, - address readHelper, + address reader, address owner, uint48 epochDuration ) { initialize( - network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper, owner, epochDuration + network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader, owner, epochDuration ); } @@ -76,11 +77,11 @@ contract SimplePosMiddleware is address vaultRegistry, address operatorRegistry, address operatorNetOptin, - address readHelper, + address reader, address owner, uint48 epochDuration ) internal initializer { - __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); __OwnableAccessManager_init(owner); __EpochCapture_init(epochDuration); } diff --git a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol index 301b8bb..b15c36a 100644 --- a/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol +++ b/src/examples/sqrt-task-network/SqrtTaskMiddleware.sol @@ -54,10 +54,10 @@ contract SqrtTaskMiddleware is address operatorRegistry, address vaultRegistry, address operatorNetOptin, - address readHelper, + address reader, address owner ) EIP712("SqrtTaskMiddleware", "1") { - initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper, owner); + initialize(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader, owner); } function initialize( @@ -66,10 +66,10 @@ contract SqrtTaskMiddleware is address vaultRegistry, address operatorRegistry, address operatorNetOptin, - address readHelper, + address reader, address owner ) internal initializer { - __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, readHelper); + __BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader); __OwnableAccessManager_init(owner); } diff --git a/src/extensions/managers/access/OzAccessControl.sol b/src/extensions/managers/access/OzAccessControl.sol index bb832ba..fd62812 100644 --- a/src/extensions/managers/access/OzAccessControl.sol +++ b/src/extensions/managers/access/OzAccessControl.sol @@ -129,7 +129,7 @@ abstract contract OzAccessControl is AccessManager, IOzAccessControl { } /** - * @notice public function to grant a role + * @notice Grants a role * @param role The role to grant * @param account The account to grant the role to * @return bool True if role was granted @@ -145,7 +145,7 @@ abstract contract OzAccessControl is AccessManager, IOzAccessControl { } /** - * @notice public function to revoke a role + * @notice Revokes a role * @param role The role to revoke * @param account The account to revoke the role from * @return bool True if role was revoked diff --git a/src/extensions/managers/keys/KeyManager256.sol b/src/extensions/managers/keys/KeyManager256.sol index 66408df..b0a39b2 100644 --- a/src/extensions/managers/keys/KeyManager256.sol +++ b/src/extensions/managers/keys/KeyManager256.sol @@ -83,8 +83,6 @@ abstract contract KeyManager256 is KeyManager { * @dev Handles key rotation by disabling old key and registering new one * @param operator The operator address to update * @param key_ The new key to register, encoded as bytes - * @custom:throws DuplicateKey if key is already registered to another operator - * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key_) internal override { KeyManager256Storage storage $ = _getKeyManager256Storage(); diff --git a/src/extensions/managers/keys/KeyManagerBytes.sol b/src/extensions/managers/keys/KeyManagerBytes.sol index 7de4af9..dbc3839 100644 --- a/src/extensions/managers/keys/KeyManagerBytes.sol +++ b/src/extensions/managers/keys/KeyManagerBytes.sol @@ -81,8 +81,6 @@ abstract contract KeyManagerBytes is KeyManager { * @dev Handles key rotation by disabling old key and registering new one * @param operator The operator address to update * @param key The new key to register - * @custom:throws DuplicateKey if key is already registered to another operator - * @custom:throws MaxDisabledKeysReached if operator has too many disabled keys */ function _updateKey(address operator, bytes memory key) internal override { KeyManagerBytesStorage storage $ = _getKeyManagerBytesStorage(); diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index 9b89612..057b80b 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -11,6 +11,24 @@ import {CaptureTimestampManager} from "./extendable/CaptureTimestampManager.sol" import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; +/** + * @title OperatorManager + * @notice Manages operator registration and validation for the protocol + * @dev Inherits from NetworkStorage, SlashingWindowStorage, and CaptureTimestampManager + * to provide operator management functionality with network awareness and time-based features + * + * Key features: + * - Operator registration and validation + * - Network opt-in verification + * - Pauseable operator enumeration + * - Timestamp-based operator management + * + * Storage: + * - _operatorRegistry: Registry contract for validating operators + * - _operatorNetOptin: Service for verifying network opt-in status + * - _operators: Set of registered operators with pause functionality + */ + abstract contract OperatorManager is NetworkStorage, SlashingWindowStorage, CaptureTimestampManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; @@ -140,8 +158,6 @@ abstract contract OperatorManager is NetworkStorage, SlashingWindowStorage, Capt /** * @notice Registers a new operator * @param operator The address of the operator to register - * @custom:throws NotOperator if operator is not registered in the operator registry - * @custom:throws OperatorNotOptedIn if operator has not opted into the network */ function _registerOperator( address operator diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol deleted file mode 100644 index f219392..0000000 --- a/src/managers/VaultManager.sol +++ /dev/null @@ -1,734 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.25; - -import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; -import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; -import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; -import {IEntity} from "@symbiotic/interfaces/common/IEntity.sol"; -import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; -import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; -import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; -import {IOperatorSpecificDelegator} from "@symbiotic/interfaces/delegator/IOperatorSpecificDelegator.sol"; - -import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; - -import {StakePowerManager} from "./extendable/StakePowerManager.sol"; -import {CaptureTimestampManager} from "./extendable/CaptureTimestampManager.sol"; - -import {NetworkStorage} from "./storages/NetworkStorage.sol"; -import {SlashingWindowStorage} from "./storages/SlashingWindowStorage.sol"; - -import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; - -/** - * @title VaultManager - * @notice Abstract contract for managing vaults and their relationships with operators and subnetworks - * @dev Extends BaseManager and provides functionality for registering, pausing, and managing vaults - */ -abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, CaptureTimestampManager, StakePowerManager { - using EnumerableMap for EnumerableMap.AddressToUintMap; - using EnumerableMap for EnumerableMap.AddressToAddressMap; - using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; - using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; - using Subnetwork for address; - - error NotVault(); - error NotOperatorVault(); - error VaultNotInitialized(); - error VaultAlreadyRegistered(); - error VaultEpochTooShort(); - error InactiveVaultSlash(); - error UnknownSlasherType(); - error NonVetoSlasher(); - error NoSlasher(); - error TooOldTimestampSlash(); - error NotOperatorSpecificVault(); - - /// @custom:storage-location erc7201:symbiotic.storage.VaultManager - struct VaultManagerStorage { - address _vaultRegistry; - PauseableEnumerableSet.Uint160Set _subnetworks; - PauseableEnumerableSet.AddressSet _sharedVaults; - mapping(address => PauseableEnumerableSet.AddressSet) _operatorVaults; - EnumerableMap.AddressToAddressMap _vaultOperator; - } - - event InstantSlash(address vault, bytes32 subnetwork, uint256 amount); - event VetoSlash(address vault, bytes32 subnetwork, uint256 index); - - enum SlasherType { - INSTANT, // Instant slasher type - VETO // Veto slasher type - - } - - enum DelegatorType { - FULL_RESTAKE, - NETWORK_RESTAKE, - OPERATOR_SPECIFIC - } - - // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.VaultManager")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant VaultManagerStorageLocation = - 0x485f0695561726d087d0cb5cf546efed37ef61dfced21455f1ba7eb5e5b3db00; - - /** - * @notice Internal helper to access the VaultManager storage struct - * @dev Uses assembly to load storage location from a constant slot - * @return $ Storage pointer to the VaultManagerStorage struct - */ - function _getVaultManagerStorage() internal pure returns (VaultManagerStorage storage $) { - assembly { - $.slot := VaultManagerStorageLocation - } - } - - /** - * @notice Initializes the VaultManager with required parameters - * @param vaultRegistry The address of the vault registry contract - */ - function __VaultManager_init_private( - address vaultRegistry - ) internal onlyInitializing { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._vaultRegistry = vaultRegistry; - } - - /** - * @notice Gets the address of the vault registry contract - * @return The vault registry contract address - */ - function _VAULT_REGISTRY() internal view returns (address) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._vaultRegistry; - } - - /** - * @notice Gets the total number of registered subnetworks - * @return uint256 The count of registered subnetworks - */ - function _subnetworksLength() internal view returns (uint256) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._subnetworks.length(); - } - - /** - * @notice Gets the subnetwork information at a specific index - * @param pos The index position to query - * @return uint160 The subnetwork address - * @return uint48 The time when the subnetwork was enabled - * @return uint48 The time when the subnetwork was disabled - */ - function _subnetworkWithTimesAt( - uint256 pos - ) internal view returns (uint160, uint48, uint48) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._subnetworks.at(pos); - } - - /** - * @notice Gets all currently active subnetworks - * @return uint160[] Array of active subnetwork addresses - */ - function _activeSubnetworks() internal view returns (uint160[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._subnetworks.getActive(getCaptureTimestamp()); - } - - /** - * @notice Gets all subnetworks that were active at a specific timestamp - * @param timestamp The timestamp to check - * @return uint160[] Array of subnetwork addresses that were active at the timestamp - */ - function _activeSubnetworksAt( - uint48 timestamp - ) internal view returns (uint160[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._subnetworks.getActive(timestamp); - } - - /** - * @notice Checks if a subnetwork was active at a specific timestamp - * @param timestamp The timestamp to check - * @param subnetwork The subnetwork identifier - * @return bool True if the subnetwork was active at the timestamp - */ - function _subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) internal view returns (bool) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); - } - - /** - * @notice Gets the total number of shared vaults - * @return uint256 The count of shared vaults - */ - function _sharedVaultsLength() internal view returns (uint256) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._sharedVaults.length(); - } - - /** - * @notice Gets the vault information at a specific index - * @param pos The index position to query - * @return address The vault address - * @return uint48 The time when the vault was enabled - * @return uint48 The time when the vault was disabled - */ - function _sharedVaultWithTimesAt( - uint256 pos - ) internal view returns (address, uint48, uint48) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._sharedVaults.at(pos); - } - - /** - * @notice Gets all currently active shared vaults - * @return address[] Array of active shared vault addresses - */ - function _activeSharedVaults() internal view returns (address[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._sharedVaults.getActive(getCaptureTimestamp()); - } - - /** - * @notice Gets all shared vaults that were active at a specific timestamp - * @param timestamp The timestamp to check - * @return address[] Array of shared vault addresses that were active at the timestamp - */ - function _activeSharedVaultsAt( - uint48 timestamp - ) internal view returns (address[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._sharedVaults.getActive(timestamp); - } - - /** - * @notice Gets the number of vaults associated with an operator - * @param operator The operator address to query - * @return uint256 The count of vaults for the operator - */ - function _operatorVaultsLength( - address operator - ) internal view returns (uint256) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._operatorVaults[operator].length(); - } - - /** - * @notice Gets the vault information at a specific index for an operator - * @param operator The operator address - * @param pos The index position to query - * @return address The vault address - * @return uint48 The time when the vault was enabled - * @return uint48 The time when the vault was disabled - */ - function _operatorVaultWithTimesAt(address operator, uint256 pos) internal view returns (address, uint48, uint48) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._operatorVaults[operator].at(pos); - } - - /** - * @notice Gets all currently active vaults for a specific operator - * @param operator The operator address - * @return address[] Array of active vault addresses - */ - function _activeOperatorVaults( - address operator - ) internal view returns (address[] memory) { - return _activeOperatorVaultsAt(getCaptureTimestamp(), operator); - } - - /** - * @notice Gets all currently active vaults for a specific operator at a specific timestamp - * @param timestamp The timestamp to check - * @param operator The operator address - * @return address[] Array of active vault addresses - */ - function _activeOperatorVaultsAt(uint48 timestamp, address operator) internal view returns (address[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._operatorVaults[operator].getActive(timestamp); - } - - /** - * @notice Gets all currently active vaults across all operators - * @return address[] Array of all active vault addresses - */ - function _activeVaults() internal view returns (address[] memory) { - return _activeVaultsAt(getCaptureTimestamp()); - } - - /** - * @notice Gets all vaults that were active at a specific timestamp - * @param timestamp The timestamp to check - * @return address[] Array of vault addresses that were active at the timestamp - */ - function _activeVaultsAt( - uint48 timestamp - ) internal view returns (address[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); - uint256 len = activeSharedVaults_.length; - uint256 operatorVaultsLen = $._vaultOperator.length(); - address[] memory vaults = new address[](len + operatorVaultsLen); - - for (uint256 i; i < len; ++i) { - vaults[i] = activeSharedVaults_[i]; - } - - for (uint256 i; i < operatorVaultsLen; ++i) { - (address vault, address operator) = $._vaultOperator.at(i); - if ($._operatorVaults[operator].wasActiveAt(timestamp, vault)) { - vaults[len++] = vault; - } - } - - assembly { - mstore(vaults, len) - } - - return vaults; - } - - /** - * @notice Gets all currently active vaults for a specific operator - * @param operator The operator address - * @return address[] Array of active vault addresses for the operator - */ - function _activeVaults( - address operator - ) internal view returns (address[] memory) { - return _activeVaultsAt(getCaptureTimestamp(), operator); - } - - /** - * @notice Gets all vaults that were active for an operator at a specific timestamp - * @param timestamp The timestamp to check - * @param operator The operator address - * @return address[] Array of vault addresses that were active at the timestamp - */ - function _activeVaultsAt(uint48 timestamp, address operator) internal view returns (address[] memory) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); - address[] memory activeOperatorVaults_ = $._operatorVaults[operator].getActive(timestamp); - - uint256 activeSharedVaultsLen = activeSharedVaults_.length; - uint256 activeOperatorVaultsLen = activeOperatorVaults_.length; - address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaultsLen); - for (uint256 i; i < activeSharedVaultsLen; ++i) { - vaults[i] = activeSharedVaults_[i]; - } - for (uint256 i; i < activeOperatorVaultsLen; ++i) { - vaults[activeSharedVaultsLen + i] = activeOperatorVaults_[i]; - } - - return vaults; - } - - /** - * @notice Checks if a vault was active at a specific timestamp - * @param timestamp The timestamp to check - * @param operator The operator address - * @param vault The vault address - * @return bool True if the vault was active at the timestamp - */ - function _vaultWasActiveAt(uint48 timestamp, address operator, address vault) internal view returns (bool) { - return _sharedVaultWasActiveAt(timestamp, vault) || _operatorVaultWasActiveAt(timestamp, operator, vault); - } - - /** - * @notice Checks if a shared vault was active at a specific timestamp - * @param timestamp The timestamp to check - * @param vault The vault address - * @return bool True if the shared vault was active at the timestamp - */ - function _sharedVaultWasActiveAt(uint48 timestamp, address vault) internal view returns (bool) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._sharedVaults.wasActiveAt(timestamp, vault); - } - - /** - * @notice Checks if an operator vault was active at a specific timestamp - * @param timestamp The timestamp to check - * @param operator The operator address - * @param vault The vault address - * @return bool True if the operator vault was active at the timestamp - */ - function _operatorVaultWasActiveAt( - uint48 timestamp, - address operator, - address vault - ) internal view returns (bool) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - return $._operatorVaults[operator].wasActiveAt(timestamp, vault); - } - - /** - * @notice Gets the stake amount for an operator in a vault and subnetwork at a specific timestamp - * @param timestamp The timestamp to check - * @param operator The operator address - * @param vault The vault address - * @param subnetwork The subnetwork identifier - * @return uint256 The stake amount at the timestamp - */ - function _getOperatorStakeAt( - uint48 timestamp, - address operator, - address vault, - uint96 subnetwork - ) private view returns (uint256) { - bytes32 subnetworkId = _NETWORK().subnetwork(subnetwork); - return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); - } - - /** - * @notice Gets the power amount for an operator in a vault and subnetwork - * @param operator The operator address - * @param vault The vault address - * @param subnetwork The subnetwork identifier - * @return uint256 The power amount - */ - function _getOperatorPower(address operator, address vault, uint96 subnetwork) internal view returns (uint256) { - return _getOperatorPowerAt(getCaptureTimestamp(), operator, vault, subnetwork); - } - - /** - * @notice Gets the power amount for an operator in a vault and subnetwork at a specific timestamp - * @param timestamp The timestamp to check - * @param operator The operator address - * @param vault The vault address - * @param subnetwork The subnetwork identifier - * @return uint256 The power amount at the timestamp - */ - function _getOperatorPowerAt( - uint48 timestamp, - address operator, - address vault, - uint96 subnetwork - ) internal view returns (uint256) { - uint256 stake = _getOperatorStakeAt(timestamp, operator, vault, subnetwork); - return stakeToPower(vault, stake); - } - - /** - * @notice Gets the total power amount for an operator across all vaults and subnetworks - * @param operator The operator address - * @return power The total power amount - */ - function _getOperatorPower( - address operator - ) internal view returns (uint256 power) { - return _getOperatorPowerAt(getCaptureTimestamp(), operator); - } - - /** - * @notice Gets the total power amount for an operator across all vaults and subnetworks at a specific timestamp - * @param timestamp The timestamp to check - * @param operator The operator address - * @return power The total power amount at the timestamp - */ - function _getOperatorPowerAt(uint48 timestamp, address operator) internal view returns (uint256 power) { - address[] memory vaults = _activeVaultsAt(timestamp, operator); - uint160[] memory subnetworks = _activeSubnetworksAt(timestamp); - - return _getOperatorPower(operator, vaults, subnetworks); - } - - /** - * @notice Gets the total power amount for an operator across all vaults and subnetworks - * @param operator The operator address - * @param vaults The list of vault addresses - * @param subnetworks The list of subnetwork identifiers - * @return power The total power amount - */ - function _getOperatorPower( - address operator, - address[] memory vaults, - uint160[] memory subnetworks - ) internal view returns (uint256 power) { - return _getOperatorPowerAt(getCaptureTimestamp(), operator, vaults, subnetworks); - } - - /** - * @notice Gets the total power amount for an operator across all vaults and subnetworks at a specific timestamp - * @param timestamp The timestamp to check - * @param operator The operator address - * @param vaults The list of vault addresses - * @param subnetworks The list of subnetwork identifiers - * @return power The total power amount at the timestamp - */ - function _getOperatorPowerAt( - uint48 timestamp, - address operator, - address[] memory vaults, - uint160[] memory subnetworks - ) internal view returns (uint256 power) { - for (uint256 i; i < vaults.length; ++i) { - address vault = vaults[i]; - for (uint256 j; j < subnetworks.length; ++j) { - power += _getOperatorPowerAt(timestamp, operator, vault, uint96(subnetworks[j])); - } - } - - return power; - } - - /** - * @notice Calculates the total power for a list of operators - * @param operators Array of operator addresses - * @return power The total power amount - */ - function _totalPower( - address[] memory operators - ) internal view returns (uint256 power) { - for (uint256 i; i < operators.length; ++i) { - power += _getOperatorPower(operators[i]); - } - - return power; - } - - /** - * @notice Registers a new subnetwork - * @param subnetwork The subnetwork identifier to register - */ - function _registerSubnetwork( - uint96 subnetwork - ) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.register(_now(), uint160(subnetwork)); - } - - /** - * @notice Pauses a subnetwork - * @param subnetwork The subnetwork identifier to pause - */ - function _pauseSubnetwork( - uint96 subnetwork - ) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.pause(_now(), uint160(subnetwork)); - } - - /** - * @notice Unpauses a subnetwork - * @param subnetwork The subnetwork identifier to unpause - */ - function _unpauseSubnetwork( - uint96 subnetwork - ) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.unpause(_now(), _SLASHING_WINDOW(), uint160(subnetwork)); - } - - /** - * @notice Unregisters a subnetwork - * @param subnetwork The subnetwork identifier to unregister - */ - function _unregisterSubnetwork( - uint96 subnetwork - ) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._subnetworks.unregister(_now(), _SLASHING_WINDOW(), uint160(subnetwork)); - } - - /** - * @notice Registers a new shared vault - * @param vault The vault address to register - */ - function _registerSharedVault( - address vault - ) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - _validateVault(vault); - $._sharedVaults.register(_now(), vault); - } - - /** - * @notice Registers a new operator vault - * @param operator The operator address - * @param vault The vault address to register - */ - function _registerOperatorVault(address operator, address vault) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - _validateVault(vault); - _validateOperatorVault(operator, vault); - - $._operatorVaults[operator].register(_now(), vault); - $._vaultOperator.set(vault, operator); - } - - /** - * @notice Pauses a shared vault - * @param vault The vault address to pause - */ - function _pauseSharedVault( - address vault - ) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.pause(_now(), vault); - } - - /** - * @notice Unpauses a shared vault - * @param vault The vault address to unpause - */ - function _unpauseSharedVault( - address vault - ) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.unpause(_now(), _SLASHING_WINDOW(), vault); - } - - /** - * @notice Pauses an operator vault - * @param operator The operator address - * @param vault The vault address to pause - */ - function _pauseOperatorVault(address operator, address vault) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].pause(_now(), vault); - } - - /** - * @notice Unpauses an operator vault - * @param operator The operator address - * @param vault The vault address to unpause - */ - function _unpauseOperatorVault(address operator, address vault) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].unpause(_now(), _SLASHING_WINDOW(), vault); - } - - /** - * @notice Unregisters a shared vault - * @param vault The vault address to unregister - */ - function _unregisterSharedVault( - address vault - ) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._sharedVaults.unregister(_now(), _SLASHING_WINDOW(), vault); - } - - /** - * @notice Unregisters an operator vault - * @param operator The operator address - * @param vault The vault address to unregister - */ - function _unregisterOperatorVault(address operator, address vault) internal { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - $._operatorVaults[operator].unregister(_now(), _SLASHING_WINDOW(), vault); - $._vaultOperator.remove(vault); - } - - /** - * @notice Slashes a vault based on provided conditions - * @param timestamp The timestamp when the slash occurs - * @param vault The vault address - * @param subnetwork The subnetwork identifier - * @param operator The operator to slash - * @param amount The amount to slash - * @param hints Additional data for the slasher - * @return response index for veto slashing or amount for instant slashing - */ - function _slashVault( - uint48 timestamp, - address vault, - bytes32 subnetwork, - address operator, - uint256 amount, - bytes memory hints - ) internal returns (uint256 response) { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - if (!($._sharedVaults.contains(vault) || $._operatorVaults[operator].contains(vault))) { - revert NotOperatorVault(); - } - - if (!_vaultWasActiveAt(timestamp, operator, vault)) { - revert InactiveVaultSlash(); - } - - if (timestamp + _SLASHING_WINDOW() < _now()) { - revert TooOldTimestampSlash(); - } - - address slasher = IVault(vault).slasher(); - if (slasher == address(0)) { - revert NoSlasher(); - } - - uint64 slasherType = IEntity(slasher).TYPE(); - if (slasherType == uint64(SlasherType.INSTANT)) { - response = ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, hints); - emit InstantSlash(vault, subnetwork, response); - } else if (slasherType == uint64(SlasherType.VETO)) { - response = IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, hints); - emit VetoSlash(vault, subnetwork, response); - } else { - revert UnknownSlasherType(); - } - } - - /** - * @notice Executes a veto-based slash for a vault - * @param vault The vault address - * @param slashIndex The index of the slash to execute - * @param hints Additional data for the veto slasher - * @return slashedAmount The amount that was slashed - */ - function _executeSlash( - address vault, - uint256 slashIndex, - bytes calldata hints - ) internal returns (uint256 slashedAmount) { - address slasher = IVault(vault).slasher(); - uint64 slasherType = IEntity(slasher).TYPE(); - if (slasherType != uint64(SlasherType.VETO)) { - revert NonVetoSlasher(); - } - - return IVetoSlasher(slasher).executeSlash(slashIndex, hints); - } - - /** - * @notice Validates if a vault is properly initialized and registered - * @param vault The vault address to validate - */ - function _validateVault( - address vault - ) private view { - VaultManagerStorage storage $ = _getVaultManagerStorage(); - if (!IRegistry(_VAULT_REGISTRY()).isEntity(vault)) { - revert NotVault(); - } - - if (!IVault(vault).isInitialized()) { - revert VaultNotInitialized(); - } - - if ($._vaultOperator.contains(vault) || $._sharedVaults.contains(vault)) { - revert VaultAlreadyRegistered(); - } - - uint48 vaultEpoch = IVault(vault).epochDuration(); - - address slasher = IVault(vault).slasher(); - if (slasher != address(0) && IEntity(slasher).TYPE() == uint64(SlasherType.VETO)) { - vaultEpoch -= IVetoSlasher(slasher).vetoDuration(); - } - - if (vaultEpoch < _SLASHING_WINDOW()) { - revert VaultEpochTooShort(); - } - } - - function _validateOperatorVault(address operator, address vault) internal view { - address delegator = IVault(vault).delegator(); - if ( - IEntity(delegator).TYPE() != uint64(DelegatorType.OPERATOR_SPECIFIC) - || IOperatorSpecificDelegator(delegator).operator() != operator - ) { - revert NotOperatorSpecificVault(); - } - } -} diff --git a/src/managers/extendable/SigManager.sol b/src/managers/extendable/SigManager.sol index c3ec014..3bdd956 100644 --- a/src/managers/extendable/SigManager.sol +++ b/src/managers/extendable/SigManager.sol @@ -3,6 +3,17 @@ pragma solidity ^0.8.25; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +/** + * @title SigManager + * @notice Abstract contract for verifying signatures against operator keys + * @dev Provides signature verification functionality for operator keys + * + * Key features: + * - Signature verification against operator keys + * - Abstract implementation allowing different signature schemes + * - Initializable pattern for upgradeable contracts + */ + abstract contract SigManager is Initializable { /** * @notice Verifies that a signature was created by the owner of a key diff --git a/src/managers/storages/NetworkStorage.sol b/src/managers/storages/NetworkStorage.sol index 46cdb01..9246f47 100644 --- a/src/managers/storages/NetworkStorage.sol +++ b/src/managers/storages/NetworkStorage.sol @@ -3,6 +3,20 @@ pragma solidity ^0.8.25; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +/** + * @title NetworkStorage + * @notice Storage contract for managing the network address + * @dev Uses a single storage slot to store the network address value + * + * Key features: + * - Immutable network address after initialization + * - Single storage slot usage for gas efficiency + * - Assembly-level storage access + * + * Storage: + * - NetworkStorageLocation: Storage slot containing the network address + */ + abstract contract NetworkStorage is Initializable { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.NetworkStorage")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant NetworkStorageLocation = 0x779150488f5e984d1f840ba606e388ada6c73b44f261274c3595c61a30023e00; diff --git a/src/managers/storages/SlashingWindowStorage.sol b/src/managers/storages/SlashingWindowStorage.sol index 8a93350..b43b35a 100644 --- a/src/managers/storages/SlashingWindowStorage.sol +++ b/src/managers/storages/SlashingWindowStorage.sol @@ -3,11 +3,30 @@ pragma solidity ^0.8.25; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +/** + * @title SlashingWindowStorage + * @notice Storage contract for managing the slashing window duration + * @dev Uses a single storage slot to store the slashing window duration value + * + * Key features: + * - Immutable slashing window duration after initialization + * - Single storage slot usage for gas efficiency + * - Assembly-level storage access + * + * Storage: + * - SlashingWindowStorageLocation: Storage slot containing the slashing window duration + */ + abstract contract SlashingWindowStorage is Initializable { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.SlashingWindowStorage")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant SlashingWindowStorageLocation = 0x52becd5b30d67421b1f63b9d90d513daf82b3973912d3edfdac9468c1743c000; + /** + * @notice Initializes the SlashingWindowStorage contract + * @param _slashingWindow The duration of the slashing window in seconds + */ + function __SlashingWindowStorage_init_private( uint48 _slashingWindow ) internal onlyInitializing { @@ -16,6 +35,10 @@ abstract contract SlashingWindowStorage is Initializable { } } + /** + * @notice Returns the slashing window duration + * @return slashingWindow The duration of the slashing window in seconds + */ function _SLASHING_WINDOW() internal view returns (uint48) { uint48 slashingWindow; assembly { diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 905b5e6..2e60f1f 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -23,37 +23,47 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager uint64 public constant BaseMiddleware_VERSION = 1; // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseMiddleware")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant ReadHelperStorageLocation = + bytes32 private constant ReaderStorageLocation = 0xfd87879bc98f37af7578af722aecfbe5843e5ad354da2d1e70cb5157c4ec8800; + /** + * @notice Initializes the BaseMiddleware contract with required dependencies and parameters + * @dev This internal initialization function sets up core storage and manager components + * @param network The address of the network contract + * @param slashingWindow The duration of the slashing window in blocks + * @param vaultRegistry The address of the vault registry contract + * @param operatorRegistry The address of the operator registry contract + * @param operatorNetOptin The address of the operator network opt-in contract + * @param reader The address of the reader contract used for delegatecall + */ function __BaseMiddleware_init( address network, uint48 slashingWindow, address vaultRegistry, address operatorRegistry, address operatorNetOptin, - address readHelper + address reader ) internal onlyInitializing { __NetworkStorage_init_private(network); __SlashingWindowStorage_init_private(slashingWindow); __VaultManager_init_private(vaultRegistry); __OperatorManager_init_private(operatorRegistry, operatorNetOptin); assembly { - sstore(ReadHelperStorageLocation, readHelper) + sstore(ReaderStorageLocation, reader) } } /** - * @notice The fallback function is used to implement getter functions by delegating calls to the ReadHelper contract - * @dev This allows the BaseMiddleware to expose view functions defined in the ReadHelper without explicitly implementing them, + * @notice The fallback function is used to implement getter functions by delegating calls to the BasemiddlewareReader contract + * @dev This allows the BaseMiddleware to expose view functions defined in the BasemiddlewareReader without explicitly implementing them, * reducing code duplication and maintaining a single source of truth for read operations */ fallback() external { - address readHelper_; + address reader_; assembly { - readHelper_ := sload(ReadHelperStorageLocation) + reader_ := sload(ReaderStorageLocation) } - (bool success, bytes memory returndata) = readHelper_.delegatecall(abi.encodePacked(msg.data, address(this))); + (bool success, bytes memory returndata) = reader_.delegatecall(abi.encodePacked(msg.data, address(this))); if (!success) { assembly { revert(add(returndata, 0x20), mload(returndata)) diff --git a/src/middleware/BaseMiddlewareReader.sol b/src/middleware/BaseMiddlewareReader.sol index ca9fe2e..020fbf8 100644 --- a/src/middleware/BaseMiddlewareReader.sol +++ b/src/middleware/BaseMiddlewareReader.sol @@ -12,164 +12,342 @@ import {NoKeyManager} from "../extensions/managers/keys/NoKeyManager.sol"; * management capabilities that can be extended with additional functionality. */ contract BaseMiddlewareReader is BaseMiddleware, NoAccessManager, NoKeyManager { + /** + * @notice Gets the capture timestamp from the middleware + * @return timestamp The capture timestamp + */ function getCaptureTimestamp() public view override returns (uint48 timestamp) { return BaseMiddleware(_getMiddleware()).getCaptureTimestamp(); } + /** + * @notice Converts stake amount to voting power using a 1:1 ratio + * @param vault The vault address (unused in this implementation) + * @param stake The stake amount + * @return power The calculated voting power (equal to stake) + */ function stakeToPower(address vault, uint256 stake) public view override returns (uint256 power) { return BaseMiddleware(_getMiddleware()).stakeToPower(vault, stake); } + /** + * @notice Gets the network address + * @return The network address + */ function NETWORK() external view returns (address) { return _NETWORK(); } + /** + * @notice Gets the slashing window + * @return The slashing window + */ function SLASHING_WINDOW() external view returns (uint48) { return _SLASHING_WINDOW(); } + /** + * @notice Gets the vault registry address + * @return The vault registry address + */ function VAULT_REGISTRY() external view returns (address) { return _VAULT_REGISTRY(); } + /** + * @notice Gets the operator registry address + * @return The operator registry address + */ function OPERATOR_REGISTRY() external view returns (address) { return _OPERATOR_REGISTRY(); } + /** + * @notice Gets the operator net opt-in address + * @return The operator net opt-in address + */ function OPERATOR_NET_OPTIN() external view returns (address) { return _OPERATOR_NET_OPTIN(); } + /** + * @notice Gets the number of operators + * @return The number of operators + */ function operatorsLength() external view returns (uint256) { return _operatorsLength(); } + /** + * @notice Gets the operator and its times at a specific position + * @param pos The position + * @return The operator address, start time, and end time + */ function operatorWithTimesAt( uint256 pos ) external view returns (address, uint48, uint48) { return _operatorWithTimesAt(pos); } + /** + * @notice Gets the list of active operators + * @return The list of active operators + */ function activeOperators() external view returns (address[] memory) { return _activeOperators(); } + /** + * @notice Gets the list of active operators at a specific timestamp + * @param timestamp The timestamp + * @return The list of active operators at the given timestamp + */ function activeOperatorsAt( uint48 timestamp ) external view returns (address[] memory) { return _activeOperatorsAt(timestamp); } + /** + * @notice Checks if an operator was active at a specific timestamp + * @param timestamp The timestamp + * @param operator The operator address + * @return True if the operator was active at the given timestamp, false otherwise + */ function operatorWasActiveAt(uint48 timestamp, address operator) external view returns (bool) { return _operatorWasActiveAt(timestamp, operator); } + /** + * @notice Checks if an operator is registered + * @param operator The operator address + * @return True if the operator is registered, false otherwise + */ function isOperatorRegistered( address operator ) external view returns (bool) { return _isOperatorRegistered(operator); } + /** + * @notice Gets the number of subnetworks + * @return The number of subnetworks + */ function subnetworksLength() external view returns (uint256) { return _subnetworksLength(); } + /** + * @notice Gets the subnetwork and its times at a specific position + * @param pos The position + * @return The subnetwork address, start time, and end time + */ function subnetworkWithTimesAt( uint256 pos ) external view returns (uint160, uint48, uint48) { return _subnetworkWithTimesAt(pos); } + /** + * @notice Gets the list of active subnetworks + * @return The list of active subnetworks + */ function activeSubnetworks() external view returns (uint160[] memory) { return _activeSubnetworks(); } + /** + * @notice Gets the list of active subnetworks at a specific timestamp + * @param timestamp The timestamp + * @return The list of active subnetworks at the given timestamp + */ function activeSubnetworksAt( uint48 timestamp ) external view returns (uint160[] memory) { return _activeSubnetworksAt(timestamp); } + /** + * @notice Checks if a subnetwork was active at a specific timestamp + * @param timestamp The timestamp + * @param subnetwork The subnetwork address + * @return True if the subnetwork was active at the given timestamp, false otherwise + */ function subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) external view returns (bool) { return _subnetworkWasActiveAt(timestamp, subnetwork); } + /** + * @notice Gets the number of shared vaults + * @return The number of shared vaults + */ function sharedVaultsLength() external view returns (uint256) { return _sharedVaultsLength(); } + /** + * @notice Gets the shared vault and its times at a specific position + * @param pos The position + * @return The shared vault address, start time, and end time + */ function sharedVaultWithTimesAt( uint256 pos ) external view returns (address, uint48, uint48) { return _sharedVaultWithTimesAt(pos); } + /** + * @notice Gets the list of active shared vaults + * @return The list of active shared vaults + */ function activeSharedVaults() external view returns (address[] memory) { return _activeSharedVaults(); } + /** + * @notice Gets the list of active shared vaults at a specific timestamp + * @param timestamp The timestamp + * @return The list of active shared vaults at the given timestamp + */ function activeSharedVaultsAt( uint48 timestamp ) external view returns (address[] memory) { return _activeSharedVaultsAt(timestamp); } + /** + * @notice Gets the number of vaults for a specific operator + * @param operator The operator address + * @return The number of vaults for the given operator + */ function operatorVaultsLength( address operator ) external view returns (uint256) { return _operatorVaultsLength(operator); } + /** + * @notice Gets the operator vault and its times at a specific position + * @param operator The operator address + * @param pos The position + * @return The operator vault address, start time, and end time + */ function operatorVaultWithTimesAt(address operator, uint256 pos) external view returns (address, uint48, uint48) { return _operatorVaultWithTimesAt(operator, pos); } + /** + * @notice Gets the list of active vaults for a specific operator + * @param operator The operator address + * @return The list of active vaults for the given operator + */ function activeOperatorVaults( address operator ) external view returns (address[] memory) { return _activeOperatorVaults(operator); } + /** + * @notice Gets the list of active vaults for a specific operator at a specific timestamp + * @param timestamp The timestamp + * @param operator The operator address + * @return The list of active vaults for the given operator at the given timestamp + */ function activeOperatorVaultsAt(uint48 timestamp, address operator) external view returns (address[] memory) { return _activeOperatorVaultsAt(timestamp, operator); } + /** + * @notice Gets the list of active vaults + * @return The list of active vaults + */ function activeVaults() external view returns (address[] memory) { return _activeVaults(); } + /** + * @notice Gets the list of active vaults at a specific timestamp + * @param timestamp The timestamp + * @return The list of active vaults at the given timestamp + */ function activeVaultsAt( uint48 timestamp ) external view returns (address[] memory) { return _activeVaultsAt(timestamp); } + /** + * @notice Gets the list of active vaults for a specific operator + * @param operator The operator address + * @return The list of active vaults for the given operator + */ function activeVaults( address operator ) external view returns (address[] memory) { return _activeVaults(operator); } + /** + * @notice Gets the list of active vaults for a specific operator at a specific timestamp + * @param timestamp The timestamp + * @param operator The operator address + * @return The list of active vaults for the given operator at the given timestamp + */ function activeVaultsAt(uint48 timestamp, address operator) external view returns (address[] memory) { return _activeVaultsAt(timestamp, operator); } + /** + * @notice Checks if a vault was active at a specific timestamp for a specific operator + * @param timestamp The timestamp + * @param operator The operator address + * @param vault The vault address + * @return True if the vault was active at the given timestamp for the given operator, false otherwise + */ function vaultWasActiveAt(uint48 timestamp, address operator, address vault) external view returns (bool) { return _vaultWasActiveAt(timestamp, operator, vault); } + /** + * @notice Checks if a shared vault was active at a specific timestamp + * @param timestamp The timestamp + * @param vault The shared vault address + * @return True if the shared vault was active at the given timestamp, false otherwise + */ function sharedVaultWasActiveAt(uint48 timestamp, address vault) external view returns (bool) { return _sharedVaultWasActiveAt(timestamp, vault); } + /** + * @notice Checks if an operator vault was active at a specific timestamp for a specific operator + * @param timestamp The timestamp + * @param operator The operator address + * @param vault The vault address + * @return True if the operator vault was active at the given timestamp for the given operator, false otherwise + */ function operatorVaultWasActiveAt(uint48 timestamp, address operator, address vault) external view returns (bool) { return _operatorVaultWasActiveAt(timestamp, operator, vault); } + /** + * @notice Gets the power of an operator for a specific vault and subnetwork + * @param operator The operator address + * @param vault The vault address + * @param subnetwork The subnetwork address + * @return The power of the operator for the given vault and subnetwork + */ function getOperatorPower(address operator, address vault, uint96 subnetwork) external view returns (uint256) { return _getOperatorPower(operator, vault, subnetwork); } + /** + * @notice Gets the power of an operator for a specific vault and subnetwork at a specific timestamp + * @param timestamp The timestamp + * @param operator The operator address + * @param vault The vault address + * @param subnetwork The subnetwork address + * @return The power of the operator for the given vault and subnetwork at the given timestamp + */ function getOperatorPowerAt( uint48 timestamp, address operator, @@ -179,16 +357,34 @@ contract BaseMiddlewareReader is BaseMiddleware, NoAccessManager, NoKeyManager { return _getOperatorPowerAt(timestamp, operator, vault, subnetwork); } + /** + * @notice Gets the power of an operator + * @param operator The operator address + * @return The power of the operator + */ function getOperatorPower( address operator ) external view returns (uint256) { return _getOperatorPower(operator); } + /** + * @notice Gets the power of an operator at a specific timestamp + * @param timestamp The timestamp + * @param operator The operator address + * @return The power of the operator at the given timestamp + */ function getOperatorPowerAt(uint48 timestamp, address operator) external view returns (uint256) { return _getOperatorPowerAt(timestamp, operator); } + /** + * @notice Gets the power of an operator for specific vaults and subnetworks + * @param operator The operator address + * @param vaults The list of vault addresses + * @param subnetworks The list of subnetwork addresses + * @return The power of the operator for the given vaults and subnetworks + */ function getOperatorPower( address operator, address[] memory vaults, @@ -197,6 +393,14 @@ contract BaseMiddlewareReader is BaseMiddleware, NoAccessManager, NoKeyManager { return _getOperatorPower(operator, vaults, subnetworks); } + /** + * @notice Gets the power of an operator for specific vaults and subnetworks at a specific timestamp + * @param timestamp The timestamp + * @param operator The operator address + * @param vaults The list of vault addresses + * @param subnetworks The list of subnetwork addresses + * @return The power of the operator for the given vaults and subnetworks at the given timestamp + */ function getOperatorPowerAt( uint48 timestamp, address operator, @@ -206,12 +410,21 @@ contract BaseMiddlewareReader is BaseMiddleware, NoAccessManager, NoKeyManager { return _getOperatorPowerAt(timestamp, operator, vaults, subnetworks); } + /** + * @notice Gets the total power of a list of operators + * @param operators The list of operator addresses + * @return The total power of the given operators + */ function totalPower( address[] memory operators ) external view returns (uint256) { return _totalPower(operators); } + /** + * @notice Gets the middleware address from the calldata + * @return The middleware address + */ function _getMiddleware() private pure returns (address) { address middleware; assembly { From 1b8cec1bc5d66eb13c4b3a3bee1c1d052ab9202c Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 11 Dec 2024 17:08:50 +0400 Subject: [PATCH 111/115] fix: register default subnetwork 0 --- src/managers/VaultManager.sol | 737 ++++++++++++++++++++++++++++++++++ 1 file changed, 737 insertions(+) create mode 100644 src/managers/VaultManager.sol diff --git a/src/managers/VaultManager.sol b/src/managers/VaultManager.sol new file mode 100644 index 0000000..a894003 --- /dev/null +++ b/src/managers/VaultManager.sol @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import {IVault} from "@symbiotic/interfaces/vault/IVault.sol"; +import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol"; +import {IRegistry} from "@symbiotic/interfaces/common/IRegistry.sol"; +import {IEntity} from "@symbiotic/interfaces/common/IEntity.sol"; +import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol"; +import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol"; +import {ISlasher} from "@symbiotic/interfaces/slasher/ISlasher.sol"; +import {IOperatorSpecificDelegator} from "@symbiotic/interfaces/delegator/IOperatorSpecificDelegator.sol"; + +import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; + +import {StakePowerManager} from "./extendable/StakePowerManager.sol"; +import {CaptureTimestampManager} from "./extendable/CaptureTimestampManager.sol"; + +import {NetworkStorage} from "./storages/NetworkStorage.sol"; +import {SlashingWindowStorage} from "./storages/SlashingWindowStorage.sol"; + +import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; + +/** + * @title VaultManager + * @notice Abstract contract for managing vaults and their relationships with operators and subnetworks + * @dev Extends BaseManager and provides functionality for registering, pausing, and managing vaults + */ +abstract contract VaultManager is NetworkStorage, SlashingWindowStorage, CaptureTimestampManager, StakePowerManager { + using EnumerableMap for EnumerableMap.AddressToUintMap; + using EnumerableMap for EnumerableMap.AddressToAddressMap; + using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; + using PauseableEnumerableSet for PauseableEnumerableSet.Uint160Set; + using Subnetwork for address; + + error NotVault(); + error NotOperatorVault(); + error VaultNotInitialized(); + error VaultAlreadyRegistered(); + error VaultEpochTooShort(); + error InactiveVaultSlash(); + error UnknownSlasherType(); + error NonVetoSlasher(); + error NoSlasher(); + error TooOldTimestampSlash(); + error NotOperatorSpecificVault(); + + /// @custom:storage-location erc7201:symbiotic.storage.VaultManager + struct VaultManagerStorage { + address _vaultRegistry; + PauseableEnumerableSet.Uint160Set _subnetworks; + PauseableEnumerableSet.AddressSet _sharedVaults; + mapping(address => PauseableEnumerableSet.AddressSet) _operatorVaults; + EnumerableMap.AddressToAddressMap _vaultOperator; + } + + event InstantSlash(address vault, bytes32 subnetwork, uint256 amount); + event VetoSlash(address vault, bytes32 subnetwork, uint256 index); + + enum SlasherType { + INSTANT, // Instant slasher type + VETO // Veto slasher type + + } + + enum DelegatorType { + FULL_RESTAKE, + NETWORK_RESTAKE, + OPERATOR_SPECIFIC + } + + // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.VaultManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant VaultManagerStorageLocation = + 0x485f0695561726d087d0cb5cf546efed37ef61dfced21455f1ba7eb5e5b3db00; + + /** + * @notice Internal helper to access the VaultManager storage struct + * @dev Uses assembly to load storage location from a constant slot + * @return $ Storage pointer to the VaultManagerStorage struct + */ + function _getVaultManagerStorage() internal pure returns (VaultManagerStorage storage $) { + assembly { + $.slot := VaultManagerStorageLocation + } + } + + uint96 private constant DEFAULT_SUBNETWORK = 0; + + /** + * @notice Initializes the VaultManager with required parameters + * @param vaultRegistry The address of the vault registry contract + */ + function __VaultManager_init_private( + address vaultRegistry + ) internal onlyInitializing { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._vaultRegistry = vaultRegistry; + _registerSubnetwork(DEFAULT_SUBNETWORK); + } + + /** + * @notice Gets the address of the vault registry contract + * @return The vault registry contract address + */ + function _VAULT_REGISTRY() internal view returns (address) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._vaultRegistry; + } + + /** + * @notice Gets the total number of registered subnetworks + * @return uint256 The count of registered subnetworks + */ + function _subnetworksLength() internal view returns (uint256) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.length(); + } + + /** + * @notice Gets the subnetwork information at a specific index + * @param pos The index position to query + * @return uint160 The subnetwork address + * @return uint48 The time when the subnetwork was enabled + * @return uint48 The time when the subnetwork was disabled + */ + function _subnetworkWithTimesAt( + uint256 pos + ) internal view returns (uint160, uint48, uint48) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.at(pos); + } + + /** + * @notice Gets all currently active subnetworks + * @return uint160[] Array of active subnetwork addresses + */ + function _activeSubnetworks() internal view returns (uint160[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.getActive(getCaptureTimestamp()); + } + + /** + * @notice Gets all subnetworks that were active at a specific timestamp + * @param timestamp The timestamp to check + * @return uint160[] Array of subnetwork addresses that were active at the timestamp + */ + function _activeSubnetworksAt( + uint48 timestamp + ) internal view returns (uint160[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.getActive(timestamp); + } + + /** + * @notice Checks if a subnetwork was active at a specific timestamp + * @param timestamp The timestamp to check + * @param subnetwork The subnetwork identifier + * @return bool True if the subnetwork was active at the timestamp + */ + function _subnetworkWasActiveAt(uint48 timestamp, uint96 subnetwork) internal view returns (bool) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._subnetworks.wasActiveAt(timestamp, uint160(subnetwork)); + } + + /** + * @notice Gets the total number of shared vaults + * @return uint256 The count of shared vaults + */ + function _sharedVaultsLength() internal view returns (uint256) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.length(); + } + + /** + * @notice Gets the vault information at a specific index + * @param pos The index position to query + * @return address The vault address + * @return uint48 The time when the vault was enabled + * @return uint48 The time when the vault was disabled + */ + function _sharedVaultWithTimesAt( + uint256 pos + ) internal view returns (address, uint48, uint48) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.at(pos); + } + + /** + * @notice Gets all currently active shared vaults + * @return address[] Array of active shared vault addresses + */ + function _activeSharedVaults() internal view returns (address[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.getActive(getCaptureTimestamp()); + } + + /** + * @notice Gets all shared vaults that were active at a specific timestamp + * @param timestamp The timestamp to check + * @return address[] Array of shared vault addresses that were active at the timestamp + */ + function _activeSharedVaultsAt( + uint48 timestamp + ) internal view returns (address[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.getActive(timestamp); + } + + /** + * @notice Gets the number of vaults associated with an operator + * @param operator The operator address to query + * @return uint256 The count of vaults for the operator + */ + function _operatorVaultsLength( + address operator + ) internal view returns (uint256) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._operatorVaults[operator].length(); + } + + /** + * @notice Gets the vault information at a specific index for an operator + * @param operator The operator address + * @param pos The index position to query + * @return address The vault address + * @return uint48 The time when the vault was enabled + * @return uint48 The time when the vault was disabled + */ + function _operatorVaultWithTimesAt(address operator, uint256 pos) internal view returns (address, uint48, uint48) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._operatorVaults[operator].at(pos); + } + + /** + * @notice Gets all currently active vaults for a specific operator + * @param operator The operator address + * @return address[] Array of active vault addresses + */ + function _activeOperatorVaults( + address operator + ) internal view returns (address[] memory) { + return _activeOperatorVaultsAt(getCaptureTimestamp(), operator); + } + + /** + * @notice Gets all currently active vaults for a specific operator at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @return address[] Array of active vault addresses + */ + function _activeOperatorVaultsAt(uint48 timestamp, address operator) internal view returns (address[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._operatorVaults[operator].getActive(timestamp); + } + + /** + * @notice Gets all currently active vaults across all operators + * @return address[] Array of all active vault addresses + */ + function _activeVaults() internal view returns (address[] memory) { + return _activeVaultsAt(getCaptureTimestamp()); + } + + /** + * @notice Gets all vaults that were active at a specific timestamp + * @param timestamp The timestamp to check + * @return address[] Array of vault addresses that were active at the timestamp + */ + function _activeVaultsAt( + uint48 timestamp + ) internal view returns (address[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); + uint256 len = activeSharedVaults_.length; + uint256 operatorVaultsLen = $._vaultOperator.length(); + address[] memory vaults = new address[](len + operatorVaultsLen); + + for (uint256 i; i < len; ++i) { + vaults[i] = activeSharedVaults_[i]; + } + + for (uint256 i; i < operatorVaultsLen; ++i) { + (address vault, address operator) = $._vaultOperator.at(i); + if ($._operatorVaults[operator].wasActiveAt(timestamp, vault)) { + vaults[len++] = vault; + } + } + + assembly { + mstore(vaults, len) + } + + return vaults; + } + + /** + * @notice Gets all currently active vaults for a specific operator + * @param operator The operator address + * @return address[] Array of active vault addresses for the operator + */ + function _activeVaults( + address operator + ) internal view returns (address[] memory) { + return _activeVaultsAt(getCaptureTimestamp(), operator); + } + + /** + * @notice Gets all vaults that were active for an operator at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @return address[] Array of vault addresses that were active at the timestamp + */ + function _activeVaultsAt(uint48 timestamp, address operator) internal view returns (address[] memory) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + address[] memory activeSharedVaults_ = $._sharedVaults.getActive(timestamp); + address[] memory activeOperatorVaults_ = $._operatorVaults[operator].getActive(timestamp); + + uint256 activeSharedVaultsLen = activeSharedVaults_.length; + uint256 activeOperatorVaultsLen = activeOperatorVaults_.length; + address[] memory vaults = new address[](activeSharedVaultsLen + activeOperatorVaultsLen); + for (uint256 i; i < activeSharedVaultsLen; ++i) { + vaults[i] = activeSharedVaults_[i]; + } + for (uint256 i; i < activeOperatorVaultsLen; ++i) { + vaults[activeSharedVaultsLen + i] = activeOperatorVaults_[i]; + } + + return vaults; + } + + /** + * @notice Checks if a vault was active at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @param vault The vault address + * @return bool True if the vault was active at the timestamp + */ + function _vaultWasActiveAt(uint48 timestamp, address operator, address vault) internal view returns (bool) { + return _sharedVaultWasActiveAt(timestamp, vault) || _operatorVaultWasActiveAt(timestamp, operator, vault); + } + + /** + * @notice Checks if a shared vault was active at a specific timestamp + * @param timestamp The timestamp to check + * @param vault The vault address + * @return bool True if the shared vault was active at the timestamp + */ + function _sharedVaultWasActiveAt(uint48 timestamp, address vault) internal view returns (bool) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._sharedVaults.wasActiveAt(timestamp, vault); + } + + /** + * @notice Checks if an operator vault was active at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @param vault The vault address + * @return bool True if the operator vault was active at the timestamp + */ + function _operatorVaultWasActiveAt( + uint48 timestamp, + address operator, + address vault + ) internal view returns (bool) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + return $._operatorVaults[operator].wasActiveAt(timestamp, vault); + } + + /** + * @notice Gets the stake amount for an operator in a vault and subnetwork at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @param vault The vault address + * @param subnetwork The subnetwork identifier + * @return uint256 The stake amount at the timestamp + */ + function _getOperatorStakeAt( + uint48 timestamp, + address operator, + address vault, + uint96 subnetwork + ) private view returns (uint256) { + bytes32 subnetworkId = _NETWORK().subnetwork(subnetwork); + return IBaseDelegator(IVault(vault).delegator()).stakeAt(subnetworkId, operator, timestamp, ""); + } + + /** + * @notice Gets the power amount for an operator in a vault and subnetwork + * @param operator The operator address + * @param vault The vault address + * @param subnetwork The subnetwork identifier + * @return uint256 The power amount + */ + function _getOperatorPower(address operator, address vault, uint96 subnetwork) internal view returns (uint256) { + return _getOperatorPowerAt(getCaptureTimestamp(), operator, vault, subnetwork); + } + + /** + * @notice Gets the power amount for an operator in a vault and subnetwork at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @param vault The vault address + * @param subnetwork The subnetwork identifier + * @return uint256 The power amount at the timestamp + */ + function _getOperatorPowerAt( + uint48 timestamp, + address operator, + address vault, + uint96 subnetwork + ) internal view returns (uint256) { + uint256 stake = _getOperatorStakeAt(timestamp, operator, vault, subnetwork); + return stakeToPower(vault, stake); + } + + /** + * @notice Gets the total power amount for an operator across all vaults and subnetworks + * @param operator The operator address + * @return power The total power amount + */ + function _getOperatorPower( + address operator + ) internal view returns (uint256 power) { + return _getOperatorPowerAt(getCaptureTimestamp(), operator); + } + + /** + * @notice Gets the total power amount for an operator across all vaults and subnetworks at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @return power The total power amount at the timestamp + */ + function _getOperatorPowerAt(uint48 timestamp, address operator) internal view returns (uint256 power) { + address[] memory vaults = _activeVaultsAt(timestamp, operator); + uint160[] memory subnetworks = _activeSubnetworksAt(timestamp); + + return _getOperatorPower(operator, vaults, subnetworks); + } + + /** + * @notice Gets the total power amount for an operator across all vaults and subnetworks + * @param operator The operator address + * @param vaults The list of vault addresses + * @param subnetworks The list of subnetwork identifiers + * @return power The total power amount + */ + function _getOperatorPower( + address operator, + address[] memory vaults, + uint160[] memory subnetworks + ) internal view returns (uint256 power) { + return _getOperatorPowerAt(getCaptureTimestamp(), operator, vaults, subnetworks); + } + + /** + * @notice Gets the total power amount for an operator across all vaults and subnetworks at a specific timestamp + * @param timestamp The timestamp to check + * @param operator The operator address + * @param vaults The list of vault addresses + * @param subnetworks The list of subnetwork identifiers + * @return power The total power amount at the timestamp + */ + function _getOperatorPowerAt( + uint48 timestamp, + address operator, + address[] memory vaults, + uint160[] memory subnetworks + ) internal view returns (uint256 power) { + for (uint256 i; i < vaults.length; ++i) { + address vault = vaults[i]; + for (uint256 j; j < subnetworks.length; ++j) { + power += _getOperatorPowerAt(timestamp, operator, vault, uint96(subnetworks[j])); + } + } + + return power; + } + + /** + * @notice Calculates the total power for a list of operators + * @param operators Array of operator addresses + * @return power The total power amount + */ + function _totalPower( + address[] memory operators + ) internal view returns (uint256 power) { + for (uint256 i; i < operators.length; ++i) { + power += _getOperatorPower(operators[i]); + } + + return power; + } + + /** + * @notice Registers a new subnetwork + * @param subnetwork The subnetwork identifier to register + */ + function _registerSubnetwork( + uint96 subnetwork + ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._subnetworks.register(_now(), uint160(subnetwork)); + } + + /** + * @notice Pauses a subnetwork + * @param subnetwork The subnetwork identifier to pause + */ + function _pauseSubnetwork( + uint96 subnetwork + ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._subnetworks.pause(_now(), uint160(subnetwork)); + } + + /** + * @notice Unpauses a subnetwork + * @param subnetwork The subnetwork identifier to unpause + */ + function _unpauseSubnetwork( + uint96 subnetwork + ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._subnetworks.unpause(_now(), _SLASHING_WINDOW(), uint160(subnetwork)); + } + + /** + * @notice Unregisters a subnetwork + * @param subnetwork The subnetwork identifier to unregister + */ + function _unregisterSubnetwork( + uint96 subnetwork + ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._subnetworks.unregister(_now(), _SLASHING_WINDOW(), uint160(subnetwork)); + } + + /** + * @notice Registers a new shared vault + * @param vault The vault address to register + */ + function _registerSharedVault( + address vault + ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + _validateVault(vault); + $._sharedVaults.register(_now(), vault); + } + + /** + * @notice Registers a new operator vault + * @param operator The operator address + * @param vault The vault address to register + */ + function _registerOperatorVault(address operator, address vault) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + _validateVault(vault); + _validateOperatorVault(operator, vault); + + $._operatorVaults[operator].register(_now(), vault); + $._vaultOperator.set(vault, operator); + } + + /** + * @notice Pauses a shared vault + * @param vault The vault address to pause + */ + function _pauseSharedVault( + address vault + ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._sharedVaults.pause(_now(), vault); + } + + /** + * @notice Unpauses a shared vault + * @param vault The vault address to unpause + */ + function _unpauseSharedVault( + address vault + ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._sharedVaults.unpause(_now(), _SLASHING_WINDOW(), vault); + } + + /** + * @notice Pauses an operator vault + * @param operator The operator address + * @param vault The vault address to pause + */ + function _pauseOperatorVault(address operator, address vault) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._operatorVaults[operator].pause(_now(), vault); + } + + /** + * @notice Unpauses an operator vault + * @param operator The operator address + * @param vault The vault address to unpause + */ + function _unpauseOperatorVault(address operator, address vault) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._operatorVaults[operator].unpause(_now(), _SLASHING_WINDOW(), vault); + } + + /** + * @notice Unregisters a shared vault + * @param vault The vault address to unregister + */ + function _unregisterSharedVault( + address vault + ) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._sharedVaults.unregister(_now(), _SLASHING_WINDOW(), vault); + } + + /** + * @notice Unregisters an operator vault + * @param operator The operator address + * @param vault The vault address to unregister + */ + function _unregisterOperatorVault(address operator, address vault) internal { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + $._operatorVaults[operator].unregister(_now(), _SLASHING_WINDOW(), vault); + $._vaultOperator.remove(vault); + } + + /** + * @notice Slashes a vault based on provided conditions + * @param timestamp The timestamp when the slash occurs + * @param vault The vault address + * @param subnetwork The subnetwork identifier + * @param operator The operator to slash + * @param amount The amount to slash + * @param hints Additional data for the slasher + * @return response index for veto slashing or amount for instant slashing + */ + function _slashVault( + uint48 timestamp, + address vault, + bytes32 subnetwork, + address operator, + uint256 amount, + bytes memory hints + ) internal returns (uint256 response) { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + if (!($._sharedVaults.contains(vault) || $._operatorVaults[operator].contains(vault))) { + revert NotOperatorVault(); + } + + if (!_vaultWasActiveAt(timestamp, operator, vault)) { + revert InactiveVaultSlash(); + } + + if (timestamp + _SLASHING_WINDOW() < _now()) { + revert TooOldTimestampSlash(); + } + + address slasher = IVault(vault).slasher(); + if (slasher == address(0)) { + revert NoSlasher(); + } + + uint64 slasherType = IEntity(slasher).TYPE(); + if (slasherType == uint64(SlasherType.INSTANT)) { + response = ISlasher(slasher).slash(subnetwork, operator, amount, timestamp, hints); + emit InstantSlash(vault, subnetwork, response); + } else if (slasherType == uint64(SlasherType.VETO)) { + response = IVetoSlasher(slasher).requestSlash(subnetwork, operator, amount, timestamp, hints); + emit VetoSlash(vault, subnetwork, response); + } else { + revert UnknownSlasherType(); + } + } + + /** + * @notice Executes a veto-based slash for a vault + * @param vault The vault address + * @param slashIndex The index of the slash to execute + * @param hints Additional data for the veto slasher + * @return slashedAmount The amount that was slashed + */ + function _executeSlash( + address vault, + uint256 slashIndex, + bytes calldata hints + ) internal returns (uint256 slashedAmount) { + address slasher = IVault(vault).slasher(); + uint64 slasherType = IEntity(slasher).TYPE(); + if (slasherType != uint64(SlasherType.VETO)) { + revert NonVetoSlasher(); + } + + return IVetoSlasher(slasher).executeSlash(slashIndex, hints); + } + + /** + * @notice Validates if a vault is properly initialized and registered + * @param vault The vault address to validate + */ + function _validateVault( + address vault + ) private view { + VaultManagerStorage storage $ = _getVaultManagerStorage(); + if (!IRegistry(_VAULT_REGISTRY()).isEntity(vault)) { + revert NotVault(); + } + + if (!IVault(vault).isInitialized()) { + revert VaultNotInitialized(); + } + + if ($._vaultOperator.contains(vault) || $._sharedVaults.contains(vault)) { + revert VaultAlreadyRegistered(); + } + + uint48 vaultEpoch = IVault(vault).epochDuration(); + + address slasher = IVault(vault).slasher(); + if (slasher != address(0) && IEntity(slasher).TYPE() == uint64(SlasherType.VETO)) { + vaultEpoch -= IVetoSlasher(slasher).vetoDuration(); + } + + if (vaultEpoch < _SLASHING_WINDOW()) { + revert VaultEpochTooShort(); + } + } + + function _validateOperatorVault(address operator, address vault) internal view { + address delegator = IVault(vault).delegator(); + if ( + IEntity(delegator).TYPE() != uint64(DelegatorType.OPERATOR_SPECIFIC) + || IOperatorSpecificDelegator(delegator).operator() != operator + ) { + revert NotOperatorSpecificVault(); + } + } +} From 5e243950e3368cf43425814a25e8f05878d0e6c1 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 11 Dec 2024 17:09:13 +0400 Subject: [PATCH 112/115] fix: lint --- src/managers/OperatorManager.sol | 1 - src/managers/extendable/SigManager.sol | 1 - src/managers/storages/NetworkStorage.sol | 3 +-- src/managers/storages/SlashingWindowStorage.sol | 2 -- src/middleware/BaseMiddleware.sol | 5 ++--- 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index 057b80b..5bfd5cc 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -28,7 +28,6 @@ import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; * - _operatorNetOptin: Service for verifying network opt-in status * - _operators: Set of registered operators with pause functionality */ - abstract contract OperatorManager is NetworkStorage, SlashingWindowStorage, CaptureTimestampManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; diff --git a/src/managers/extendable/SigManager.sol b/src/managers/extendable/SigManager.sol index 3bdd956..e9efa7f 100644 --- a/src/managers/extendable/SigManager.sol +++ b/src/managers/extendable/SigManager.sol @@ -13,7 +13,6 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * - Abstract implementation allowing different signature schemes * - Initializable pattern for upgradeable contracts */ - abstract contract SigManager is Initializable { /** * @notice Verifies that a signature was created by the owner of a key diff --git a/src/managers/storages/NetworkStorage.sol b/src/managers/storages/NetworkStorage.sol index 9246f47..855a9d9 100644 --- a/src/managers/storages/NetworkStorage.sol +++ b/src/managers/storages/NetworkStorage.sol @@ -10,13 +10,12 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * * Key features: * - Immutable network address after initialization - * - Single storage slot usage for gas efficiency + * - Single storage slot usage for gas efficiency * - Assembly-level storage access * * Storage: * - NetworkStorageLocation: Storage slot containing the network address */ - abstract contract NetworkStorage is Initializable { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.NetworkStorage")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant NetworkStorageLocation = 0x779150488f5e984d1f840ba606e388ada6c73b44f261274c3595c61a30023e00; diff --git a/src/managers/storages/SlashingWindowStorage.sol b/src/managers/storages/SlashingWindowStorage.sol index b43b35a..a2ab7eb 100644 --- a/src/managers/storages/SlashingWindowStorage.sol +++ b/src/managers/storages/SlashingWindowStorage.sol @@ -16,7 +16,6 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * Storage: * - SlashingWindowStorageLocation: Storage slot containing the slashing window duration */ - abstract contract SlashingWindowStorage is Initializable { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.SlashingWindowStorage")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant SlashingWindowStorageLocation = @@ -26,7 +25,6 @@ abstract contract SlashingWindowStorage is Initializable { * @notice Initializes the SlashingWindowStorage contract * @param _slashingWindow The duration of the slashing window in seconds */ - function __SlashingWindowStorage_init_private( uint48 _slashingWindow ) internal onlyInitializing { diff --git a/src/middleware/BaseMiddleware.sol b/src/middleware/BaseMiddleware.sol index 2e60f1f..bf03ac9 100644 --- a/src/middleware/BaseMiddleware.sol +++ b/src/middleware/BaseMiddleware.sol @@ -23,8 +23,7 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager uint64 public constant BaseMiddleware_VERSION = 1; // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.BaseMiddleware")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant ReaderStorageLocation = - 0xfd87879bc98f37af7578af722aecfbe5843e5ad354da2d1e70cb5157c4ec8800; + bytes32 private constant ReaderStorageLocation = 0xfd87879bc98f37af7578af722aecfbe5843e5ad354da2d1e70cb5157c4ec8800; /** * @notice Initializes the BaseMiddleware contract with required dependencies and parameters @@ -32,7 +31,7 @@ abstract contract BaseMiddleware is VaultManager, OperatorManager, AccessManager * @param network The address of the network contract * @param slashingWindow The duration of the slashing window in blocks * @param vaultRegistry The address of the vault registry contract - * @param operatorRegistry The address of the operator registry contract + * @param operatorRegistry The address of the operator registry contract * @param operatorNetOptin The address of the operator network opt-in contract * @param reader The address of the reader contract used for delegatecall */ From 48828bb18de63b1d63ef10703cdf33e2cc57d226 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 11 Dec 2024 17:15:18 +0400 Subject: [PATCH 113/115] chore: upd readme --- README.md | 4 +++- img/middleware.png | Bin 0 -> 87712 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 img/middleware.png diff --git a/README.md b/README.md index 8a9959d..4170f13 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ This repository provides a framework for developing middleware in a modular and ## Key Components: +![Middleware Architecture](img/middleware.png) + - **BaseMiddleware**: The foundational contract that combines core manager functionalities from `VaultManager`, `OperatorManager`, `AccessManager`, and `KeyManager`. - **Extensions**: Modular contracts that provide additional functionalities. Key extensions include: @@ -127,7 +129,7 @@ To develop your middleware: } ``` -3. **Implement Required Functions** (Optional): Override functions as needed to implement your middleware's logic. Additionally, implement your own functions to extend the middleware's capabilities. +3. **Custom Logic** (Optional): Override manager functions to implement custom logic without using extensions (e.g. `StakePower` manager). Additionally, implement your own functions to extend the middleware's capabilities. ## Example: Creating a Custom Middleware diff --git a/img/middleware.png b/img/middleware.png new file mode 100644 index 0000000000000000000000000000000000000000..78d482eabf9755c7dab719655024324656163fe5 GIT binary patch literal 87712 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 Lm!GH5<3Pf{q8O22 literal 0 HcmV?d00001 From a931af18880389102d6b70b85ade98a81b36bbe8 Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 11 Dec 2024 17:17:00 +0400 Subject: [PATCH 114/115] fix: selfed middleware --- .../SelfRegisterEd25519Middleware.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol index fa86a99..0bda53b 100644 --- a/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol +++ b/src/examples/self-register-network/SelfRegisterEd25519Middleware.sol @@ -20,14 +20,14 @@ contract SelfRegisterEd25519Middleware is TimestampCapture, EqualStakePower { - /* - * @notice Constructor for initializing the SelfRegisterEd25519Middleware contract. - * @param network The address of the network. - * @param operatorRegistry The address of the operator registry. - * @param vaultRegistry The address of the vault registry. - * @param operatorNetOptin The address of the operator network opt-in service. - * @param owner The address of the contract owner. + /** + * @notice Constructor for initializing the SelfRegisterEd25519Middleware contract + * @param network The address of the network * @param slashingWindow The duration of the slashing window + * @param vaultRegistry The address of the vault registry + * @param operatorRegistry The address of the operator registry + * @param operatorNetOptin The address of the operator network opt-in service + * @param reader The address of the reader contract used for delegatecall */ constructor( address network, From 74706624e4700dc76b9b564420173f807f9d5d6e Mon Sep 17 00:00:00 2001 From: alrxy Date: Wed, 11 Dec 2024 17:21:49 +0400 Subject: [PATCH 115/115] fix: docstring --- src/managers/OperatorManager.sol | 11 ----------- src/managers/extendable/SigManager.sol | 5 ----- src/managers/storages/NetworkStorage.sol | 8 -------- src/managers/storages/SlashingWindowStorage.sol | 8 -------- 4 files changed, 32 deletions(-) diff --git a/src/managers/OperatorManager.sol b/src/managers/OperatorManager.sol index 5bfd5cc..4934428 100644 --- a/src/managers/OperatorManager.sol +++ b/src/managers/OperatorManager.sol @@ -16,17 +16,6 @@ import {PauseableEnumerableSet} from "../libraries/PauseableEnumerableSet.sol"; * @notice Manages operator registration and validation for the protocol * @dev Inherits from NetworkStorage, SlashingWindowStorage, and CaptureTimestampManager * to provide operator management functionality with network awareness and time-based features - * - * Key features: - * - Operator registration and validation - * - Network opt-in verification - * - Pauseable operator enumeration - * - Timestamp-based operator management - * - * Storage: - * - _operatorRegistry: Registry contract for validating operators - * - _operatorNetOptin: Service for verifying network opt-in status - * - _operators: Set of registered operators with pause functionality */ abstract contract OperatorManager is NetworkStorage, SlashingWindowStorage, CaptureTimestampManager { using PauseableEnumerableSet for PauseableEnumerableSet.AddressSet; diff --git a/src/managers/extendable/SigManager.sol b/src/managers/extendable/SigManager.sol index e9efa7f..21e8a28 100644 --- a/src/managers/extendable/SigManager.sol +++ b/src/managers/extendable/SigManager.sol @@ -7,11 +7,6 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * @title SigManager * @notice Abstract contract for verifying signatures against operator keys * @dev Provides signature verification functionality for operator keys - * - * Key features: - * - Signature verification against operator keys - * - Abstract implementation allowing different signature schemes - * - Initializable pattern for upgradeable contracts */ abstract contract SigManager is Initializable { /** diff --git a/src/managers/storages/NetworkStorage.sol b/src/managers/storages/NetworkStorage.sol index 855a9d9..4914cb5 100644 --- a/src/managers/storages/NetworkStorage.sol +++ b/src/managers/storages/NetworkStorage.sol @@ -7,14 +7,6 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * @title NetworkStorage * @notice Storage contract for managing the network address * @dev Uses a single storage slot to store the network address value - * - * Key features: - * - Immutable network address after initialization - * - Single storage slot usage for gas efficiency - * - Assembly-level storage access - * - * Storage: - * - NetworkStorageLocation: Storage slot containing the network address */ abstract contract NetworkStorage is Initializable { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.NetworkStorage")) - 1)) & ~bytes32(uint256(0xff)) diff --git a/src/managers/storages/SlashingWindowStorage.sol b/src/managers/storages/SlashingWindowStorage.sol index a2ab7eb..9a07e10 100644 --- a/src/managers/storages/SlashingWindowStorage.sol +++ b/src/managers/storages/SlashingWindowStorage.sol @@ -7,14 +7,6 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini * @title SlashingWindowStorage * @notice Storage contract for managing the slashing window duration * @dev Uses a single storage slot to store the slashing window duration value - * - * Key features: - * - Immutable slashing window duration after initialization - * - Single storage slot usage for gas efficiency - * - Assembly-level storage access - * - * Storage: - * - SlashingWindowStorageLocation: Storage slot containing the slashing window duration */ abstract contract SlashingWindowStorage is Initializable { // keccak256(abi.encode(uint256(keccak256("symbiotic.storage.SlashingWindowStorage")) - 1)) & ~bytes32(uint256(0xff))