Skip to content

Commit

Permalink
getWhitelistedOperators legacy whitelist support, fix reduceOperatorFee
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed Jun 8, 2024
1 parent e1009fa commit add2434
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 41 deletions.
4 changes: 3 additions & 1 deletion contracts/modules/SSVOperators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "../libraries/CoreLib.sol";
import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";

contract SSVOperators is ISSVOperators {
uint64 private constant MINIMAL_OPERATOR_FEE = 100_000_000;
uint64 private constant MINIMAL_OPERATOR_FEE = 1_000_000_000;
uint64 private constant PRECISION_FACTOR = 10_000;

using Types256 for uint256;
Expand Down Expand Up @@ -155,6 +155,8 @@ contract SSVOperators is ISSVOperators {
Operator memory operator = s.operators[operatorId];
operator.checkOwner();

if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) revert FeeTooLow();

uint64 shrunkAmount = fee.shrink();
if (shrunkAmount >= operator.fee) revert FeeIncreaseNotAllowed();

Expand Down
29 changes: 16 additions & 13 deletions contracts/modules/SSVViews.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ contract SSVViews is ISSVViews {

function getWhitelistedOperators(
uint64[] calldata operatorIds,
address whitelistedAddress
address addressToCheck
) external view override returns (uint64[] memory whitelistedOperatorIds) {
uint256 operatorsLength = operatorIds.length;
if (operatorsLength == 0) return whitelistedOperatorIds;
if (operatorsLength == 0 || addressToCheck == address(0)) return whitelistedOperatorIds;

StorageData storage s = SSVStorage.load();

Expand All @@ -97,7 +97,7 @@ contract SSVViews is ISSVViews {
for (uint256 blockIndex; blockIndex < masks.length; ++blockIndex) {
// Only check blocks that have operator IDs
if (masks[blockIndex] != 0) {
whitelistedMask = s.addressWhitelistedForOperators[whitelistedAddress][blockIndex];
whitelistedMask = s.addressWhitelistedForOperators[addressToCheck][blockIndex];

// This will give the matching whitelisted operators
matchedMask = whitelistedMask & masks[blockIndex];
Expand Down Expand Up @@ -130,19 +130,22 @@ contract SSVViews is ISSVViews {
uint64 operatorId = operatorIds[operatorIndex];

// Check if operatorId is already in internalWhitelistedOperatorIds
if (internalWhitelistIndex < internalCount && operatorId == internalWhitelistedOperatorIds[internalWhitelistIndex]) {
if (
internalWhitelistIndex < internalCount &&
operatorId == internalWhitelistedOperatorIds[internalWhitelistIndex]
) {
whitelistedOperatorIds[count++] = operatorId;
++internalWhitelistIndex;
} else {
// Check whitelisting contract
address whitelistingContract = s.operatorsWhitelist[operatorId];
if (whitelistingContract != address(0)) {
if (
OperatorLib.isWhitelistingContract(whitelistingContract) &&
ISSVWhitelistingContract(whitelistingContract).isWhitelisted(whitelistedAddress, operatorId)
) {
whitelistedOperatorIds[count++] = operatorId;
}
address whitelistedAddress = s.operatorsWhitelist[operatorId];

// Legacy address whitelists (EOAs or generic contracts)
if (
whitelistedAddress == addressToCheck ||
(OperatorLib.isWhitelistingContract(whitelistedAddress) &&
ISSVWhitelistingContract(whitelistedAddress).isWhitelisted(addressToCheck, operatorId))
) {
whitelistedOperatorIds[count++] = operatorId;
}
}
++operatorIndex;
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/mocks/BeneficiaryContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ contract BeneficiaryContract {
}

function registerOperator() external returns (uint64 operatorId) {
return ISSVOperators(ssvContract).registerOperator("0xcafecafe", 100000000, false);
return ISSVOperators(ssvContract).registerOperator("0xcafecafe", 1000000000, false);
}
}
12 changes: 8 additions & 4 deletions test-forked/operators-whitelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,17 @@ describe('Whitelisting Tests (fork) - Pre-upgrade SSV Core Contracts Tests', ()
// get the current whitelisted address
const prevWhitelistedAddress = (await ssvViews.read.getOperatorById([314]))[3];

await ssvNetwork.write.setOperatosWhitelists([[314, 315, 316, 317], [owners[2].account.address]], {
await ssvNetwork.write.setOperatosWhitelists([[315, 316, 317], [owners[2].account.address]], {
account: { address: '0xB4084F25DfCb2c1bf6636b420b59eda807953769' },
});

expect(
await ssvViews.read.getWhitelistedOperators([[314, 315, 316, 317], owners[2].account.address]),
).to.deep.equal([314n, 315n, 316n, 317n]);
expect(await ssvViews.read.getWhitelistedOperators([[315, 316, 317], owners[2].account.address])).to.deep.equal([
315n,
316n,
317n,
]);

expect(await ssvViews.read.getWhitelistedOperators([[314], prevWhitelistedAddress])).to.deep.equal([314n]);

// the operator uses the previous whitelisting main address
expect((await ssvViews.read.getOperatorById([314]))[3]).to.deep.equal(prevWhitelistedAddress);
Expand Down
2 changes: 1 addition & 1 deletion test/helpers/contract-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const CONFIG: SSVConfig = {
operatorMaxFeeIncrease: 1000,
declareOperatorFeePeriod: 3600, // HOUR
executeOperatorFeePeriod: 86400, // DAY
minimalOperatorFee: 100000000n,
minimalOperatorFee: 1000000000n,
minimalBlocksBeforeLiquidation: 100800,
minimumLiquidationCollateral: 200000000,
validatorsPerOperatorLimit: 500,
Expand Down
6 changes: 6 additions & 0 deletions test/operators/update-fee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,12 @@ describe('Operator Fee Tests', () => {
]);
});

it('Reduce fee with a fee thats too low reverts "FeeTooLow"', async () => {
await expect(ssvNetwork.write.reduceOperatorFee([1, 10e6], { account: owners[2].account })).to.be.rejectedWith(
'FeeTooLow',
);
});

it('Reduce fee with an increased value reverts "FeeIncreaseNotAllowed"', async () => {
await expect(
ssvNetwork.write.reduceOperatorFee([1, initialFee * 2n], { account: owners[2].account }),
Expand Down
75 changes: 54 additions & 21 deletions test/sanity/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,14 @@ describe('Balance Tests', () => {
const newNetworkFee = networkFee * 2n;
await ssvNetwork.write.updateNetworkFee([newNetworkFee]);

const newBurnPerBlock = (CONFIG.minimalOperatorFee * 4n) + newNetworkFee;
const newBurnPerBlock = CONFIG.minimalOperatorFee * 4n + newNetworkFee;
await mine(1);


expect(
await ssvViews.read.getBalance([owners[4].account.address, cluster1.operatorIds, cluster1.cluster]),
).to.equal(minDepositAmount - (burnPerBlock * 2n) - newBurnPerBlock);
).to.equal(minDepositAmount - burnPerBlock * 2n - newBurnPerBlock);
expect((await ssvViews.read.getNetworkEarnings()) - initNetworkFeeBalance).to.equal(
(networkFee * 4n) + (newNetworkFee * 2n),
networkFee * 4n + newNetworkFee * 2n,
);

const minDep2 = minDepositAmount * 2n;
Expand All @@ -314,7 +313,6 @@ describe('Balance Tests', () => {
active: true,
});


await mine(2);

expect(await ssvViews.read.getOperatorEarnings([1])).to.equal(
Expand All @@ -327,10 +325,10 @@ describe('Balance Tests', () => {
expect(await ssvViews.read.getOperatorEarnings([5])).to.equal(CONFIG.minimalOperatorFee * 2n);
expect(
await ssvViews.read.getBalance([owners[4].account.address, cluster1.operatorIds, cluster1.cluster]),
).to.equal(minDepositAmount - (burnPerBlock * 2n) - (newBurnPerBlock * 5n));
).to.equal(minDepositAmount - burnPerBlock * 2n - newBurnPerBlock * 5n);
expect(
await ssvViews.read.getBalance([owners[4].account.address, cluster2.args.operatorIds, cluster2.args.cluster]),
).to.equal(minDep2 - (newBurnPerBlock * 2n));
).to.equal(minDep2 - newBurnPerBlock * 2n);

// cold cluster + cluster1 * networkFee (4) + (cold cluster + cluster1 * newNetworkFee (5 + 5)) + cluster2 * newNetworkFee (2)
expect((await ssvViews.read.getNetworkEarnings()) - initNetworkFeeBalance).to.equal(
Expand Down Expand Up @@ -363,20 +361,6 @@ describe('Balance Tests', () => {
);
});

it('Check operator earnings and cluster balance when reducing operator fee"', async () => {
const newFee = CONFIG.minimalOperatorFee / 2n;
await ssvNetwork.write.reduceOperatorFee([1, newFee]);

await mine(2);

expect(await ssvViews.read.getOperatorEarnings([1])).to.equal(
CONFIG.minimalOperatorFee * 4n + (CONFIG.minimalOperatorFee + newFee * 2n),
);
expect(
await ssvViews.read.getBalance([owners[4].account.address, cluster1.operatorIds, cluster1.cluster]),
).to.equal(minDepositAmount - burnPerBlock - (CONFIG.minimalOperatorFee * 3n + networkFee) * 2n - newFee * 2n);
});

it('Check cluster balance after withdraw and deposit', async () => {
await mine(1);
expect(
Expand Down Expand Up @@ -541,3 +525,52 @@ describe('Balance Tests', () => {
);
});
});

describe('Balance Tests (reduce fee)', () => {
beforeEach(async () => {
// Initialize contract
const metadata = await initializeContract();
ssvNetwork = metadata.ssvNetwork;
ssvViews = metadata.ssvNetworkViews;
ssvToken = metadata.ssvToken;

// Register operators
await registerOperators(0, 14, CONFIG.minimalOperatorFee * 2n);

networkFee = CONFIG.minimalOperatorFee;
burnPerBlock = CONFIG.minimalOperatorFee * 2n * 4n + networkFee;
minDepositAmount = BigInt(CONFIG.minimalBlocksBeforeLiquidation) * burnPerBlock;

// Set network fee
await ssvNetwork.write.updateNetworkFee([networkFee]);

// Register validators
// cold register
await coldRegisterValidator();

cluster1 = (
await bulkRegisterValidators(4, 1, DEFAULT_OPERATOR_IDS[4], minDepositAmount, {
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0n,
active: true,
})
).args;
});

it('Check operator earnings and cluster balance when reducing operator fee"', async () => {
const prevOperatorFee = CONFIG.minimalOperatorFee * 2n;
const newFee = CONFIG.minimalOperatorFee;
await ssvNetwork.write.reduceOperatorFee([1, newFee]);

await mine(2);

expect(await ssvViews.read.getOperatorEarnings([1])).to.equal(
prevOperatorFee * 4n + (prevOperatorFee + newFee * 2n),
);
expect(
await ssvViews.read.getBalance([owners[4].account.address, cluster1.operatorIds, cluster1.cluster]),
).to.equal(minDepositAmount - burnPerBlock - (prevOperatorFee * 3n + networkFee) * 2n - newFee * 2n);
});
});

0 comments on commit add2434

Please sign in to comment.