Skip to content

Commit

Permalink
fix: updated the Operators.sol and Clusters.sol
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohsen-T committed Feb 20, 2024
1 parent d8605a9 commit 16318ea
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 86 deletions.
136 changes: 92 additions & 44 deletions contracts/echidna/Clusters.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;

import "../token/SSVToken.sol";
import "../modules/SSVClusters.sol";
import "../libraries/ClusterLib.sol";
import "../libraries/ProtocolLib.sol";
import "../libraries/OperatorLib.sol";
import "./EchidnaLib.sol";
import "./Operators.sol";

contract Clusters is SSVClusters {
using ClusterLib for Cluster;
Expand All @@ -12,71 +16,115 @@ contract Clusters is SSVClusters {
using ProtocolLib for StorageProtocol;

bytes[] publicKeys;
bytes32[] hashedClusters;
uint64[] opIds;

Check warning on line 19 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Explicitly mark visibility of state
mapping(bytes32 => Cluster) clusters;

Check warning on line 20 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Explicitly mark visibility of state

uint64 private constant MINIMAL_OPERATOR_FEE = 100_000_000;
uint64 private constant MAXIMUM_OPERATOR_FEE = 76_528_650_000_000;
uint64 private constant MIN_OPERATORS_LENGTH = 4;
uint64 private constant MAX_OPERATORS_LENGTH = 13;
uint64 private constant MODULO_OPERATORS_LENGTH = 3;
uint64 private constant PUBLIC_KEY_LENGTH = 48;
uint64 private constant MIN_BLOCKS_BEFORE_LIQUIDATION = 214800;
uint256 private constant MIN_LIQUIDATION_COLLATERAL = 1000000000000000000;
uint256 private constant TOTAL_SSVTOKEN_BALANCE = 1000000000000000000000;
uint256 private sault = 0;

Operators ssvOperators;

Check warning on line 33 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Explicitly mark visibility of state
SSVToken ssvToken;

Check warning on line 34 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Explicitly mark visibility of state

event AssertionFailed(uint256 amount);

constructor() {

Check warning on line 38 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Explicitly mark visibility in function
// operators = new Operators();
// for (uint i; i < 4; i++) {
// uint64 operatorId = operators.helper_createOperator();
// }
// assert(operators.getOperatorBlock(1) == SSVStorage.load().operators[1].snapshot.block);
StorageProtocol storage sp = SSVStorageProtocol.load();
sp.minimumBlocksBeforeLiquidation = MIN_BLOCKS_BEFORE_LIQUIDATION;
sp.minimumLiquidationCollateral = MIN_LIQUIDATION_COLLATERAL.shrink();
sp.validatorsPerOperatorLimit = 500;
sp.declareOperatorFeePeriod = 604800;
sp.executeOperatorFeePeriod = 604800;
sp.operatorMaxFeeIncrease = 1000;
sp.operatorMaxFee = MAXIMUM_OPERATOR_FEE;
sp.updateNetworkFee(0);

ssvOperators = new Operators();
for (uint256 i; i < MAX_OPERATORS_LENGTH; i++) {
uint64 operatorId = ssvOperators.helper_createOperator();
opIds.push(operatorId);
updateStorage(operatorId, ssvOperators.getOperatorById(operatorId));
}

ssvToken = new SSVToken();
SSVStorage.load().token = ssvToken;
ssvToken.approve(address(this), TOTAL_SSVTOKEN_BALANCE);
}

function _generatePublicKey() internal returns (bytes memory) {
bytes memory randomBytes = new bytes(48);
for (uint i = 0; i < 48; i++) {
randomBytes[i] = bytes1(
uint8(uint(keccak256(abi.encodePacked(sault, block.timestamp, msg.sender, i))) % 256)
);
function updateStorage(uint64 id, ISSVNetworkCore.Operator memory operator) internal {
StorageData storage s = SSVStorage.load();
s.operators[id] = operator;
}

function check_registerValidator() public {

Check warning on line 66 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Function name must be in mixedCase
StorageProtocol storage sp = SSVStorageProtocol.load();
StorageData storage s = SSVStorage.load();

bytes memory publicKey = EchidnaLib.generatePublicKey(sault++);
bytes memory emptyBytes;

bytes32 hashedCluster = keccak256(abi.encodePacked(msg.sender, opIds));
Cluster memory cluster = clusters[hashedCluster];
cluster.active = true;

uint64 liquidationThreshold = MIN_BLOCKS_BEFORE_LIQUIDATION * EchidnaLib.getBurnRate(opIds, s);
uint256 min = liquidationThreshold.expand() > MIN_LIQUIDATION_COLLATERAL
? liquidationThreshold.expand()
: MIN_LIQUIDATION_COLLATERAL;
uint256 amount = EchidnaLib.generateRandomShrinkable(sault++, min, min * 2);

try this.registerValidator(publicKey, opIds, emptyBytes, amount, cluster) {

Check failure on line 83 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Expected indentation of 8 spaces but found 12

Check failure on line 83 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Expected indentation of 8 spaces but found 82
publicKeys.push(publicKey);

(uint64 clusterIndex, ) = OperatorLib.updateClusterOperators(opIds, true, true, 1, s, sp);
cluster.balance += amount;
cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex());
cluster.validatorCount += 1;

clusters[hashedCluster] = cluster;
} catch {
emit AssertionFailed(amount);
}
sault++;
return randomBytes;
}

// function helper_registerValidator(bytes calldata sharesData, uint256 amount, Cluster memory cluster) public {
// StorageData storage s = SSVStorage.load();
// bytes memory _publicKey = _generatePublicKey();
// uint64[] memory _operatorIds = operators.getOperatorIds();

// bytes32 _hashedCluster = keccak256(abi.encodePacked(msg.sender, _operatorIds));

// bytes32 clusterData = s.clusters[_hashedCluster];
// if (clusterData == bytes32(0)) {
// cluster.validatorCount = 0;
// cluster.networkFeeIndex = 0;
// cluster.index = 0;
// cluster.balance = 0;
// cluster.active = true;
// } else {
// s.clusters[_hashedCluster] = cluster.hashClusterData();
// function check_bulkRegisterValidator(uint256 amount) public {
// bytes[] memory publicKey = new bytes[](4);
// bytes[] memory sharesData = new bytes[](4);
// for (uint256 i; i < publicKey.length; i++) {
// publicKey[i] = EchidnaLib.generatePublicKey(sault++);
// }
// Cluster memory cluster;
// cluster.active = true;

// uint256 minLiquidationCollateral = SSVStorageProtocol.load().minimumLiquidationCollateral.expand();
// require(amount > minLiquidationCollateral, "InsufficientBalance");

// try this.registerValidator(_publicKey, _operatorIds, sharesData, amount, cluster) {
// publicKeys.push(_publicKey);
// hashedClusters.push(_hashedCluster);
// try this.bulkRegisterValidator(publicKey, opIds, sharesData, amount, cluster) {
// for (uint256 i; i < publicKey.length; i++) {
// publicKeys.push(publicKey[i]);
// }
// } catch {
// assert(false);
// }
// }

// function check_removeValidator(uint64 publicKeyId, uint64[] calldata operatorIds, Cluster memory cluster) public {
// publicKeyId = publicKeyId % uint64(publicKeys.length);

// this.removeValidator(publicKeys[publicKeyId], operatorIds, cluster);
// }
function check_validRegisteredOperators() public {

Check warning on line 118 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Function name must be in mixedCase
assert(opIds.length == MAX_OPERATORS_LENGTH);

// function check_invariant_validatorPKs() public {
// StorageData storage s = SSVStorage.load();
for (uint256 i; i < opIds.length; i++) {
ISSVNetworkCore.Operator memory operator = ssvOperators.getOperatorById(opIds[i]);
assert(operator.owner != address(0));
}
}

// for (uint64 i = 0; i < hashedClusters.length; i++) {
// assert(s.clusters[hashedClusters[i]] == bytes32(0));
// }
// }
function check_RegisteredValidatorsCount() public {

Check warning on line 127 in contracts/echidna/Clusters.sol

View workflow job for this annotation

GitHub Actions / ci

Function name must be in mixedCase
assert(sault == 0 || publicKeys.length > 0);
}
}
60 changes: 60 additions & 0 deletions contracts/echidna/EchidnaLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;

import "../libraries/Types.sol";
import "../libraries/SSVStorage.sol";

library EchidnaLib {
function generatePublicKey(uint256 sault) internal view returns (bytes memory) {
bytes memory randomBytes = new bytes(48);
for (uint i = 0; i < 48; i++) {
randomBytes[i] = bytes1(
uint8(uint(keccak256(abi.encodePacked(sault, block.timestamp, msg.sender, i))) % 256)
);
}
return randomBytes;
}

function generateRandom(uint256 salt, uint256 min, uint256 max) internal view returns (uint256) {
require(max > min, "max must be greater than min");
uint256 random = uint256(keccak256(abi.encodePacked(salt, block.timestamp, msg.sender))) % (max - min + 1);
return min + random;
}

function generateRandomShrinkable(uint256 sault, uint256 min, uint256 max) internal returns (uint256) {
require(max > min, "Max must be greater than min");
require(
min % DEDUCTED_DIGITS == 0 && max % DEDUCTED_DIGITS == 0,
"Min and Max must be multiples of 10,000,000"
);

uint256 randomHash = uint256(keccak256(abi.encodePacked(sault, block.timestamp)));
sault++;
uint64 reducedHash = uint64(randomHash);

// Calculate a fee within the range, ensuring it ends in a multiple of 10,000,000
uint256 range = (max - min) / DEDUCTED_DIGITS + 1;
uint256 feeMultiplier = (reducedHash % range) * DEDUCTED_DIGITS;
uint256 fee = min + feeMultiplier;
fee = fee - (fee % DEDUCTED_DIGITS);

return fee;
}

function getBurnRate(uint64[] memory operatorIds, StorageData storage s) internal view returns (uint64 burnRate) {
uint256 operatorsLength = operatorIds.length;

for (uint256 i; i < operatorsLength; ) {
uint64 operatorId = operatorIds[i];

ISSVNetworkCore.Operator memory operator = s.operators[operatorId];
if (operator.snapshot.block != 0) {
burnRate += operator.fee;
}

unchecked {
++i;
}
}
}
}
45 changes: 6 additions & 39 deletions contracts/echidna/Operators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity 0.8.18;

import "../modules/SSVOperators.sol";
import "../libraries/ProtocolLib.sol";
import "./EchidnaLib.sol";

contract Operators is SSVOperators {
using Types64 for uint64;
Expand All @@ -29,55 +30,21 @@ contract Operators is SSVOperators {
sp.executeOperatorFeePeriod = 604800;
sp.operatorMaxFeeIncrease = 1000;
sp.operatorMaxFee = MAXIMUM_OPERATOR_FEE;
sp.validatorsPerOperatorLimit = 50;

sp.updateNetworkFee(0);
}

function getOperatorIds() public view returns (uint64[] memory) {
return opIds;
}

function getOperatorBlock(uint64 operatorId) public view returns (uint256) {
return SSVStorage.load().operators[operatorId].snapshot.block;
}

function _generatePublicKey() internal returns (bytes memory) {
bytes memory randomBytes = new bytes(48);
for (uint i = 0; i < 48; i++) {
randomBytes[i] = bytes1(
uint8(uint(keccak256(abi.encodePacked(sault, block.timestamp, msg.sender, i))) % 256)
);
}
sault++;
return randomBytes;
}

function _generateFee(uint256 min, uint256 max) internal returns (uint256) {
require(max > min, "Max must be greater than min");
require(
min % DEDUCTED_DIGITS == 0 && max % DEDUCTED_DIGITS == 0,
"Min and Max must be multiples of 10,000,000"
);

uint256 randomHash = uint256(keccak256(abi.encodePacked(sault, block.timestamp)));
sault++;
uint64 reducedHash = uint64(randomHash);

// Calculate a fee within the range, ensuring it ends in a multiple of 10,000,000
uint256 range = (max - min) / DEDUCTED_DIGITS + 1;
uint256 feeMultiplier = (reducedHash % range) * DEDUCTED_DIGITS;
uint256 fee = min + feeMultiplier;
fee = fee - (fee % DEDUCTED_DIGITS);

return fee;
function getOperatorById(uint64 id) public view returns (ISSVNetworkCore.Operator memory operator) {
return SSVStorage.load().operators[id];
}

function helper_createOperator() public returns (uint64) {
uint256 minN = minNetworkFee;
uint256 maxN = SSVStorageProtocol.load().operatorMaxFee;

bytes memory publicKey = _generatePublicKey();
uint256 fee = _generateFee(minN, maxN);
bytes memory publicKey = EchidnaLib.generatePublicKey(sault++);
uint256 fee = EchidnaLib.generateRandomShrinkable(sault++, minN, maxN);

try this.registerOperator(publicKey, fee) returns (uint64 operatorId) {
opIds.push(operatorId);
Expand Down
2 changes: 1 addition & 1 deletion contracts/token/SSVToken.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.4;
pragma solidity 0.8.18;

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
Expand Down
2 changes: 1 addition & 1 deletion echidna.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
testMode: assertion
testLimit: 2000
testLimit: 5000
corpusDir: 'echidna-corpus'
cryticArgs: ['--solc-remaps', '@openzeppelin=node_modules/@openzeppelin']
format: text
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
"lint:fix": "eslint --fix . --ext .ts",
"solidity-coverage": "NO_GAS_ENFORCE=1 npx hardhat coverage",
"slither": "slither contracts --solc-remaps @openzeppelin=node_modules/@openzeppelin",
"size-contracts": "npx hardhat size-contracts"
"size-contracts": "npx hardhat size-contracts",
"install-echidna": "brew install echidna",
"echidna": "node .echidna.test.js"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
Expand Down

0 comments on commit 16318ea

Please sign in to comment.