StakingMaster Contract

The `StakingMaster` contract is the central contract of the staking system. It handles the logic for users staking ETH, managing their stakes,unstake and interacting with the `CLETH` token and individual `StakeHolder` contracts.

Functions

  1. 1.
    stake():
Users can put their ETH into this system by using the stake() function. When they do this, the system keeps track of how much ETH they've put in and gives them special tokens called CLETH tokens as a reward. This function also creates a separate place for each user to keep their ETH safe within the system.
function stake() public payable {
require(msg.value > 0, "Must send ETH to stake");
StakeHolder stakeHolder = stakeHolders[msg.sender];
if (address(stakeHolder) == address(0)) {
// Create aaaaa new StakeHolder contract
stakeHolder = new StakeHolder{value: msg.value}(msg.sender, address(this));
stakeHolders[msg.sender] = stakeHolder;
} else {
// Send the ETH to the existing StakeHolder contract
(bool success, ) = address(stakeHolder).call{value: msg.value}("");
require(success, "Failed to send ETH to StakeHolder");
}
stakedBalance[msg.sender] += msg.value;
lastStakeTime[msg.sender] = block.timestamp;
totalPool += msg.value;
userHistory[msg.sender].push(UserAction(ActionType.Staked, msg.value, block.timestamp));
clethToken.mint(msg.sender, msg.value);
emit Staked(msg.sender, stakeHolder, msg.value);
}
  1. 2.
    unstake(uint256 amount):
When users want to withdraw their ETH, they use the unstake() function. But they can only do this after a certain amount of time has passed. Once that time is up, they can take their ETH back, and the CLETH tokens they received earlier will be burned.
function unstake(uint256 amount) public onlyWhitelisted notBlacklisted{
require(block.timestamp >= lastStakeTime[msg.sender] + lockDuration, "Tokens are still locked");
require(stakedBalance[msg.sender] >= amount, "Not enough staked ETH");
require(clethToken.balanceOf(msg.sender) >= amount, "Not enough CLETH tokens");
stakedBalance[msg.sender] -= amount;
clethToken.burn(msg.sender, amount);
payable(msg.sender).transfer(amount);
totalPool -= amount;
userHistory[msg.sender].push(UserAction(ActionType.Unstaked, amount, block.timestamp));
}
  1. 3.
    addToWhitelist(address user):
The addToWhitelist() function allows the owner to include a user in the whitelist, enabling them to participate in staking and unstaking activities within the system. It is restricted to the owner to maintain control over who can access these features.
function addToWhitelist(address user) public onlyOwner {
userStatuses[user] = UserStatus.Whitelisted;
}
  1. 4.
    addToBlacklist(address user):
With the addToBlacklist() function, the owner can designate a user to be part of the blacklist, preventing them from engaging in staking activities within the system. This authority is exclusive to the owner role to regulate user access.
function addToBlacklist(address user) public onlyOwner {
userStatuses[user] = UserStatus.Blacklisted;
}
  1. 5.
    removeFromWhitelist(address user) and removeFromBlacklist(address user):
Using removeFromWhitelist() and removeFromBlacklist() functions, the owner can remove a user from either the whitelist or the blacklist. This action reverts the user's status to 'Unknown', reinstating their ability to potentially participate based on the system's criteria.
function removeFromWhitelist(address user) public onlyOwner {
userStatuses[user] = UserStatus.Unknown;
}
function removeFromBlacklist(address user) public onlyOwner {
userStatuses[user] = UserStatus.Unknown;
}
  1. 6.
    transferOwnership(address newOwner):
The transferOwnership() function allows the current owner to transfer ownership of the contract to a new address. This action should be performed cautiously as it hands over control and management rights to the specified new owner.
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "New owner cannot be the zero address.");
owner = newOwner;
}
  1. 7.
    Utility Functions:
The 'Get Staked Balance' function reveals the amount of Ethereum an individual user has invested in the system, while the 'Get Last Stake Time' function discloses the precise time of the user's last investment.
On a broader scale, the 'Get Total Pool' function offers insight into the cumulative Ethereum amassed from all users within the contract.
Additionally, the 'Get Stake Holder Information' function furnishes comprehensive details about an individual user's stake, encompassing both the quantity of Ethereum invested and the timestamp of their most recent investment.
function getStakedBalance(address account) public view returns (uint256) {
return stakedBalance[account];
}
function getLastStakeTime(address account) public view returns (uint256) {
return lastStakeTime[account];
}
function getTotalPool() public view returns (uint256) {
return totalPool;
}
function getStakeHolderInfo(address user) public view returns (address, uint256) {
StakeHolder stakeHolder = stakeHolders[user];
// Ensure the user has a StakeHolder contract
require(address(stakeHolder) != address(0), "User has no stake holder contract");
// The address of the StakeHolder contract
address stakeHolderAddress = address(stakeHolder);
// The amount of ETH stored in the StakeHolder contract
uint256 stakeHolderBalance = address(stakeHolder).balance;
return (stakeHolderAddress, stakeHolderBalance);
}
  1. 8.
    depositToNodeOperators
The "depositToNodeOperators" function serves the purpose of facilitating ETH deposits to Node Operators. This function operates within a smart contract and is designed with certain restrictions and functionalities:
Role and Access Control: The function is restricted to only be called by the owner of the contract, ensuring that only authorized personnel, usually the contract owner, can initiate deposits to the staking service.
Function Signature: The function is named "depositToNodeOperators" and is designed to be callable from outside the contract (external). Additionally, it has a custom modifier called "onlyOwner" to restrict usage to the contract's owner.
/function setNodeOperatorsDepositor(address _NodeOperatorsDepositor) external onlyOwner {
NodeOperatorsDepositor = INodeOperatorsEth2Depositor(_NodeOperatorsDepositor);
}
function depositToNodeOperators(
address payable stakeHolderAddress,
string calldata pubkey,
string calldata withdrawal_credentials,
string calldata signature,
string calldata deposit_data_root
) external onlyOwner {
StakeHolder stakeHolder = StakeHolder(stakeHolderAddress);
// Convert strings to bytes
bytes memory pubkeyBytes = hexStringToBytes(pubkey);
bytes memory withdrawalCredentialsBytes = hexStringToBytes(withdrawal_credentials);
bytes memory signatureBytes = hexStringToBytes(signature);
bytes32 depositDataRootBytes32 = hexStringToBytes32(deposit_data_root);
// Create single-element arrays
bytes[] memory pubkeys = new bytes[](1);
pubkeys[0] = pubkeyBytes;
bytes[] memory withdrawalCredentials = new bytes[](1);
withdrawalCredentials[0] = withdrawalCredentialsBytes;
bytes[] memory signatures = new bytes[](1);
signatures[0] = signatureBytes;
bytes32[] memory depositDataRoots = new bytes32[](1);
depositDataRoots[0] = depositDataRootBytes32;
uint256 depositAmount = 32 ether; // Assuming 32 ETH per deposit
require(address(stakeHolder).balance >= depositAmount, "Insufficient funds in StakeHolder");
stakeHolder.transferETHToContract(depositAmount, payable(address(this)));
require(address(this).balance >= depositAmount, "Insufficient ETH for deposit");
NodeOperatorsDepositor.deposit{value: depositAmount}(
pubkeys,
withdrawalCredentials,
signatures,
depositDataRoots
);
}
function hexStringToBytes(string memory hexString) internal pure returns (bytes memory) {
bytes memory b = bytes(hexString);
require(b.length % 2 == 0, "String must have an even number of characters");
bytes memory bstr = new bytes(b.length / 2);
for (uint i = 0; i < b.length / 2; i++) {
uint8 hi = uint8(b[i * 2]);
uint8 lo = uint8(b[i * 2 + 1]);
bstr[i] = bytes1((hi << 4) | lo);
}
return bstr;
}
function hexStringToBytes32(string memory source) internal pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
require(tempEmptyStringTest.length <= 32, "String too long");
assembly {
result := mload(add(source, 32))
}
}

Function Logic

The function executes the following steps:
  1. 1.
    Converts the hexadecimal string parameters (pubkey, withdrawal_credentials, signature, deposit_data_root) into bytes or bytes32 format using helper functions.
  2. 2.
    Stores the converted byte arrays in single-element arrays (pubkeys, withdrawalCredentials, signatures, depositDataRoots) as the NodeOperators staking function expects arrays of these parameters.
  3. 3.
    Calculates the deposit amount (32 ETH for each deposit, a standard in Ethereum 2.0 staking) and checks if the StakeHolder contract holds enough ETH.
  4. 4.
    Calls the stakeHolder.transferETHToContract function to transfer the required amount of ETH from the StakeHolder contract to the current contract for further processing or staking by the NodeOperators service.
The function executes the following steps: