Skip to content

Commit

Permalink
281 fix vested staking (#82)
Browse files Browse the repository at this point in the history
* update vested staking

* handle liquid tokens

* refactor stake

* lock commission rewards on a ban
  • Loading branch information
SamBorisov authored Nov 21, 2024
1 parent af3edce commit 33a8277
Show file tree
Hide file tree
Showing 24 changed files with 704 additions and 116 deletions.
6 changes: 6 additions & 0 deletions contracts/HydraChain/modules/Inspector/Inspector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ abstract contract Inspector is IInspector, ValidatorManager {

bansInitiated[validator] = block.timestamp;
hydraStakingContract.temporaryEjectValidator(validator);
hydraDelegationContract.lockCommissionReward(validator);
}

/**
Expand All @@ -70,6 +71,7 @@ abstract contract Inspector is IInspector, ValidatorManager {
bansInitiated[msg.sender] = 0;
_updateParticipation(msg.sender);
hydraStakingContract.recoverEjectedValidator(msg.sender);
hydraDelegationContract.unlockCommissionReward(msg.sender);
}

/**
Expand All @@ -84,6 +86,10 @@ abstract contract Inspector is IInspector, ValidatorManager {
bansInitiated[validator] = 0;
}

if (owner() == msg.sender) {
hydraDelegationContract.lockCommissionReward(validator);
}

_ban(validator);
}

Expand Down
17 changes: 17 additions & 0 deletions contracts/HydraDelegation/Delegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ contract Delegation is
mapping(address => uint256) public commissionUpdateAvailableAt;
/// @notice The commission reward for the staker
mapping(address => uint256) public distributedCommissions;
/// @notice If the commission is locked for the staker
mapping(address => bool) public commissionRewardLocked;
/// @notice Keeps the delegation pools
mapping(address => DelegationPool) public delegationPools;
/// @notice The minimum delegation amount to be delegated
Expand Down Expand Up @@ -88,6 +90,7 @@ contract Delegation is
* @inheritdoc IDelegation
*/
function claimCommission(address to) external {
if (commissionRewardLocked[msg.sender]) revert CommissionRewardLocked();
_claimCommission(msg.sender, to);
}

Expand All @@ -107,6 +110,20 @@ contract Delegation is
_registerWithdrawal(msg.sender, amount);
}

/**
* @inheritdoc IDelegation
*/
function lockCommissionReward(address staker) external onlyHydraChain {
commissionRewardLocked[staker] = true;
}

/**
* @inheritdoc IDelegation
*/
function unlockCommissionReward(address staker) external onlyHydraChain {
commissionRewardLocked[staker] = false;
}

/**
* @inheritdoc IDelegation
*/
Expand Down
15 changes: 15 additions & 0 deletions contracts/HydraDelegation/IDelegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface IDelegation is IWithdrawal {
error InvalidCommission();
error NoCommissionToClaim();
error InvalidMinDelegation();
error CommissionRewardLocked();
error CommissionUpdateNotAvailable();

/**
Expand Down Expand Up @@ -50,6 +51,20 @@ interface IDelegation is IWithdrawal {
*/
function undelegate(address staker, uint256 amount) external;

/**
* @notice Locks the commission for the staker
* @param staker Address of the validator
* @dev Only callable by HydraChain
*/
function lockCommissionReward(address staker) external;

/**
* @notice Unlocks the commission for the staker
* @param staker Address of the validator
* @dev Only callable by HydraChain
*/
function unlockCommissionReward(address staker) external;

/**
* @notice Returns the total delegation amount
*/
Expand Down
25 changes: 20 additions & 5 deletions contracts/HydraStaking/HydraStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {DelegatedStaking} from "./modules/DelegatedStaking/DelegatedStaking.sol"
import {StateSyncStaking} from "./modules/StateSyncStaking/StateSyncStaking.sol";
import {PenalizeableStaking} from "./modules/PenalizeableStaking/PenalizeableStaking.sol";
import {IHydraStaking, StakerInit} from "./IHydraStaking.sol";
import {Staking} from "./Staking.sol";
import {Staking, IStaking} from "./Staking.sol";

// TODO: An optimization we can do is keeping only once the general apr params for a block so we don' have to keep them for every single user

Expand Down Expand Up @@ -68,6 +68,14 @@ contract HydraStaking is

// _______________ External functions _______________

/**
* @notice Stakes the sent amount.
* @dev Reverts if we have an active position for the staker.
*/
function stake() public payable override(IStaking, Staking, VestedStaking) {
super.stake();
}

/**
* @inheritdoc IHydraStaking
*/
Expand Down Expand Up @@ -226,6 +234,13 @@ contract HydraStaking is
function _distributeTokens(address staker, uint256 amount) internal virtual override {
VestingPosition memory position = vestedStakingPositions[staker];
if (_isOpeningPosition(position)) {
uint256 currentStake = stakeOf(staker);
if (currentStake != amount) {
currentStake -= amount;
_collectTokens(staker, currentStake);
amount += currentStake;
}

uint256 debt = _calculatePositionDebt(amount, position.duration);
liquidityDebts[staker] -= debt.toInt256Safe(); // Add negative debt
amount -= debt;
Expand Down Expand Up @@ -270,13 +285,13 @@ contract HydraStaking is
revert DistributeRewardFailed("SIGNED_BLOCKS_EXCEEDS_TOTAL");
}

uint256 stake = stakeOf(uptime.validator);
uint256 currentStake = stakeOf(uptime.validator);
uint256 delegation = _getStakerDelegatedBalance(uptime.validator);
// slither-disable-next-line divide-before-multiply
uint256 stakerRewardIndex = (fullRewardIndex * (stake + delegation) * uptime.signedBlocks) /
uint256 stakerRewardIndex = (fullRewardIndex * (currentStake + delegation) * uptime.signedBlocks) /
(totalSupply * totalBlocks);
(uint256 stakerShares, uint256 delegatorShares) = _calculateStakerAndDelegatorShares(
stake,
currentStake,
delegation,
stakerRewardIndex
);
Expand All @@ -285,7 +300,7 @@ contract HydraStaking is
_distributeDelegationRewards(uptime.validator, delegatorShares, epochId);

// Keep history record of the staker rewards to be used on maturing vesting reward claim
if (stakerShares > 0) {
if (stakerShares != 0) {
_saveStakerRewardData(uptime.validator, epochId);
}

Expand Down
8 changes: 3 additions & 5 deletions contracts/HydraStaking/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,10 @@ contract Staking is IStaking, Governed, Withdrawal, HydraChainConnector, APRCalc
// _______________ Modifiers _______________

/**
* @notice Modifier that checks if the staker meets the current min-stake.
* @dev This state is determined by the minimum stake required. If the min stake is increased,
* The staker needs to meet the new min stake requirement so he can continue having new delegators or top up delegations.
* @notice Modifier that checks if the staker has stake.
*/
modifier onlyActiveStaker(address staker) {
if (stakeOf(staker) < minStake) {
if (stakeOf(staker) == 0) {
revert Unauthorized("INACTIVE_STAKER");
}

Expand All @@ -63,7 +61,7 @@ contract Staking is IStaking, Governed, Withdrawal, HydraChainConnector, APRCalc
/**
* @inheritdoc IStaking
*/
function stake() external payable {
function stake() public payable virtual {
_stake(msg.sender, msg.value);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ interface IVestedStaking is IVesting, IStaking {
/**
* @notice Stakes sent amount with vesting period.
* @param durationWeeks Duration of the vesting in weeks. Must be between 1 and 52.
* @dev The staker also claims any rewards before opening a new position, to avoid locking them during vesting cycle
* @dev If staker has stake already, the whole stake will be in the position
*/
function stakeWithVesting(uint256 durationWeeks) external payable;

Expand Down
18 changes: 17 additions & 1 deletion contracts/HydraStaking/modules/VestedStaking/VestedStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.17;
import {VestedPositionLib} from "../../../common/Vesting/VestedPositionLib.sol";
import {VestingPosition} from "../../../common/Vesting/IVesting.sol";
import {Vesting} from "../../../common/Vesting/Vesting.sol";
import {Staking} from "../../Staking.sol";
import {Staking, IStaking} from "../../Staking.sol";
import {IVestedStaking, StakingRewardsHistory} from "./IVestedStaking.sol";

/**
Expand Down Expand Up @@ -42,6 +42,9 @@ abstract contract VestedStaking is IVestedStaking, Vesting, Staking {
// Claim the rewards before opening a new position, to avoid locking them during vesting cycle
if (unclaimedRewards(msg.sender) != 0) _claimStakingRewards(msg.sender);

// Clear the staking rewards history
delete stakingRewardsHistory[msg.sender];

uint256 duration = durationWeeks * 1 weeks;
vestedStakingPositions[msg.sender] = VestingPosition({
duration: duration,
Expand All @@ -56,6 +59,19 @@ abstract contract VestedStaking is IVestedStaking, Vesting, Staking {
_stake(msg.sender, msg.value);
}

/**
* @notice Stake the amount given by the sender
* @dev Overrides the stake function in Staking contract
* @dev If the staker has an active position, the stake will be rejected
*/
function stake() public payable virtual override(Staking, IStaking) {
if (vestedStakingPositions[msg.sender].isActive()) {
revert StakeRequirement({src: "stake", msg: "IN_ACTIVE_POSITION"});
}

super.stake();
}

/**
* @inheritdoc IVestedStaking
*/
Expand Down
81 changes: 81 additions & 0 deletions docs/HydraDelegation/Delegation.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,28 @@ Claims rewards for delegator and commissions for staker
|---|---|---|
| staker | address | Address of the validator |

### commissionRewardLocked

```solidity
function commissionRewardLocked(address) external view returns (bool)
```

If the commission is locked for the staker



#### Parameters

| Name | Type | Description |
|---|---|---|
| _0 | address | undefined |

#### Returns

| Name | Type | Description |
|---|---|---|
| _0 | bool | undefined |

### commissionUpdateAvailableAt

```solidity
Expand Down Expand Up @@ -413,6 +435,22 @@ function hydraStakingContract() external view returns (contract IHydraStaking)
|---|---|---|
| _0 | contract IHydraStaking | undefined |

### lockCommissionReward

```solidity
function lockCommissionReward(address staker) external nonpayable
```

Locks the commission for the staker

*Only callable by HydraChain*

#### Parameters

| Name | Type | Description |
|---|---|---|
| staker | address | Address of the validator |

### minDelegation

```solidity
Expand Down Expand Up @@ -619,6 +657,22 @@ Undelegates amount from staker for sender, claims rewards and validator comissio
| staker | address | Validator to undelegate from |
| amount | uint256 | The amount to undelegate |

### unlockCommissionReward

```solidity
function unlockCommissionReward(address staker) external nonpayable
```

Unlocks the commission for the staker

*Only callable by HydraChain*

#### Parameters

| Name | Type | Description |
|---|---|---|
| staker | address | Address of the validator |

### withdraw

```solidity
Expand Down Expand Up @@ -911,6 +965,17 @@ event WithdrawalRegistered(address indexed account, uint256 amount)

## Errors

### CommissionRewardLocked

```solidity
error CommissionRewardLocked()
```






### CommissionUpdateNotAvailable

```solidity
Expand Down Expand Up @@ -994,6 +1059,22 @@ error NoWithdrawalAvailable()



### Unauthorized

```solidity
error Unauthorized(string only)
```





#### Parameters

| Name | Type | Description |
|---|---|---|
| only | string | undefined |

### WithdrawalFailed

```solidity
Expand Down
Loading

0 comments on commit 33a8277

Please sign in to comment.