Skip to content

Commit

Permalink
fix: added more functions for fuzz testing
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohsen-T committed Feb 5, 2024
1 parent eb3933e commit 547f4a7
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 50 deletions.
2 changes: 1 addition & 1 deletion contracts/SSVNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ contract SSVNetwork is
/* Operator External Functions */
/*******************************/

function registerOperator(bytes calldata publicKey, uint256 fee) external override returns (uint64 id) {
function registerOperator(bytes calldata publicKey, uint64 fee) external override returns (uint64 id) {
if (!RegisterAuth.load().authorization[msg.sender].registerOperator) revert NotAuthorized();

_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/ISSVOperators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface ISSVOperators is ISSVNetworkCore {
/// @notice Registers a new operator
/// @param publicKey The public key of the operator
/// @param fee The operator's fee (SSV)
function registerOperator(bytes calldata publicKey, uint256 fee) external returns (uint64);
function registerOperator(bytes calldata publicKey, uint64 fee) external returns (uint64);

/// @notice Removes an existing operator
/// @param operatorId The ID of the operator to be removed
Expand Down
100 changes: 80 additions & 20 deletions contracts/modules/Clusters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,98 @@
pragma solidity 0.8.18;

import "./SSVClusters.sol";
import "../libraries/ClusterLib.sol";

contract Clusters is SSVClusters {
using ClusterLib for Cluster;

constructor() {}

bytes[] publicKeys;
bytes32[] hashedClusters;
uint64[] public operatorIds;

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;
uint256 private sault = 0;

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 _generateOperatorIds() internal returns (uint64[] memory operatorIds) {
uint256 baseLength = (uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, sault))) %
MIN_OPERATORS_LENGTH);
uint256 length = 4 + baseLength * 3; // This will produce lengths 4, 7, 10, or 13
sault++;

operatorIds = new uint64[](length);
for (uint256 i = 0; i < length; i++) {
uint64 randomId;
bool unique;
do {
sault++; // Ensure a different seed for the random number
randomId = _generateRandomId();
unique = !_isDuplicate(randomId);
} while (!unique);

// Insert the unique randomId in a sorted position
uint256 j = i;
while (j > 0 && operatorIds[j - 1] > randomId) {
if (j < operatorIds.length) {
operatorIds[j] = operatorIds[j - 1]; // Shift larger IDs to the right
}
j--;
}
operatorIds[j] = randomId; // Insert the new ID in its correct sorted position
}
return operatorIds;
}

function _generateRandomId() internal returns (uint64) {
return uint64(uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender, sault))) % 2 ** 64);
}

function _isDuplicate(uint64 id) internal view returns (bool) {
for (uint256 i = 0; i < operatorIds.length; i++) {
if (operatorIds[i] == id) {
return true;
}
}
return false;
}

function helper_registerValidator(bytes calldata sharesData, uint256 amount, Cluster memory cluster) public {
StorageData storage s = SSVStorage.load();

bytes memory _publicKey = _generatePublicKey();
uint64[] memory _operatorIds = _generateOperatorIds();

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 helper_registerValidator(
bytes calldata publicKey,
uint64[] memory operatorIds,
bytes calldata sharesData,
uint256 amount,
Cluster memory cluster
) public {
require(
operatorIds.length < MIN_OPERATORS_LENGTH ||
operatorIds.length > MAX_OPERATORS_LENGTH ||
operatorIds.length % MODULO_OPERATORS_LENGTH != 1,
"Invalid OperatorIds Length"
);
require(publicKey.length == PUBLIC_KEY_LENGTH, "Invalid PublicKey Length");

bytes32 hashedCluster = keccak256(abi.encodePacked(msg.sender, operatorIds));

try this.registerValidator(publicKey, operatorIds, sharesData, amount, cluster) {
publicKeys.push(publicKey);
hashedClusters.push(hashedCluster);
try this.registerValidator(_publicKey, _operatorIds, sharesData, amount, cluster) {
publicKeys.push(_publicKey);
hashedClusters.push(_hashedCluster);
} catch {
assert(false);
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/modules/DAO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ contract DAO is SSVDAO {
this.updateNetworkFee(fee);
}

function check_invariant_networkfee() public returns (bool) {
function check_invariant_networkfee() public {
StorageProtocol storage sp = SSVStorageProtocol.load();
assert(sp.networkFee > 0);
}
Expand Down
59 changes: 43 additions & 16 deletions contracts/modules/Operators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,61 @@
pragma solidity 0.8.18;

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

contract Operators is SSVOperators {
using Types64 for uint64;
using Types256 for uint256;
using ProtocolLib for StorageProtocol;

uint64 private constant MINIMAL_OPERATOR_FEE = 100_000_000;
uint64 private constant PRECISION_FACTOR = 10_000;

uint64[] opIds;
uint256 private sault;
uint64 private minNetworkFee;

event Declared(uint64 maxAllowedFee, uint64 operatorFee);
event AssertionFailed(uint64 operatorId, bool isWhitelisted);
event AssertionFailed(uint64 operatorId, uint64 approvalBeginTime);

constructor() {
minNetworkFee = MINIMAL_OPERATOR_FEE;
StorageProtocol storage sp = SSVStorageProtocol.load();
sp.minimumBlocksBeforeLiquidation = 214800;
sp.minimumLiquidationCollateral = uint256(1000000000000000000).shrink();
sp.validatorsPerOperatorLimit = 500;
sp.declareOperatorFeePeriod = 604800;
sp.executeOperatorFeePeriod = 604800;
sp.operatorMaxFeeIncrease = 1000;
}
sp.operatorMaxFee = minNetworkFee * 2;

function helper_createOperator(bytes calldata publicKey, uint256 fee) public {
require(publicKey.length != 0 && publicKey[0] != 0, "invalid publicKey: cannot be empty");
sp.updateNetworkFee(0);
}

uint256 maxValue = 2 ** 64 * DEDUCTED_DIGITS;
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;
}

uint256 minN = (MINIMAL_OPERATOR_FEE + DEDUCTED_DIGITS - 1) / DEDUCTED_DIGITS;
uint256 maxN = SSVStorageProtocol.load().operatorMaxFee;
function _generateFee(uint64 min, uint64 max) public returns (uint64) {
require(max > min, "Max must be greater than min");
uint256 randomHash = uint256(keccak256(abi.encodePacked(sault, block.timestamp)));
sault++;
uint64 reducedHash = uint64(randomHash);
return (reducedHash % (max - min + 1)) + min;
}

require(fee > minN && fee < maxN, "fee value exceeded");
fee = fee * DEDUCTED_DIGITS;
function helper_createOperator() public {
uint64 minN = minNetworkFee;
uint64 maxN = SSVStorageProtocol.load().operatorMaxFee;

require(SSVStorage.load().operatorsPKs[keccak256(publicKey)] == 0, "Operator exists");
bytes memory publicKey = _generatePublicKey();
uint64 fee = _generateFee(minN, maxN);

try this.registerOperator(publicKey, fee) returns (uint64 operatorId) {
opIds.push(operatorId);
Expand Down Expand Up @@ -92,22 +111,30 @@ contract Operators is SSVOperators {

function check_removedOperatorNoFeeDeclared(uint64 operatorId) public {
operatorId = 1 + (operatorId % (uint64(opIds.length) - 1));

Operator storage operator = SSVStorage.load().operators[operatorId];

if (
//(operator.owner != address(0)) &&
(operator.snapshot.block == 0) &&
(SSVStorage.load().operatorFeeChangeRequests[operatorId].approvalBeginTime != 0)
) {
emit AssertionFailed(operatorId, SSVStorage.load().operatorFeeChangeRequests[operatorId].approvalBeginTime);
}
}

function check_removedOperatorBalances() public returns (bool) {
function check_removedOperatorBalances() public {
for (uint256 i; i < opIds.length; i++) {
Operator memory operator = SSVStorage.load().operators[opIds[i]];
assert(operator.validatorCount > 0 || operator.snapshot.balance == 0);
}
}

function check_operatorEarningsWithBalance() public {
StorageProtocol memory sp = SSVStorageProtocol.load();
uint64 earnings;
for (uint256 i; i < opIds.length; i++) {
Operator storage operator = SSVStorage.load().operators[opIds[i]];
if (operator.validatorCount == 0) assert(operator.snapshot.balance == 0);
Operator memory operator = SSVStorage.load().operators[opIds[i]];
earnings += operator.snapshot.balance;
}
assert(sp.daoBalance == earnings);
}
}
2 changes: 0 additions & 2 deletions contracts/modules/SSVDAO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ contract SSVDAO is ISSVDAO {
uint64 private constant MINIMAL_LIQUIDATION_THRESHOLD = 100_800;

function updateNetworkFee(uint256 fee) external override {
if (fee == 0) revert FeeTooLow();

StorageProtocol storage sp = SSVStorageProtocol.load();
uint64 previousFee = sp.networkFee;

Expand Down
4 changes: 2 additions & 2 deletions contracts/modules/SSVOperators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract SSVOperators is ISSVOperators {
/* Operator External Functions */
/*******************************/

function registerOperator(bytes calldata publicKey, uint256 fee) external override returns (uint64 id) {
function registerOperator(bytes calldata publicKey, uint64 fee) external override returns (uint64 id) {
if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) {
revert ISSVNetworkCore.FeeTooLow();
}
Expand All @@ -42,7 +42,7 @@ contract SSVOperators is ISSVOperators {
owner: msg.sender,
snapshot: ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}),
validatorCount: 0,
fee: fee.shrink(),
fee: fee,
whitelisted: false
});
s.operatorsPKs[hashedPk] = id;
Expand Down
6 changes: 3 additions & 3 deletions contracts/test/SSVNetworkUpgrade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ contract SSVNetworkUpgrade is
/* Operator External Functions */
/*******************************/

function registerOperator(bytes calldata publicKey, uint256 fee) external override returns (uint64 id) {
function registerOperator(bytes calldata publicKey, uint64 fee) external override returns (uint64 id) {
bytes memory result = _delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],
abi.encodeWithSignature("registerOperator(bytes,uint256)", publicKey, fee)
abi.encodeWithSignature("registerOperator(bytes,uint64)", publicKey, fee)
);
return abi.decode(result, (uint64));
}
Expand Down Expand Up @@ -336,7 +336,7 @@ contract SSVNetworkUpgrade is
}

function updateMaximumOperatorFee(uint64 maxFee) external override {
_delegateCall(
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_DAO],
abi.encodeWithSignature("updateMaximumOperatorFee(uint64)", maxFee)
);
Expand Down
4 changes: 2 additions & 2 deletions contracts/test/modules/SSVOperatorsUpdate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract SSVOperatorsUpdate is ISSVOperators {
/* Operator External Functions */
/*******************************/

function registerOperator(bytes calldata publicKey, uint256 fee) external override returns (uint64 id) {
function registerOperator(bytes calldata publicKey, uint64 fee) external override returns (uint64 id) {
if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) {
revert ISSVNetworkCore.FeeTooLow();
}
Expand All @@ -38,7 +38,7 @@ contract SSVOperatorsUpdate is ISSVOperators {
owner: msg.sender,
snapshot: ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}),
validatorCount: 0,
fee: fee.shrink(),
fee: fee,
whitelisted: false
});
s.operatorsPKs[hashedPk] = id;
Expand Down
2 changes: 1 addition & 1 deletion crytic-export/combined_solc.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion echidna.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
testMode: assertion
testLimit: 5000
testLimit: 2000
corpusDir: 'echidna-corpus'
cryticArgs: ['--solc-remaps', '@openzeppelin=node_modules/@openzeppelin']
format: text
# filterFunctions: ['helper_createOperator()']
# contractAddr: '0xContractAddress'
# filterBlacklist: false

0 comments on commit 547f4a7

Please sign in to comment.