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 interfaceStakingBase
: 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 contractowner_
: 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 tokensmerkleProof
: 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:
- Updates unclaimed rewards for the user
- Moves tokens from staked to unbonding state
- Records unbonding timestamp
- 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:
- Validates unbonding period completion
- Applies withdrawal fees if configured
- Transfers tokens to user
- 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
- Staked: Tokens actively earning rewards at full APY rate
- Unbonding: Tokens not earning rewards, waiting for withdrawal
- 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
- Initial: No tokens staked (
stakersArrayIndex = 0
) - Active Staking: Tokens staked and earning rewards
- Partial Unbonding: Some tokens staked, some unbonding
- Full Unbonding: All tokens unbonding, no rewards accruing
- Withdrawn: Removed from contract (if no unclaimed rewards remain)
Array Management
Efficient staker array management:
stakersArrayIndex = 0
: Not in arraystakersArrayIndex > 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 tokensNoTokensUnbonding()
: No tokens in unbonding stateUnbondingPeriodNotCompleted()
: Attempting withdrawal before period endsRemainingAmountBelowLowerBound()
: Partial unbonding violates tier boundsInvalidUnbondingPeriod()
: Unbonding period exceeds maximum
Standard Staking Errors:
StakingZeroTokens()
: Attempting to stake 0 tokensStakingNotStarted()
: Staking before start dateStakingEnded()
: Staking after end dateStakingAmountExceedsUpperBound()
: Stake exceeds tier upper boundStakingAmountBelowLowerBound()
: Stake below tier lower boundPoolMaxCapExceeded()
: Total pool capacity exceeded
Reward Errors:
NoRewards()
: No rewards available to claimInsufficientRewards()
: Requested amount exceeds availableInsufficientRewardsAvailable()
: 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)