From 35f397cdeac08a413e031175ac02c83d05b54dee Mon Sep 17 00:00:00 2001 From: DrZoltanFazekas Date: Sun, 29 Jun 2025 13:41:32 +0200 Subject: [PATCH] Allow splitting the stake between validator nodes --- README.md | 11 ++++++++++ src/BaseDelegation.sol | 44 +++++++++++++++++++++++++++++++------ src/LiquidDelegation.sol | 22 ++++++++++++++++++- src/NonLiquidDelegation.sol | 23 ++++++++++++++++++- 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index da0a12d..ee46bdc 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,16 @@ cast send --legacy --value 5000000ether --private-key $PRIVATE_KEY \ 0xb14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a ``` +If you want to deposit only a certain amount from the delegation contract's available stake, run the following command with the amount (e.g. 10 million ZIL) specified as the last argument: +```bash +cast send --legacy --value 5000000ether --private-key $PRIVATE_KEY \ +0x7a0b7e6d24ede78260c9ddbd98e828b0e11a8ea2 "depositFromPool(bytes,bytes,bytes,uint256)" \ +0x92fbe50544dce63cfdcc88301d7412f0edea024c91ae5d6a04c7cd3819edfc1b9d75d9121080af12e00f054d221f876c \ +0x002408011220d5ed74b09dcbe84d3b32a56c01ab721cf82809848b6604535212a219d35c412f \ +0xb14832a866a49ddf8a3104f8ee379d29c136f29aeb8fccec9d7fb17180b99e8ed29bee2ada5ce390cb704bc6fd7f5ce814f914498376c4b8bc14841a57ae22279769ec8614e2673ba7f36edc5a4bf5733aa9d70af626279ee2b2cde939b4bd8a \ +10000000000000000000000000 +``` + Note that the reward address registered for the validator node will be the address of the delegation contract (the proxy contract to be more precise), but the deposit will not take effect and the node will not start to earn rewards until the epoch after next. @@ -512,6 +522,7 @@ If the execution reverts, you can look up the error based on the first 4 bytes o 0xa8ca83c9 StakerAlreadyExists(address) 0x30e667d2 IncompatibleVersion(uint64) 0x8579befe ZeroAddressNotAllowed() +0xa0753f46 InsufficientAvailableStake(uint256,uint256) ``` diff --git a/src/BaseDelegation.sol b/src/BaseDelegation.sol index 4ece2a0..e6cb542 100644 --- a/src/BaseDelegation.sol +++ b/src/BaseDelegation.sol @@ -237,6 +237,11 @@ abstract contract BaseDelegation is IDelegation, PausableUpgradeable, Ownable2St */ error ZeroAddressNotAllowed(); + /** + * @dev Thrown if the `amount` to be deposited exceeds the `availableStake`. + */ + error InsufficientAvailableStake(uint256 amount, uint256 availableStake); + // ************************************************************************ // // VERSION @@ -244,7 +249,7 @@ abstract contract BaseDelegation is IDelegation, PausableUpgradeable, Ownable2St // ************************************************************************ /// @dev The current version of all upgradeable contracts in the repository. - uint64 internal immutable VERSION = encodeVersion(1, 1, 1); + uint64 internal immutable VERSION = encodeVersion(1, 1, 2); /** * @dev Return the contracts' version. @@ -428,6 +433,20 @@ abstract contract BaseDelegation is IDelegation, PausableUpgradeable, Ownable2St bytes calldata signature ) public virtual payable; + /** + * @dev Turn a fully synced node into a validator using `amount` from the stake + * in the pool's balance. It must be called by the contract owner. The staking + * pool must have `amount` in its balance including the amount transferred by + * the contract owner in the current transaction and `amount` must be at least + * the minimum stake required of validators. + */ + function depositFromPool( + bytes calldata blsPubKey, + bytes calldata peerId, + bytes calldata signature, + uint256 amount + ) public virtual payable; + /** * @dev Add the validator identified by `blsPubKey` to the staking pool. It * can be called by the contract owner if the `controlAddress` has called the @@ -469,28 +488,39 @@ abstract contract BaseDelegation is IDelegation, PausableUpgradeable, Ownable2St /** * @dev Append an entry to the staking pool's list of validators. Use the pool * contract's owner address as reward address and control address in the entry. - * Register a validator by transferring the stake available in the contract - * balance to `DEPOSIT_CONTRACT`. + * Register a validator by transferring `amount` or the entire stake available + * in the contract balance if `amount` is zero to the `DEPOSIT_CONTRACT`. * * Emit {ValidatorJoined} containing the `blsPubKey` of the validator. * + * Revert with {TooManyValidators} containing the `blsPubKey` of the validator + * if the pool has already reached the maximum number of validators. + * + * Revert with {InsufficientAvailableStake} containing the `amount` of stake + * to be deposited and the maximum `availableStake` that can be deposited. + * * Revert with {DepositContractCallFailed} containing the call data and the * error data returned if the call to the `DEPOSIT_CONTRACT` fails. */ function _depositAndAddToPool( bytes calldata blsPubKey, bytes calldata peerId, - bytes calldata signature + bytes calldata signature, + uint256 amount ) internal virtual { BaseDelegationStorage storage $ = _getBaseDelegationStorage(); if (!$.activated) $.activated = true; uint256 availableStake = $.nonRewards - $.withdrawnDepositedClaims - $.nonDepositedClaims; + if (amount == 0) + amount = availableStake; + else + require(amount <= availableStake, InsufficientAvailableStake(amount, availableStake)); require($.validators.length < MAX_VALIDATORS, TooManyValidators(blsPubKey)); $.validators.push(Validator( blsPubKey, - availableStake, + amount, owner(), owner(), 0, @@ -508,11 +538,11 @@ abstract contract BaseDelegation is IDelegation, PausableUpgradeable, Ownable2St owner() ); (bool success, bytes memory data) = DEPOSIT_CONTRACT.call{ - value: availableStake + value: amount }(callData); require(success, DepositContractCallFailed(callData, data)); - $.nonRewards = $.withdrawnDepositedClaims + $.nonDepositedClaims; + $.nonRewards -= amount; emit ValidatorJoined(blsPubKey); } diff --git a/src/LiquidDelegation.sol b/src/LiquidDelegation.sol index bf88124..90f1c8a 100644 --- a/src/LiquidDelegation.sol +++ b/src/LiquidDelegation.sol @@ -106,7 +106,27 @@ contract LiquidDelegation is IDelegation, BaseDelegation { _depositAndAddToPool( blsPubKey, peerId, - signature + signature, + 0 + ); + } + + /// @inheritdoc BaseDelegation + function depositFromPool( + bytes calldata blsPubKey, + bytes calldata peerId, + bytes calldata signature, + uint256 amount + ) public override payable onlyOwner { + if (msg.value > 0) + _stake(msg.value, _msgSender()); + // the total stake must not be increased before the price is determined + _increaseStake(msg.value); + _depositAndAddToPool( + blsPubKey, + peerId, + signature, + amount ); } diff --git a/src/NonLiquidDelegation.sol b/src/NonLiquidDelegation.sol index 3d714d3..eb34c13 100644 --- a/src/NonLiquidDelegation.sol +++ b/src/NonLiquidDelegation.sol @@ -146,7 +146,28 @@ contract NonLiquidDelegation is IDelegation, BaseDelegation { _depositAndAddToPool( blsPubKey, peerId, - signature + signature, + 0 + ); + // the owner's deposit must also be recorded as staking otherwise + // the owner would not benefit from the rewards accrued by the deposit + if (msg.value > 0) + _appendToHistory(int256(msg.value), _msgSender()); + } + + /// @inheritdoc BaseDelegation + function depositFromPool( + bytes calldata blsPubKey, + bytes calldata peerId, + bytes calldata signature, + uint256 amount + ) public payable override onlyOwner { + _increaseStake(msg.value); + _depositAndAddToPool( + blsPubKey, + peerId, + signature, + amount ); // the owner's deposit must also be recorded as staking otherwise // the owner would not benefit from the rewards accrued by the deposit