Skip to content

Commit

Permalink
set a maximum operator fee (SSV)
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed Aug 22, 2023
1 parent 470fc45 commit 5496324
Show file tree
Hide file tree
Showing 15 changed files with 147 additions and 24 deletions.
10 changes: 9 additions & 1 deletion contracts/SSVNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,11 @@ contract SSVNetwork is
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]);
}

function liquidate(address clusterOwner, uint64[] calldata operatorIds, ISSVNetworkCore.Cluster memory cluster) external {
function liquidate(
address clusterOwner,
uint64[] calldata operatorIds,
ISSVNetworkCore.Cluster memory cluster
) external {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]);
}

Expand Down Expand Up @@ -245,6 +249,10 @@ contract SSVNetwork is
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_DAO]);
}

function updateMaximumOperatorFee(uint64 maxFee) external override {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_DAO]);
}

function getVersion() external pure override returns (string memory version) {
return CoreLib.getVersion();
}
Expand Down
4 changes: 4 additions & 0 deletions contracts/SSVNetworkViews.sol
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews
return ssvNetwork.getOperatorFeeIncreaseLimit();
}

function getMaximumOperatorFee() external view override returns (uint64 operatorMaxFee) {
return ssvNetwork.getMaximumOperatorFee();
}

function getOperatorFeePeriods()
external
view
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/ISSVDAO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ interface ISSVDAO is ISSVNetworkCore {
/// @param amount The new minimum collateral amount (SSV)
function updateMinimumLiquidationCollateral(uint256 amount) external;

/// @notice Updates the maximum fee an operator that uses SSV token can set
/// @param maxFee The new maximum fee (SSV)
function updateMaximumOperatorFee(uint64 maxFee) external;

event OperatorFeeIncreaseLimitUpdated(uint64 value);

event DeclareOperatorFeePeriodUpdated(uint64 value);
Expand All @@ -55,4 +59,6 @@ interface ISSVDAO is ISSVNetworkCore {
* @param recipient The recipient address.
*/
event NetworkEarningsWithdrawn(uint256 value, address recipient);

event OperatorMaximumFeeUpdated(uint64 maxFee);
}
1 change: 1 addition & 0 deletions contracts/interfaces/ISSVNetworkCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,5 @@ interface ISSVNetworkCore {
error OperatorAlreadyExists(); // 0x289c9494
error TargetModuleDoesNotExist(); // 0x8f9195fb
error MaxValueExceeded(); // 0x91aa3017
error FeeTooHigh(); // 0xcd4e6167
}
48 changes: 39 additions & 9 deletions contracts/interfaces/ISSVViews.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ interface ISSVViews is ISSVNetworkCore {
/// @param operatorId The ID of the operator
/// @return fee The fee associated with the operator (SSV). If the operator does not exist, the returned value is 0.
function getOperatorFee(uint64 operatorId) external view returns (uint256 fee);

/// @notice Gets the declared operator fee
/// @param operatorId The ID of the operator
/// @return isFeeDeclared A boolean indicating if the fee is declared
/// @return fee The declared operator fee (SSV)
/// @return approvalBeginTime The time when the fee approval process begins
/// @return approvalEndTime The time when the fee approval process ends
function getOperatorDeclaredFee(uint64 operatorId) external view returns (bool isFeeDeclared, uint256 fee, uint64 approvalBeginTime, uint64 approvalEndTime);
function getOperatorDeclaredFee(
uint64 operatorId
) external view returns (bool isFeeDeclared, uint256 fee, uint64 approvalBeginTime, uint64 approvalEndTime);

/// @notice Gets operator details by ID
/// @param operatorId The ID of the operator
Expand All @@ -31,25 +33,42 @@ interface ISSVViews is ISSVNetworkCore {
/// @return whitelisted The whitelisted address of the operator, if any
/// @return isPrivate A boolean indicating if the operator is private
/// @return active A boolean indicating if the operator is active
function getOperatorById(uint64 operatorId) external view returns (address owner, uint256 fee, uint32 validatorCount, address whitelisted, bool isPrivate, bool active);
function getOperatorById(
uint64 operatorId
)
external
view
returns (address owner, uint256 fee, uint32 validatorCount, address whitelisted, bool isPrivate, bool active);

/// @notice Checks if the cluster can be liquidated
/// @param owner The owner address of the cluster
/// @param operatorIds The IDs of the operators in the cluster
/// @return isLiquidatable A boolean indicating if the cluster can be liquidated
function isLiquidatable(address owner, uint64[] memory operatorIds, Cluster memory cluster) external view returns (bool isLiquidatable);
function isLiquidatable(
address owner,
uint64[] memory operatorIds,
Cluster memory cluster
) external view returns (bool isLiquidatable);

/// @notice Checks if the cluster is liquidated
/// @param owner The owner address of the cluster
/// @param operatorIds The IDs of the operators in the cluster
/// @return isLiquidated A boolean indicating if the cluster is liquidated
function isLiquidated(address owner, uint64[] memory operatorIds, Cluster memory cluster) external view returns (bool isLiquidated);
function isLiquidated(
address owner,
uint64[] memory operatorIds,
Cluster memory cluster
) external view returns (bool isLiquidated);

/// @notice Gets the burn rate of the cluster
/// @param owner The owner address of the cluster
/// @param operatorIds The IDs of the operators in the cluster
/// @return burnRate The burn rate of the cluster (SSV)
function getBurnRate(address owner, uint64[] memory operatorIds, Cluster memory cluster) external view returns (uint256 burnRate);
function getBurnRate(
address owner,
uint64[] memory operatorIds,
Cluster memory cluster
) external view returns (uint256 burnRate);

/// @notice Gets operator earnings
/// @param operatorId The ID of the operator
Expand All @@ -60,7 +79,11 @@ interface ISSVViews is ISSVNetworkCore {
/// @param owner The owner address of the cluster
/// @param operatorIds The IDs of the operators in the cluster
/// @return balance The balance of the cluster (SSV)
function getBalance(address owner, uint64[] memory operatorIds, Cluster memory cluster) external view returns (uint256 balance);
function getBalance(
address owner,
uint64[] memory operatorIds,
Cluster memory cluster
) external view returns (uint256 balance);

/// @notice Gets the network fee
/// @return networkFee The fee associated with the network (SSV)
Expand All @@ -74,10 +97,17 @@ interface ISSVViews is ISSVNetworkCore {
/// @return operatorMaxFeeIncrease The maximum limit of operator fee increase
function getOperatorFeeIncreaseLimit() external view returns (uint64 operatorMaxFeeIncrease);

/// @notice Gets the operator maximum fee for operators that use SSV token
/// @return operatorMaxFee The maximum fee value (SSV)
function getMaximumOperatorFee() external view returns (uint64 operatorMaxFee);

/// @notice Gets the periods of operator fee declaration and execution
/// @return declareOperatorFeePeriod The period for declaring operator fee
/// @return executeOperatorFeePeriod The period for executing operator fee
function getOperatorFeePeriods() external view returns (uint64 declareOperatorFeePeriod, uint64 executeOperatorFeePeriod);
function getOperatorFeePeriods()
external
view
returns (uint64 declareOperatorFeePeriod, uint64 executeOperatorFeePeriod);

/// @notice Gets the liquidation threshold period
/// @return blocks The number of blocks for the liquidation threshold period
Expand All @@ -94,4 +124,4 @@ interface ISSVViews is ISSVNetworkCore {
/// @notice Gets the version of the contract
/// @return version The version of the contract
function getVersion() external view returns (string memory version);
}
}
2 changes: 2 additions & 0 deletions contracts/libraries/SSVStorageProtocol.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct StorageProtocol {
uint64 executeOperatorFeePeriod;
/// @notice The maximum increase in operator fee that is allowed (percentage)
uint64 operatorMaxFeeIncrease;
/// @notice The maximum value in operator fee that is allowed (SSV)
uint64 operatorMaxFee;
}

library SSVStorageProtocol {
Expand Down
5 changes: 5 additions & 0 deletions contracts/modules/SSVDAO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,9 @@ contract SSVDAO is ISSVDAO {
SSVStorageProtocol.load().minimumLiquidationCollateral = amount.shrink();
emit MinimumLiquidationCollateralUpdated(amount);
}

function updateMaximumOperatorFee(uint64 maxFee) external override {
SSVStorageProtocol.load().operatorMaxFee = maxFee;
emit OperatorMaximumFeeUpdated(maxFee);
}
}
8 changes: 8 additions & 0 deletions contracts/modules/SSVOperators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ contract SSVOperators is ISSVOperators {
if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) {
revert ISSVNetworkCore.FeeTooLow();
}
if (fee > SSVStorageProtocol.load().operatorMaxFee) {
revert ISSVNetworkCore.FeeTooHigh();
}

StorageData storage s = SSVStorage.load();

bytes32 hashedPk = keccak256(publicKey);
Expand Down Expand Up @@ -92,6 +96,8 @@ contract SSVOperators is ISSVOperators {
StorageProtocol storage sp = SSVStorageProtocol.load();

if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) revert FeeTooLow();
if (fee > sp.operatorMaxFee) revert FeeTooHigh();

uint64 operatorFee = s.operators[operatorId].fee;
uint64 shrunkFee = fee.shrink();

Expand Down Expand Up @@ -129,6 +135,8 @@ contract SSVOperators is ISSVOperators {
revert ApprovalNotWithinTimeframe();
}

if (feeChangeRequest.fee.expand() > SSVStorageProtocol.load().operatorMaxFee) revert FeeTooHigh();

operator.updateSnapshot();
operator.fee = feeChangeRequest.fee;
s.operators[operatorId] = operator;
Expand Down
4 changes: 4 additions & 0 deletions contracts/modules/SSVViews.sol
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ contract SSVViews is ISSVViews {
return SSVStorageProtocol.load().operatorMaxFeeIncrease;
}

function getMaximumOperatorFee() external view override returns (uint64 operatorMaxFee) {
return SSVStorageProtocol.load().operatorMaxFee;
}

function getOperatorFeePeriods()
external
view
Expand Down
7 changes: 7 additions & 0 deletions contracts/test/SSVNetworkUpgrade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ contract SSVNetworkUpgrade is
);
}

function updateMaximumOperatorFee(uint64 maxFee) external override {
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_DAO],
abi.encodeWithSignature("updateMaximumOperatorFee(uint64)", maxFee)
);
}

function _delegateCall(address ssvModule, bytes memory callMessage) internal returns (bytes memory) {
/// @custom:oz-upgrades-unsafe-allow delegatecall
(bool success, bytes memory result) = ssvModule.delegatecall(callMessage);
Expand Down
9 changes: 8 additions & 1 deletion contracts/test/SSVViewsT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ contract SSVViewsT is ISSVViews {
using ClusterLib for Cluster;
using OperatorLib for Operator;
using ProtocolLib for StorageProtocol;

/*************************************/
/* Validator External View Functions */
/*************************************/
Expand All @@ -39,7 +40,9 @@ contract SSVViewsT is ISSVViews {
fee = operator.fee.expand();
}

function getOperatorDeclaredFee(uint64 operatorId) external view override returns (bool feeDeclared, uint256, uint64, uint64) {
function getOperatorDeclaredFee(
uint64 operatorId
) external view override returns (bool feeDeclared, uint256, uint64, uint64) {
OperatorFeeChangeRequest memory opFeeChangeRequest = SSVStorage.load().operatorFeeChangeRequests[operatorId];

return (
Expand Down Expand Up @@ -176,6 +179,10 @@ contract SSVViewsT is ISSVViews {
return SSVStorageProtocol.load().operatorMaxFeeIncrease;
}

function getMaximumOperatorFee() external view override returns (uint64 operatorMaxFee) {
return SSVStorageProtocol.load().operatorMaxFee;
}

function getOperatorFeePeriods()
external
view
Expand Down
5 changes: 3 additions & 2 deletions test/helpers/contract-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ export const initializeContract = async () => {
minimalOperatorFee: 100000000,
minimalBlocksBeforeLiquidation: 100800,
minimumLiquidationCollateral: 200000000,
validatorsPerOperatorLimit: 500
validatorsPerOperatorLimit: 500,
maximumOperatorFee: 21257960000000
};

DB = {
Expand Down Expand Up @@ -163,7 +164,7 @@ export const initializeContract = async () => {
await DB.ssvToken.mint(DB.owners[5].address, '10000000000000000000');
await DB.ssvToken.mint(DB.owners[6].address, '10000000000000000000');

// DB.ssvViews.contract = DB.ssvViews.contract.attach(DB.ssvNetwork.contract.address);
await DB.ssvNetwork.contract.updateMaximumOperatorFee(CONFIG.maximumOperatorFee);

return { contract: DB.ssvNetwork.contract, owner: DB.ssvNetwork.owner, ssvToken: DB.ssvToken, ssvViews: DB.ssvViews.contract };
};
Expand Down
18 changes: 10 additions & 8 deletions test/helpers/gas-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,25 @@ export enum GasGroup {

NETWORK_FEE_CHANGE,
WITHDRAW_NETWORK_EARNINGS,
OPERATOR_FEE_INCREASE_LIMIT,
OPERATOR_DECLARE_FEE_LIMIT,
OPERATOR_EXECUTE_FEE_LIMIT,
DAO_UPDATE_OPERATOR_FEE_INCREASE_LIMIT,
DAO_UPDATE_DECLARE_OPERATOR_FEE_PERIOD,
DAO_UPDATE_EXECUTE_OPERATOR_FEE_PERIOD,
DAO_UPDATE_OPERATOR_MAX_FEE,

CHANGE_LIQUIDATION_THRESHOLD_PERIOD,
CHANGE_MINIMUM_COLLATERAL
}

const MAX_GAS_PER_GROUP: any = {
/* REAL GAS LIMITS */
[GasGroup.REGISTER_OPERATOR]: 131890,
[GasGroup.REGISTER_OPERATOR]: 134500,
[GasGroup.REMOVE_OPERATOR]: 70200,
[GasGroup.REMOVE_OPERATOR_WITH_WITHDRAW]: 70200,
[GasGroup.SET_OPERATOR_WHITELIST]: 84300,

[GasGroup.DECLARE_OPERATOR_FEE]: 70000,
[GasGroup.CANCEL_OPERATOR_FEE]: 41900,
[GasGroup.EXECUTE_OPERATOR_FEE]: 49900,
[GasGroup.EXECUTE_OPERATOR_FEE]: 52000,
[GasGroup.REDUCE_OPERATOR_FEE]: 51900,

[GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER]: 202000,
Expand Down Expand Up @@ -88,9 +89,10 @@ const MAX_GAS_PER_GROUP: any = {

[GasGroup.NETWORK_FEE_CHANGE]: 45800,
[GasGroup.WITHDRAW_NETWORK_EARNINGS]: 62200,
[GasGroup.OPERATOR_FEE_INCREASE_LIMIT]: 38200,
[GasGroup.OPERATOR_DECLARE_FEE_LIMIT]: 40900,
[GasGroup.OPERATOR_EXECUTE_FEE_LIMIT]: 41000,
[GasGroup.DAO_UPDATE_OPERATOR_FEE_INCREASE_LIMIT]: 38200,
[GasGroup.DAO_UPDATE_DECLARE_OPERATOR_FEE_PERIOD]: 40900,
[GasGroup.DAO_UPDATE_EXECUTE_OPERATOR_FEE_PERIOD]: 41000,
[GasGroup.DAO_UPDATE_OPERATOR_MAX_FEE]: 38500,

[GasGroup.CHANGE_LIQUIDATION_THRESHOLD_PERIOD]: 41000,
[GasGroup.CHANGE_MINIMUM_COLLATERAL]: 41200,
Expand Down
7 changes: 7 additions & 0 deletions test/operators/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ describe('Register Operator Tests', () => {
)).to.be.revertedWithCustomError(ssvNetworkContract, 'FeeTooLow');
});

it('Register an operator with a fee thats too high reverts "FeeTooHigh"', async () => {
await expect(ssvNetworkContract.registerOperator(
helpers.DataGenerator.publicKey(0),
2e14,
)).to.be.revertedWithCustomError(ssvNetworkContract, 'FeeTooHigh');
});

it('Register same operator twice reverts "OperatorAlreadyExists"', async () => {
const publicKey = helpers.DataGenerator.publicKey(1);
await ssvNetworkContract.connect(helpers.DB.owners[1]).registerOperator(
Expand Down
Loading

0 comments on commit 5496324

Please sign in to comment.