Skip to main content

StakingWithUnbonding

Overview

The StakingWithUnbonding contract implements a staking mechanism with a two-phase withdrawal process. Users must initiate unbonding and wait for a specified period before completing withdrawals. The contract uses a fixed APY reward system with maximum stake duration caps.

Contract Specification

contract StakingWithUnbonding is IStakingWithUnbonding, StakingBase

Inheritance:

  • IStakingWithUnbonding: Unbonding staking interface
  • StakingBase: Base functionality for staking contracts

Data Structures

UnbondingStaker

struct UnbondingStaker {
uint256 amountStaked; // Total tokens currently staked
uint256 unclaimedRewards; // Accumulated unclaimed rewards
uint128 timeOfFirstStake; // Timestamp of user's first stake
uint128 timeOfLastUpdate; // Last reward update timestamp
uint128 unbondingTimestamp; // When unbonding was initiated
uint256 unbondingAmount; // Amount currently unbonding
uint64 stakersArrayIndex; // Index in stakers array (0 = not in array)
}

UnbondingStakingInitParams

struct UnbondingStakingInitParams {
bytes32 merkleRoot; // Merkle root for whitelist verification
ITypes.WhitelistStatus whitelistStatus; // Whitelist configuration mode
address stakingToken; // ERC20 token contract address
uint256 poolMaxCap; // Maximum total tokens stakeable
uint256 tierUpperBound; // Maximum tokens per individual staker
uint256 tierLowerBound; // Minimum tokens per individual staker
uint64 unbondingPeriod; // Unbonding period duration in seconds
uint64 maxStakeDuration; // Maximum duration for reward accrual
uint64 apyPercentage; // Annual percentage yield in basis points
uint80 startStakingDate; // Staking operations start timestamp
uint80 endStakingDate; // Staking operations end timestamp
ITypes.FeeType feeType; // Type of fees applied
uint256 fee; // Fee amount (wei for gas, basis points for token)
address feeCollector; // Address to receive fees
uint8 feeFunctions; // Bitmask for functions with fees
}

Constructor

constructor(UnbondingStakingInitParams memory params, address owner_) StakingBase(BaseParams(...))

Parameters:

  • params: Initialization parameters for the unbonding staking contract
  • owner_: Address to be set as contract owner

State Variables

Immutable Parameters

uint64 public immutable APY_PERCENTAGE;        // Annual percentage yield in basis points
uint64 public immutable MAX_STAKE_DURATION; // Maximum duration for earning rewards (seconds)
uint64 public immutable MAX_UNBONDING_PERIOD; // Maximum allowed unbonding period (5 years)

Mutable State

mapping(address => UnbondingStaker) public stakers;  // Staker information by address
uint64 public unbondingPeriod; // Current unbonding period (owner-configurable)

Core Functions

Staking Operations

stake

function stake(uint256 _amount) external payable nonReentrant

Stakes tokens to the caller's address.

Parameters:

  • _amount: Amount of tokens to stake

Requirements:

  • Whitelist must be disabled
  • Standard staking requirements apply

Events: TokensStaked(address indexed staker, uint256 amount, uint256 feeAmount)

function stake(uint256 _amount, address _receiver) external payable nonReentrant

Stakes tokens to a specified receiver address.

Parameters:

  • _amount: Amount of tokens to stake
  • _receiver: Address to receive the staked tokens

Requirements:

  • _receiver cannot be zero address
  • If Merkle whitelist enabled, must use merkle proof variant
  • If trusted distributors required, caller must be trusted
function stake(uint256 _amount, address _receiver, bytes32[] calldata merkleProof) external payable nonReentrant

Stakes tokens with Merkle proof verification.

Parameters:

  • _amount: Amount of tokens to stake
  • _receiver: Address to receive the staked tokens
  • merkleProof: Merkle proof for receiver address verification

Requirements:

  • Valid Merkle proof for receiver address
  • All basic staking requirements

Unbonding Operations

initiateUnbonding

function initiateUnbonding(uint256 _amount) external nonReentrant

Initiates the unbonding process for a specified amount of staked tokens.

Parameters:

  • _amount: Amount of tokens to start unbonding

Requirements:

  • Amount must be > 0
  • Amount ≤ currently staked amount
  • If partial unbonding, remaining amount ≥ tier lower bound

State Changes:

  1. Updates unclaimed rewards for the user
  2. Moves tokens from staked to unbonding state
  3. Records unbonding timestamp
  4. Tokens stop earning rewards while unbonding

Events: UnbondingInitiated(address indexed staker, uint256 amount)

completeUnbonding

function completeUnbonding() external payable nonReentrant

Completes the unbonding process and withdraws unbonding tokens.

Requirements:

  • Must have tokens in unbonding state
  • Unbonding period must have elapsed (unless unbondingPeriod == 0)
  • Gas fee required if configured for withdrawals

State Changes:

  1. Validates unbonding period completion
  2. Applies withdrawal fees if configured
  3. Transfers tokens to user
  4. Cleans up staker data if fully withdrawn and no unclaimed rewards

Events: TokensWithdrawn(address indexed staker, uint256 amount, uint256 feeAmount)

Reward Operations

claimRewards

function claimRewards() external payable nonReentrant

Claims all available rewards for the caller.

Events: RewardsClaimed(address indexed staker, uint256 rewardAmount, uint256 feeAmount)

function claimRewards(uint256 _amount) external payable nonReentrant

Claims a specific amount of rewards.

Parameters:

  • _amount: Amount of rewards to claim

Requirements:

  • Amount ≤ available rewards
  • Contract must have sufficient reward tokens

Configuration Functions

Unbonding Period Management

setUnbondingPeriod

function setUnbondingPeriod(uint64 _newUnbondingPeriod) external onlyOwner

Updates the unbonding period for new unbonding requests.

Parameters:

  • _newUnbondingPeriod: New unbonding period in seconds

Requirements:

  • Only contract owner
  • Must be ≤ MAX_UNBONDING_PERIOD (5 years)

Note: Only affects new unbonding requests. Existing unbonding tokens use the period active when unbonding was initiated.

Events: UnbondingPeriodUpdated(uint64 oldUnbondingPeriod, uint64 newUnbondingPeriod)

View Functions

Staking Information

getStakeInfo

function getStakeInfo(address _staker) external view returns (
uint256 _tokensStaked,
uint256 _rewards,
uint256 _unbondingAmount,
uint256 _timeUntilUnbondingComplete,
uint128 _firstStakeTime
)

Returns comprehensive staking information for a specific address.

Parameters:

  • _staker: Address to query

Returns:

  • _tokensStaked: Amount of tokens currently staked and earning rewards
  • _rewards: Total available rewards (claimed + unclaimed)
  • _unbondingAmount: Amount of tokens currently in unbonding state
  • _timeUntilUnbondingComplete: Seconds remaining until unbonding completion (0 if ready)
  • _firstStakeTime: Timestamp when user first staked (used for reward duration cap)

getAPY

function getAPY() external view returns (uint64)

Returns the annual percentage yield in basis points.

stakingSettings

function stakingSettings() external view returns (StakingSettings memory)

Returns comprehensive staking contract configuration for UI/integration purposes.

isInStakerList

function isInStakerList(address _staker) external view returns (bool)

Checks if an address is in the stakers list.

getAddressIndex

function getAddressIndex(address _staker) external view returns (uint64)

Returns the index of a staker in the stakersArray.

Reward Calculation System

APY-Based Formula

The contract uses a fixed APY calculation instead of flexible time units:

reward = (stakedAmount × timeStaked × APY_PERCENTAGE) / (365 days × BASIS_POINTS)

Key Characteristics:

  • APY is immutable per contract deployment
  • Rewards accrue from staking timestamp to current time
  • Rewards are capped at maxStakeDuration from first stake time
  • No rewards accrue on tokens in unbonding state

Maximum Stake Duration

Rewards stop accruing after the maximum stake duration to prevent infinite accumulation:

maxEndTime = timeOfFirstStake + MAX_STAKE_DURATION
if (currentTime > maxEndTime) {
currentTime = maxEndTime; // Cap for reward calculation
}

Reward Accrual States

  1. Staked: Tokens actively earning rewards at full APY rate
  2. Unbonding: Tokens not earning rewards, waiting for withdrawal
  3. Withdrawn: Tokens removed from contract, no longer tracked

Unbonding Mechanism

Two-Phase Withdrawal Process

Phase 1: Initiate Unbonding

  • User calls initiateUnbonding(amount)
  • Tokens move from staked to unbonding state
  • Unbonding timestamp recorded
  • Rewards stop accruing on unbonded tokens

Phase 2: Complete Unbonding

  • User calls completeUnbonding() after waiting period
  • Validates unbonding period has elapsed
  • Applies withdrawal fees if configured
  • Transfers tokens to user

Unbonding Period Configuration

Immediate Withdrawals:

  • unbondingPeriod = 0: No waiting period required
  • Functions like basic staking for withdrawals

Delayed Withdrawals:

  • unbondingPeriod > 0: Must wait specified time
  • Maximum: MAX_UNBONDING_PERIOD (5 years)
  • Provides protocol stability and prevents sudden exits

Partial Unbonding

Users can initiate partial unbonding:

  • Must leave at least tierLowerBound tokens staked, OR
  • Unbond entire staked amount (full exit)
  • Each partial unbonding overwrites previous unbonding timestamp

State Management

Staker Lifecycle

  1. Initial: No tokens staked (stakersArrayIndex = 0)
  2. Active Staking: Tokens staked and earning rewards
  3. Partial Unbonding: Some tokens staked, some unbonding
  4. Full Unbonding: All tokens unbonding, no rewards accruing
  5. Withdrawn: Removed from contract (if no unclaimed rewards remain)

Array Management

Efficient staker array management:

  • stakersArrayIndex = 0: Not in array
  • stakersArrayIndex > 0: Array index + 1 (to distinguish from 0)
  • Automatic cleanup when fully withdrawn with no unclaimed rewards

Error Conditions

Unbonding-Specific Errors:

  • ZeroAmount(): Attempting to unbond 0 tokens
  • NoTokensUnbonding(): No tokens in unbonding state
  • UnbondingPeriodNotCompleted(): Attempting withdrawal before period ends
  • RemainingAmountBelowLowerBound(): Partial unbonding violates tier bounds
  • InvalidUnbondingPeriod(): Unbonding period exceeds maximum

Standard Staking Errors:

  • StakingZeroTokens(): Attempting to stake 0 tokens
  • StakingNotStarted(): Staking before start date
  • StakingEnded(): Staking after end date
  • StakingAmountExceedsUpperBound(): Stake exceeds tier upper bound
  • StakingAmountBelowLowerBound(): Stake below tier lower bound
  • PoolMaxCapExceeded(): Total pool capacity exceeded

Reward Errors:

  • NoRewards(): No rewards available to claim
  • InsufficientRewards(): Requested amount exceeds available
  • InsufficientRewardsAvailable(): Contract has insufficient reward tokens

Constants

uint64 public constant MAX_UNBONDING_PERIOD = 365 days * 5; // 5 years maximum
uint256 public constant BASIS_POINTS = 10_000; // 100% = 10,000 basis points

Inherited Functionality

The contract inherits extensive functionality from StakingBase:

  • Fee management (token and gas fees)
  • Whitelist control (Merkle trees and trusted distributors)
  • Pool management (capacity, tier bounds)
  • Token operations (funding, excess withdrawal)
  • Access control (owner, fee collector roles)
  • Security features (reentrancy protection, input validation)