Skip to content

Instantly share code, notes, and snippets.

@jadeden1999
Created July 19, 2023 12:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jadeden1999/3150f04f38631ec55f712138dae420ef to your computer and use it in GitHub Desktop.
Save jadeden1999/3150f04f38631ec55f712138dae420ef to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.18+commit.87f61d96.js&optimize=false&runs=200&gist=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "./UniswapV2Handler.sol";
import "hardhat/console.sol";
interface IRewardToken is IERC20 {
function decimals() external view returns (uint8);
}
contract StakingSystem is Ownable, ERC721Holder {
IRewardToken public rewardsToken;
address public rewardTokenAddress;
IERC721 public nft;
UniswapV2Handler uniswapHandler;
uint256 public stakedTotal;
uint256 public stakingStartTime;
uint256 constant stakingTime = 7 days;
uint256 rewardTokenDecimals; // Decimals for usdt
uint256 rewardPayoutAmount;
struct Staker {
uint256[] tokenIds;
mapping(uint256 => uint256) tokenStakingCoolDown;
uint256 balance;
uint256 rewardsReleased;
}
constructor(IERC721 _nft, IRewardToken _rewardsToken, address _uniswapHandlerAddress) {
nft = _nft;
rewardsToken = _rewardsToken;
uniswapHandler = UniswapV2Handler(_uniswapHandlerAddress);
rewardTokenDecimals = 6;
rewardPayoutAmount = 1_000_000; // 1 usdt 6 decimals
initStaking();
setTokensClaimable(true);
}
/// @notice mapping of a staker to its wallet
mapping(address => Staker) public stakers;
/// @notice Mapping from token ID to owner address
mapping(uint256 => address) public tokenOwner;
bool public tokensClaimable;
bool initialised;
/// @notice event emitted when a user has staked a nft
event Staked(address owner, uint256 amount);
/// @notice event emitted when a user has unstaked a nft
event Unstaked(address owner, uint256 amount);
/// @notice event emitted when a user claims reward
event RewardPaid(address indexed user, uint256 reward);
/// @notice Allows reward tokens to be claimed
event ClaimableStatusUpdated(bool status);
/// @notice Emergency unstake tokens without rewards
event EmergencyUnstake(address indexed user, uint256 tokenId);
function initStaking() public onlyOwner {
//needs access control
require(!initialised, "Already initialised");
stakingStartTime = block.timestamp;
initialised = true;
}
function setTokensClaimable(bool _enabled) public onlyOwner {
//needs access control
tokensClaimable = _enabled;
emit ClaimableStatusUpdated(_enabled);
}
function setUniswapHandler(UniswapV2Handler _uniswapHandler) public onlyOwner {
uniswapHandler = UniswapV2Handler(_uniswapHandler);
}
function getStakedTokens(
address _user
) public view returns (uint256[] memory tokenIds) {
return stakers[_user].tokenIds;
}
function getUserDetails(address _user)
public
view
returns (uint256[] memory, uint256[] memory, uint256[] memory)
{
Staker storage staker = stakers[_user];
uint256[] memory ids = staker.tokenIds;
uint256[] memory cooldowns = new uint256[](ids.length);
uint256[] memory rewards = new uint256[](ids.length);
for (uint256 i = 0; i < ids.length; i++) {
uint256 tokenId = ids[i];
cooldowns[i] = staker.tokenStakingCoolDown[tokenId];
if (
staker.tokenStakingCoolDown[tokenId] <
block.timestamp + stakingTime &&
staker.tokenStakingCoolDown[tokenId] > 0
) {
uint256 stakedDays = (
(block.timestamp -
uint(staker.tokenStakingCoolDown[tokenId]))
) / stakingTime;
rewards[i] = stakedDays * rewardPayoutAmount;
}
}
return (ids, cooldowns, rewards);
}
function stake(uint256 tokenId) public {
require(
block.timestamp >= stakingStartTime,
"Staking has not started yet"
);
_stake(msg.sender, tokenId);
}
function stakeBatch(uint256[] memory tokenIds) public {
for (uint256 i = 0; i < tokenIds.length; i++) {
_stake(msg.sender, tokenIds[i]);
}
}
function _stake(address _user, uint256 _tokenId) internal {
require(initialised, "Staking System: the staking has not started");
require(
nft.ownerOf(_tokenId) == _user,
"user must be the owner of the token"
);
Staker storage staker = stakers[_user];
staker.tokenIds.push(_tokenId);
staker.tokenStakingCoolDown[_tokenId] = block.timestamp;
tokenOwner[_tokenId] = _user;
nft.approve(address(this), _tokenId);
nft.safeTransferFrom(_user, address(this), _tokenId);
emit Staked(_user, _tokenId);
stakedTotal++;
}
function unstake(uint256 _tokenId) public {
claimReward(msg.sender);
_unstake(msg.sender, _tokenId);
}
function unstakeBatch(uint256[] memory tokenIds) public {
claimReward(msg.sender);
for (uint256 i = 0; i < tokenIds.length; i++) {
if (tokenOwner[tokenIds[i]] == msg.sender) {
_unstake(msg.sender, tokenIds[i]);
}
}
}
// Unstake without caring about rewards. EMERGENCY ONLY.
function emergencyUnstake(uint256 _tokenId) public {
require(
tokenOwner[_tokenId] == msg.sender,
"nft._unstake: Sender must have staked tokenID"
);
_unstake(msg.sender, _tokenId);
emit EmergencyUnstake(msg.sender, _tokenId);
}
function _unstake(address _user, uint256 _tokenId) internal {
require(
tokenOwner[_tokenId] == _user,
"Nft Staking System: user must be the owner of the staked nft"
);
Staker storage staker = stakers[_user];
uint256 lastIndex = staker.tokenIds.length - 1;
for (uint256 i = 0; i <= lastIndex; i++) {
if (staker.tokenIds[i] == _tokenId) {
staker.tokenIds[i] = staker.tokenIds[lastIndex]; // Swap with the last element
break;
}
}
if (staker.tokenIds.length > 0) {
staker.tokenIds.pop(); // Remove the last element
}
staker.tokenStakingCoolDown[_tokenId] = 0;
delete tokenOwner[_tokenId];
nft.safeTransferFrom(address(this), _user, _tokenId);
emit Unstaked(_user, _tokenId);
stakedTotal--;
}
function unstakeAll() public {
claimReward(msg.sender);
uint256[] memory tokenIds = stakers[msg.sender].tokenIds;
for (uint256 i = 0; i < tokenIds.length; i++) {
_unstake(msg.sender, tokenIds[i]);
}
}
function updateReward(address _user) public {
Staker storage staker = stakers[_user];
uint256[] storage ids = staker.tokenIds;
for (uint256 i = 0; i < ids.length; i++) {
if (
staker.tokenStakingCoolDown[ids[i]] <
block.timestamp + stakingTime &&
staker.tokenStakingCoolDown[ids[i]] > 0
) {
uint256 stakedDays = (
(block.timestamp -
uint(staker.tokenStakingCoolDown[ids[i]]))
) / stakingTime;
uint256 partialTime = (
(block.timestamp -
uint(staker.tokenStakingCoolDown[ids[i]]))
) % stakingTime;
staker.balance += stakedDays;
staker.tokenStakingCoolDown[ids[i]] =
block.timestamp +
partialTime;
console.logUint(staker.tokenStakingCoolDown[ids[i]]);
console.logUint(staker.balance);
}
}
}
/// @notice Calculate the reward to be released
/// @param rewardBalance accumulated rewards for the user
/// @return rewardAmount total payable amount to user
function calculateReward(uint256 rewardBalance) public view returns(uint256 rewardAmount){
rewardAmount = rewardBalance * rewardPayoutAmount;
return rewardAmount;
}
/// @notice Claims all rewards for specified user
/// @param _user Address of user to claim reward for
function claimReward(address _user) public {
require(tokensClaimable == true, "Tokens cannnot be claimed yet");
updateReward(_user);
require(stakers[_user].balance > 0, "0 rewards yet");
uint256 rewardAmount = calculateReward(stakers[_user].balance);
stakers[_user].rewardsReleased += rewardAmount;
stakers[_user].balance = 0;
uniswapHandler.swapTokensWithFees(rewardAmount, _user);
emit RewardPaid(_user, rewardAmount);
}
function setNft(IERC721 _nft ) public onlyOwner {
nft= _nft;
}
/// @notice Claims all rewards by user
function claimAllRewards() public {
require(tokensClaimable == true, "Tokens cannnot be claimed yet");
updateReward(msg.sender);
uint256 rewardAmount = calculateReward(stakers[msg.sender].balance);
stakers[msg.sender].rewardsReleased += rewardAmount;
stakers[msg.sender].balance = 0;
uniswapHandler.swapTokensWithFees(rewardAmount, msg.sender);
emit RewardPaid(msg.sender, rewardAmount);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment