DEV Community

Cover image for Understanding the Staking Contract
Sumana
Sumana

Posted on • Edited on

Understanding the Staking Contract

Introduction

Staking is a popular concept in the DeFi world that allows users to lock up their tokens in a smart contract to earn rewards. In this blog, we’ll dive into the mechanics of a basic staking contract written in Solidity, exploring how it functions and why it is beneficial for both developers and users. We’ll also touch on some of the technical details to give you a clearer picture of how staking contracts are implemented.

The Staking Contract

Here’s a look at the core staking contract implemented in Solidity:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

import "./Openzeppelin/IERC20.sol";
import "./Openzeppelin/ReentrancyGuard.sol";

error TransferFailed();
error NeedsMoreThanZero();

contract Staking is ReentrancyGuard {
    IERC20 public s_stakingToken;
    IERC20 public s_rewardsToken;

    // Reward rate per second
    uint256 public constant REWARD_RATE = 100;
    uint256 public s_lastUpdateTime;
    uint256 public s_rewardPerTokenStored;

    mapping(address => uint256) public s_userRewardPerTokenPaid; //depends on pool changes
    mapping(address => uint256) public s_rewards; //stores the pending rewards for each user.

    uint256 private s_totalSupply;
    mapping(address => uint256) public s_balances; //tracks the amount of tokens each user has staked.

    event Staked(address indexed user, uint256 indexed amount);
    event Withdrawn(address indexed user, uint256 indexed amount);
    event RewardsClaimed(address indexed user, uint256 indexed amount);

    constructor(address stakingToken, address rewardsToken) {
        s_stakingToken = IERC20(stakingToken);
        s_rewardsToken = IERC20(rewardsToken);
    }

    function rewardPerToken() public view returns (uint256) {
        if (s_totalSupply == 0) {
            return s_rewardPerTokenStored;
        }
        return s_rewardPerTokenStored + (((block.timestamp - s_lastUpdateTime) * REWARD_RATE * 1e18) / s_totalSupply);
    }

    function earned(address account) public view returns (uint256) {
        return ((s_balances[account] * (rewardPerToken() - s_userRewardPerTokenPaid[account])) / 1e18) + s_rewards[account];
    }

    function stake(uint256 amount) external updateReward(msg.sender) nonReentrant moreThanZero(amount) {
        s_totalSupply += amount;
        s_balances[msg.sender] += amount;
        emit Staked(msg.sender, amount);
        bool success = s_stakingToken.transferFrom(msg.sender, address(this), amount);
        if (!success) {
            revert TransferFailed();
        }
    }

    function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
        s_totalSupply -= amount;
        s_balances[msg.sender] -= amount;
        emit Withdrawn(msg.sender, amount);
        bool success = s_stakingToken.transfer(msg.sender, amount);
        if (!success) {
            revert TransferFailed();
        }
    }

    function claimReward() external nonReentrant updateReward(msg.sender) {
        uint256 reward = s_rewards[msg.sender];
        s_rewards[msg.sender] = 0;
        emit RewardsClaimed(msg.sender, reward);
        bool success = s_rewardsToken.transfer(msg.sender, reward);
        if (!success) {
            revert TransferFailed();
        }
    }

    modifier updateReward(address account) {
        s_rewardPerTokenStored = rewardPerToken();
        s_lastUpdateTime = block.timestamp;
        s_rewards[account] = earned(account);
        s_userRewardPerTokenPaid[account] = s_rewardPerTokenStored;
        _;
    }

    modifier moreThanZero(uint256 amount) {
        if (amount == 0) {
            revert NeedsMoreThanZero();
        }
        _;
    }

    function getStaked(address account) public view returns (uint256) {
        return s_balances[account];
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Features

1. Reward Calculation

The rewardPerToken function calculates the reward earned per token based on the time elapsed and the total supply of staked tokens. This ensures that rewards are proportional to the time tokens have been staked.

2. Staking Tokens

Users can stake tokens using the stake function. This function updates the total supply and user balances, and transfers tokens from the user to the contract. It also emits a Staked event to log the action.

3. Withdrawing Tokens

The withdraw function allows users to withdraw their staked tokens. It updates the total supply and user balances, then transfers tokens back to the user. An Withdrawn event is emitted to record the action.

4. Claiming Rewards

Users can claim their accumulated rewards using the claimReward function. This function transfers the rewards from the contract to the user and emits a RewardsClaimed event.

Why Stake?

Staking offers several benefits:

  • Earn Passive Income: Users earn rewards for locking up their tokens, which can be a source of passive income.
  • Support the Network: In some DeFi projects, staking helps support the network or protocol, contributing to its stability and security.
  • Participate in Governance: Staking can also provide governance rights, allowing users to participate in decision-making processes.

Technical Insights

  1. Reentrancy Guard: The contract inherits from ReentrancyGuard to prevent reentrancy attacks, ensuring that functions that transfer tokens cannot be called recursively.

  2. Error Handling: Custom errors (TransferFailed and NeedsMoreThanZero) are used for better gas efficiency and error handling compared to traditional require statements.

  3. Modifiers: The updateReward modifier updates reward calculations before executing functions like stake, withdraw, and claimReward, ensuring that rewards are always accurate.

Conclusion

This staking contract provides a straightforward yet effective way for users to stake their tokens and earn rewards. By understanding the contract’s components and functionality, you can appreciate the role of staking in the DeFi ecosystem and how smart contracts facilitate this process.

Explore the Code

If you’re interested in exploring the code or contributing to its development, check out the GitHub repository where the full contract and additional details are available.

Feel free to experiment with and extend this contract to fit more complex use cases or integrate it into your DeFi projects. Happy coding🚀!

Top comments (1)

Collapse
 
hadjinu profile image
hadi

Hello,
I hope you're doing well. I am seeking guidance on how to unstake my tokens from a staking contract, as the staking platform is currently offline and the developer appears to have abandoned the project. Unfortunately, there is no active community support, and I have not received any responses to my inquiries.

The contract is located on Arbiscan, but it is not verified and lacks an ABI. Furthermore, it is a proxy contract, which complicates direct interaction with it. There are no "read" or "write" functions available, only bytecode is accessible.

Given these circumstances, I would like to know if there is any way to manually interact with or unstake my tokens directly from the contract. Any advice or insights you can provide would be greatly appreciated.

Thank you for your time and assistance.

Best regards,
Hadi