Created
July 19, 2023 12:52
-
-
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=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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