Skip to content

Instantly share code, notes, and snippets.

@Signor1
Last active January 27, 2024 19:09
Show Gist options
  • Select an option

  • Save Signor1/b1c93afa5cb5a02bb0ded6070d98387c to your computer and use it in GitHub Desktop.

Select an option

Save Signor1/b1c93afa5cb5a02bb0ded6070d98387c to your computer and use it in GitHub Desktop.
Solidity Staking Contract
#Explanations
First of all, to create a staking contract there is need to make use of a token for the staking. Creating an ERC20 token is paramount. Then after that, we talk about the components of a staking contract. For a stake to happen, there would be a staker, a range of amount to be staked, the duration of the staking, the reward which will be calculated by the duration and the amount staked, then after a certain duration of staking, the staker can withdraw.
In this solidity staking contract, we have the Solidity version that is ^0.8.20 which would instruct the compiler to use a certain version for compilations.
The Structure of the Contract
1. Contract Definition and Imports
The contract is defined with a name - StakingRewards (Staring it a capital letter is the best practise)
It includes two state variables - stakingToken and rewardsToken, which are instances of the IERC20 interface.
owner is a state variable to store the address of the contract owner.
2. The constructor
The constructor initializes the contract with the staking token and rewards token addresses.
The owner is set to the address that deploys the contract (msg.sender).
3. Modifiers
The onlyOwner modifier ensures that only the contract owner can execute certain functions.
The updateReward modifier updates reward-related variables before executing a function.
4. Helper Functions
These functions (lastTimeRewardApplicable, rewardPerToken, earned) calculate the last time rewards were applicable, the reward per token, and the earned rewards for a specific account.
The _min function returns the minimum of two values.
5. Staking Functions
The stake function allows users to stake tokens, updating the reward information.
The withdraw function allows users to withdraw their staked tokens, updating the reward information.
6. Reward Claiming Function
The getReward function allows users to claim their earned rewards, updating the reward information.
7. Owner-Only Functions
The setRewardsDuration function allows the owner to set the duration of the rewards.
The notifyRewardAmount function allows the owner to notify the contract about the amount of rewards to be distributed.
The Flow
In the contract, we have the state variables as serves as the storage that would be updated. We have the staking token and reward token which are unchangeable. We have the owner and other variables of unsigned integer data type.
Then we map getting the userRewardPerTokenPaid, the rewards and balance.
The constructor initializes the contract and it recieves the staking token and the reward token as the parameters; and then sets the owner to the address of the contract deployer.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract StakingRewards {
IERC20 public immutable stakingToken;
IERC20 public immutable rewardsToken;
address public owner;
// Duration of rewards to be paid out (in seconds)
uint public duration;
// Timestamp of when the rewards finish
uint public finishAt;
// Minimum of last updated time and reward finish time
uint public updatedAt;
// Reward to be paid out per second
uint public rewardRate;
// Sum of (reward rate * dt * 1e18 / total supply)
uint public rewardPerTokenStored;
// User address => rewardPerTokenStored
mapping(address => uint) public userRewardPerTokenPaid;
// User address => rewards to be claimed
mapping(address => uint) public rewards;
// Total staked
uint public totalSupply;
// User address => staked amount
mapping(address => uint) public balanceOf;
constructor(address _stakingToken, address _rewardToken) {
owner = msg.sender;
stakingToken = IERC20(_stakingToken);
rewardsToken = IERC20(_rewardToken);
}
modifier onlyOwner() {
require(msg.sender == owner, "not authorized");
_;
}
modifier updateReward(address _account) {
rewardPerTokenStored = rewardPerToken();
updatedAt = lastTimeRewardApplicable();
if (_account != address(0)) {
rewards[_account] = earned(_account);
userRewardPerTokenPaid[_account] = rewardPerTokenStored;
}
_;
}
function lastTimeRewardApplicable() public view returns (uint) {
return _min(finishAt, block.timestamp);
}
function rewardPerToken() public view returns (uint) {
if (totalSupply == 0) {
return rewardPerTokenStored;
}
return
rewardPerTokenStored +
(rewardRate * (lastTimeRewardApplicable() - updatedAt) * 1e18) /
totalSupply;
}
function stake(uint _amount) external updateReward(msg.sender) {
require(_amount > 0, "amount = 0");
stakingToken.transferFrom(msg.sender, address(this), _amount);
balanceOf[msg.sender] += _amount;
totalSupply += _amount;
}
function withdraw(uint _amount) external updateReward(msg.sender) {
require(_amount > 0, "amount = 0");
balanceOf[msg.sender] -= _amount;
totalSupply -= _amount;
stakingToken.transfer(msg.sender, _amount);
}
function earned(address _account) public view returns (uint) {
return
((balanceOf[_account] *
(rewardPerToken() - userRewardPerTokenPaid[_account])) / 1e18) +
rewards[_account];
}
function getReward() external updateReward(msg.sender) {
uint reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.transfer(msg.sender, reward);
}
}
function setRewardsDuration(uint _duration) external onlyOwner {
require(finishAt < block.timestamp, "reward duration not finished");
duration = _duration;
}
function notifyRewardAmount(
uint _amount
) external onlyOwner updateReward(address(0)) {
if (block.timestamp >= finishAt) {
rewardRate = _amount / duration;
} else {
uint remainingRewards = (finishAt - block.timestamp) * rewardRate;
rewardRate = (_amount + remainingRewards) / duration;
}
require(rewardRate > 0, "reward rate = 0");
require(
rewardRate * duration <= rewardsToken.balanceOf(address(this)),
"reward amount > balance"
);
finishAt = block.timestamp + duration;
updatedAt = block.timestamp;
}
function _min(uint x, uint y) private pure returns (uint) {
return x <= y ? x : y;
}
}
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment