59866 sc high the delegator s rewards in period 1 cannot be claimed

Submitted on Nov 16th 2025 at 14:41:35 UTC by @shaflow1 for Audit Comp | Vechain | Stargate Hayabusaarrow-up-right

  • Report ID: #59866

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/tree/main/packages/contracts/contracts/Stargate.sol

  • Impacts: Permanent freezing of unclaimed yield

Description

Brief / Intro

When a validator is in the Queue rather than Active, completedPeriods will return 0, and CurrentIteration will also return 0. Adding a delegate to a validator in this state should take effect in period 1 (current + 1 = 0 + 1 = 1) and start accruing rewards.

However, the _delegate function in the Stargate contract does not account for this special case and records the delegate as taking effect in period 2 (completedPeriods + 2 = 0 + 2) for reward accrual. This causes the delegator to be unable to claim the delegate rewards for period 1, and the rewards get stuck in the StargateNFT contract.


Vulnerability Details

First, the intended logic in the protocol staker contract (Go code excerpt) for adding a delegation:

https://github.com/vechain/thor/blob/bffc3378d5d8aa346433ae911495bcc3952b265d/builtin/staker/staker.go#L535

func (s *Staker) AddDelegation(...) (*big.Int, error) {
    //...
	// add delegation on the next iteration - val.CurrentIteration() + 1,
	current, err := val.CurrentIteration(currentBlock)
	if err != nil {
		return nil, err
	}
	delegationID, err := s.delegationService.Add(validator, current+1, stake, multiplier)
    //...
}

func (v *Validation) CurrentIteration(currentBlock uint32) (uint32, error) {
	// Unknown, Queued return 0
	if v.Status == StatusUnknown || v.Status == StatusQueued {
		return 0, nil
	}

	// Exited, from active or queued
	if v.Status == StatusExit {
		return v.CompletedPeriods, nil
	}

	// Active(signaled exit)
	// Once signaled exit, complete iterations is set to the current
	// iteration of the time that exit is signaled
	if v.CompletedPeriods > 0 {
		return v.CompletedPeriods, nil
	}

	// Active
	if currentBlock < v.StartBlock {
		return 0, errors.New("curren block cannot be less than start block")
	}
	if v.Period == 0 {
		return 0, errors.New("period cannot be zero")
	}
	elapsedBlocks := currentBlock - v.StartBlock
	completedPeriods := elapsedBlocks / v.Period
	return completedPeriods + 1, nil
}

Under normal circumstances, completedPeriods + 1 = currentPeriods, and a delegation takes effect in the next period, so the effective period is completedPeriods + 2.

However, there is a special case: if the validator is in the Queue state rather than Active, completedPeriods = currentPeriods = 0. In this case, the effective period of the delegation should be completedPeriods + 1.

In the Stargate contract _delegate function, the special case is not considered and the effective period is always recorded as completedPeriods + 2:

https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/blob/e9c0bc9b0f24dc0c44de273181d9a99aaf2c31b0/packages/contracts/contracts/Stargate.sol#L461

Because of this, when delegating to a validator in the QUEUED state, the Stargate contract sets the effective stake starting in period 2, while lastClaimedPeriod[_tokenId] is set to period 1. This mismatch leaves the rewards for period 1 permanently unclaimable.


Impact Details

If a delegation is made to a validator that is in the Queue:

  • _updatePeriodEffectiveStake will set the effective period to period 2,

  • lastClaimedPeriod[_tokenId] is set to period 1.

This results in the user's staking delegation rewards for period 1 being unclaimable. The delegation rewards will become stuck in the Stargate contract (StargateNFT), effectively freezing yield.


Proof of Concept

The following integration test demonstrates the issue by delegating to a queued validator and checking the effective stake periods:

To run the POC, add the above code to packages/contracts/test/integration/Delegation.test.ts and run the integration tests.

Based on test logs (attachments), when delegation is performed while the validator is in the QUEUED state, the rewards begin to take effect from period 2 (EffectiveStake = 2) in the Stargate contract, whereas in the protocolStakerContract the delegation is effective from period 1. This discrepancy causes rewards from period 1 to be unclaimable.


References

  • Stargate.sol (target): https://github.com/immunefi-team/audit-comp-vechain-stargate-hayabusa/tree/main/packages/contracts/contracts/Stargate.sol

  • Protocol staker AddDelegation & CurrentIteration reference: https://github.com/vechain/thor/blob/bffc3378d5d8aa346433ae911495bcc3952b265d/builtin/staker/staker.go#L535

Was this helpful?