Skip to content

Instantly share code, notes, and snippets.

@casweeney
Last active February 21, 2023 23:25
Show Gist options
  • Select an option

  • Save casweeney/389e5b50519458b431d2a2900260849f to your computer and use it in GitHub Desktop.

Select an option

Save casweeney/389e5b50519458b431d2a2900260849f to your computer and use it in GitHub Desktop.
Staking Rewards contract with multiple staking positions for a particular user (address)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "./IERC20.sol";
contract AtomicStaking {
IERC20 public rewardToken;
IERC20 public stakingToken;
address owner;
uint256 public duration;
uint256 public finishAt;
uint256 public updatedAt;
uint256 public rewardRate;
uint256 public rewardPerTokenStored;
uint256 public totalStake;
struct StakingPosition {
uint256 balance; // amount staked for this position
uint256 reward; // reward earned for this position
uint256 rewardPerToken;
}
mapping(address => StakingPosition[]) public userStakingPositions;
constructor(address _rewardToken, address _stakingToken) {
owner = msg.sender;
rewardToken = IERC20(_rewardToken);
stakingToken = IERC20(_stakingToken);
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier updateReward(address _account, uint256 _positionIndex) {
rewardPerTokenStored = rewardPerToken();
updatedAt = lastTimeRewardApplicable();
if(_account != address(0)) {
if(userStakingPositions[_account].length != 0){
uint lastIndex = _positionIndex - 1;
userStakingPositions[_account][lastIndex].reward = rewardsEarned(_account, lastIndex);
userStakingPositions[_account][lastIndex].rewardPerToken = rewardPerTokenStored;
}
}
_;
}
function setRewardDuration(uint256 _duration) external onlyOwner {
require(block.timestamp > finishAt, "duration for reward not ended");
duration = _duration;
}
function setRewardAmount(uint256 _amount) external onlyOwner updateReward(address(0), 0) {
require(duration > 0, "reward duration = 0");
if (block.timestamp > finishAt) {
rewardRate = _amount / duration;
} else {
uint256 remainingRewards = rewardRate * (finishAt - block.timestamp);
rewardRate = (remainingRewards + _amount) / duration;
}
require(rewardRate > 0, "reward rate = 0");
require(rewardRate * duration <= rewardToken.balanceOf(address(this)), "insufficient reward token");
finishAt = block.timestamp + duration;
updatedAt = block.timestamp;
}
function stake(uint256 _amount) external updateReward(msg.sender, userNextPositionIndex(msg.sender)) {
require(_amount > 0, "can't stake zero amount");
require(duration > 0, "duration not set");
require(rewardRate > 0, "reward rate not set");
stakingToken.transferFrom(msg.sender, address(this), _amount);
StakingPosition memory sp = StakingPosition({balance: _amount, reward: 0, rewardPerToken: 0});
userStakingPositions[msg.sender].push(sp);
totalStake += _amount;
}
function withdrawToken(uint256 _amount, uint256 _positionIndex) external updateReward(msg.sender, _positionIndex + 1) {
require(_positionIndex <= (userStakingPositions[msg.sender].length - 1), "invalid position index");
require(_amount > 0, "can't withdraw zero amount");
userStakingPositions[msg.sender][_positionIndex].balance -= _amount;
totalStake -= _amount;
}
function lastTimeRewardApplicable() public view returns (uint256) {
return _min(block.timestamp, finishAt);
}
function rewardPerToken() public view returns (uint256) {
if (totalStake == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (rewardRate * (lastTimeRewardApplicable() - updatedAt) * 1e18) / totalStake;
}
function rewardsEarned(address _account, uint256 _positionIndex) public view returns (uint256) {
if(userStakingPositions[_account].length == 0){
return 0;
}
return ((userStakingPositions[_account][_positionIndex].balance * (rewardPerToken() - userStakingPositions[_account][_positionIndex].rewardPerToken)) / 1e18) + userStakingPositions[_account][_positionIndex].reward;
}
function getReward(uint256 _positionIndex) external updateReward(msg.sender, _positionIndex + 1) {
require(_positionIndex <= (userStakingPositions[msg.sender].length - 1), "invalid position index");
uint256 reward = userStakingPositions[msg.sender][_positionIndex].reward;
if (reward > 0) {
userStakingPositions[msg.sender][_positionIndex].reward = 0;
rewardToken.transfer(msg.sender, reward);
}
}
function userNextPositionIndex(address _account) public view returns (uint256) {
return userStakingPositions[_account].length;
}
function _min(uint256 x, uint256 y) private pure returns (uint256) {
return x <= y ? x : y;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment