DEV Community

Cover image for How to write dynamic staking smart contract step by step in practice
Mark Santiago
Mark Santiago

Posted on

How to write dynamic staking smart contract step by step in practice

Introduction

In the ever-expanding landscape of decentralized finance (DeFi), staking has emerged as a popular mechanism for users to earn rewards by participating in network security and governance. Unlike traditional financial systems, staking allows cryptocurrency holders to lock up their assets to support blockchain operations, all while earning passive income. However, the conventional staking model often comes with restrictions, such as lock-in periods and fixed reward structures that can limit user flexibility and engagement.

This article provides a detailed, hands-on guide to implementing dynamic staking smart contracts on Ethereum. We'll walk through building a production-ready staking system that enables real-time reward calculations and flexible withdrawals.
This article is the implementation of the Basic understanding of Dynamic Staking.
By following this step-by-step tutorial, you'll learn how to:

  • Create an upgradeable staking contract with role-based access control
  • Implement share-based reward calculations for precise token distribution
  • Build flexible deposit and withdrawal mechanisms without lock-in periods
  • Add essential security features and administrative controls
  • Handle real-time reward tracking and distribution

Whether you're building a DeFi protocol or enhancing an existing platform, this practical guide will equip you with the technical knowledge to implement a robust dynamic staking system. The code examples and implementation patterns shown here are battle-tested and ready for production use, helping you create staking mechanisms that truly serve the needs of modern DeFi users.

Let's dive into the technical implementation and understand how each component works together to create a flexible and secure dynamic staking system.

Implementation Details

Let's break down the key components of the dynamic staking contract:

1. Contract Structure and Inheritance

contract Staking is
    Initializable,
    UUPSUpgradeable,
    AccessControlUpgradeable,
    PausableUpgradeable
{
    using EnumerableSet for EnumerableSet.AddressSet;
Enter fullscreen mode Exit fullscreen mode

The contract inherits from:

  • Initializable: For upgradeable contract initialization
  • UUPSUpgradeable: For upgrade functionality
  • AccessControlUpgradeable: For role-based access control
  • PausableUpgradeable: For emergency pause functionality

2. State Variables and Data Structures

struct Stake {
    uint256 stakedMRKST;
    uint256 shares;
}

IERC20 private MRKST;
EnumerableSet.AddressSet private stakeholders;
uint256 private totalStakes;
uint256 private totalShares;
mapping(address => Stake) private stakeholderToStake;
Enter fullscreen mode Exit fullscreen mode

3. Key Functions

Initialization

function initialize(
    address admin1,
    address admin2,
    address _MRKST
) public initializer {
    AccessControlUpgradeable.__AccessControl_init();
    PausableUpgradeable.__Pausable_init();
    ADMIN_ROLE = keccak256("ADMIN_ROLE");
    _setupRole(ADMIN_ROLE, admin1);
    _setupRole(ADMIN_ROLE, admin2);
    MRKST = IERC20(_MRKST);
    base = 10**18;
}
Enter fullscreen mode Exit fullscreen mode

This function initializes the contracts. It

  • Sets up the initial contract state
  • Assigns two administrators for enhanced security
  • Links the staking token contract
  • Establishes the base unit (10^18) for precise calculations
  • Initializes access control and pause functionality

Staking Implementation

function createStake(uint256 stakeAmount) public whenNotPaused isInitialRatioSet {
    uint256 shares = (stakeAmount * totalShares) / MRKST.balanceOf(address(this));

    require(MRKST.transferFrom(msg.sender, address(this), stakeAmount), "MRKST transfer failed");

    stakeholders.add(msg.sender);
    stakeholderToStake[msg.sender].stakedMRKST += stakeAmount;
    stakeholderToStake[msg.sender].shares += shares;
    totalStakes += stakeAmount;
    totalShares += shares;
}
Enter fullscreen mode Exit fullscreen mode

This function

  • Accepts user deposits
  • Calculates shares based on current token/share ratio
  • Transfers tokens from user to contract
  • Records stake details in storage
  • Updates global totals
  • Adds user to stakeholder list

Withdrawal Implementation

function removeStake(uint256 stakeAmount) public whenNotPaused {
    uint256 stakeholderStake = stakeholderToStake[msg.sender].stakedMRKST;
    uint256 stakeholderShares = stakeholderToStake[msg.sender].shares;

    require(stakeholderStake >= stakeAmount, "Not enough staked!");

    uint256 sharesToWithdraw = (stakeAmount * stakeholderShares) / stakeholderStake;
    uint256 rewards = calculateRewards(stakeAmount, stakeholderStake, stakeholderShares);

    updateStakeAndShares(msg.sender, stakeAmount, sharesToWithdraw);
    transferRewards(msg.sender, stakeAmount, rewards);
}
Enter fullscreen mode Exit fullscreen mode

This function

  • Processes withdrawal requests
  • Calculates earned rewards
  • Determines shares to burn
  • Updates user's stake balance
  • Transfers principal plus rewards
  • Removes user from stakeholder list if fully withdrawn

Reward Calculation

function rewardOf(address stakeholder) public view returns (uint256) {
    uint256 stakeholderStake = stakeholderToStake[stakeholder].stakedMRKST;
    uint256 stakeholderShares = stakeholderToStake[stakeholder].shares;

    if (stakeholderShares == 0) {
      return 0;
    }

    uint256 stakedRatio = (stakeholderStake * base) / stakeholderShares;
    uint256 currentRatio = (MRKST.balanceOf(address(this)) * base) /
      totalShares;

    if (currentRatio <= stakedRatio) {
      return 0;
    }

    uint256 rewards = (stakeholderShares * (currentRatio - stakedRatio)) / base;

    return rewards;
  }
Enter fullscreen mode Exit fullscreen mode

This function

  • Calculates total pending rewards for a stakeholder
  • Uses ratio comparison between initial stake and current value
  • Accounts for all accumulated rewards
  • Returns total claimable reward amount

Reward Distribution

function rewardForStake(address stakeholder, uint256 stakeAmount)
    public
    view
    returns (uint256)
  {
    uint256 stakeholderStake = stakeholderToStake[stakeholder].stakedMRKST;
    uint256 stakeholderShares = stakeholderToStake[stakeholder].shares;

    require(stakeholderStake >= stakeAmount, "Not enough staked!");

    uint256 stakedRatio = (stakeholderStake * base) / stakeholderShares;
    uint256 currentRatio = (MRKST.balanceOf(address(this)) * base) /
      totalShares;
    uint256 sharesToWithdraw = (stakeAmount * stakeholderShares) /
      stakeholderStake;

    if (currentRatio <= stakedRatio) {
      return 0;
    }

    uint256 rewards = (sharesToWithdraw * (currentRatio - stakedRatio)) / base;

    return rewards;
  }
Enter fullscreen mode Exit fullscreen mode

This function

  • Calculates rewards for partial withdrawal
  • Determines proportional share of rewards
  • Validates withdrawal amount
  • Returns specific reward amount for requested withdrawal

Refund Locking Implementation

  function refundLockedStake(uint256 from, uint256 to) public hasAdminRole {
    require(to <= stakeholders.length(), "Invalid `to` param");
    uint256 s;

    for (s = from; s < to; s += 1) {
      totalStakes -= stakeholderToStake[stakeholders.at(s)].stakedMRKST;

      require(
        MRKST.transfer(
          stakeholders.at(s),
          stakeholderToStake[stakeholders.at(s)].stakedMRKST
        ),
        "MRKST transfer failed"
      );

      stakeholderToStake[stakeholders.at(s)].stakedMRKST = 0;
    }
  }
Enter fullscreen mode Exit fullscreen mode

This function

  • Emergency withdrawal mechanism
  • Processes refunds in batches
  • Returns original stake amounts
  • Clears stake records

Conclusion

This dynamic staking implementation provides a flexible and secure way to manage token staking with real-time reward calculations. The share-based system ensures fair distribution of rewards while maintaining precision in calculations.

Future Possibilities

  • Multiple token support
  • Tiered reward systems
  • Governance integration
  • Advanced reward strategies
  • Enhanced analytics and reporting

This implementation serves as a robust foundation for building more sophisticated staking mechanisms in DeFi applications.

For more detailed information, please refer to the source code.

Thank you! Happy coding! 😍

Top comments (12)

Collapse
 
stevendev0822 profile image
Steven

Good implementation.
Thanks for your valuable article.

Collapse
 
btc415 profile image
LovelyBTC

Thanks for sharing the article.

Collapse
 
ichikawa0822 profile image
Ichikawa Hiroshi

Great article.
Highly recommend

Collapse
 
robert_angelo_484 profile image
Robert Angelo

Good article

Collapse
 
dodger213 profile image
Mitsuru Kudo

Thanks for sharing valuable article.
Great

Collapse
 
eugene_garrett_d1a47f08f6 profile image
eugene garrett

Great

Collapse
 
arlo_oscar_d8a2de736e7c73 profile image
Arlo Oscar

Good article.
👍👍

Collapse
 
sebastian_robinson_64 profile image
Sebastian Robinson

Nice article

Collapse
 
jin_william_f348810a6d5e6 profile image
Jin William

Interesting

Collapse
 
jason_smith_6d79eeb6aaba0 profile image
Jason

Great.
Thx