-
Notifications
You must be signed in to change notification settings - Fork 1
/
NftStaking.sol
185 lines (149 loc) · 7.13 KB
/
NftStaking.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract ERC721Staking is ReentrancyGuard {
using SafeERC20 for IERC20;
// Interfaces for ERC20 and ERC721
IERC20 public immutable rewardsToken;
IERC721 public immutable nftCollection;
// Constructor function to set the rewards token and the NFT collection addresses
constructor(IERC721 _nftCollection, IERC20 _rewardsToken) {
nftCollection = _nftCollection;
rewardsToken = _rewardsToken;
}
struct StakedToken {
address staker;
uint256 tokenId;
}
// Staker info
struct Staker {
// Amount of tokens staked by the staker
uint256 amountStaked;
// Staked token ids
StakedToken[] stakedTokens;
// Last time of the rewards were calculated for this user
uint256 timeOfLastUpdate;
// Calculated, but unclaimed rewards for the User. The rewards are
// calculated each time the user writes to the Smart Contract
uint256 unclaimedRewards;
}
// Rewards per hour per token deposited in wei.
uint256 private rewardsPerHour = 100000;
// Mapping of User Address to Staker info
mapping(address => Staker) public stakers;
// Mapping of Token Id to staker. Made for the SC to remeber
// who to send back the ERC721 Token to.
mapping(uint256 => address) public stakerAddress;
// If address already has ERC721 Token/s staked, calculate the rewards.
// Increment the amountStaked and map msg.sender to the Token Id of the staked
// Token to later send back on withdrawal. Finally give timeOfLastUpdate the
// value of now.
function stake(uint256 _tokenId) external nonReentrant {
// If wallet has tokens staked, calculate the rewards before adding the new token
if (stakers[msg.sender].amountStaked > 0) {
uint256 rewards = calculateRewards(msg.sender);
stakers[msg.sender].unclaimedRewards += rewards;
}
// Wallet must own the token they are trying to stake
require(
nftCollection.ownerOf(_tokenId) == msg.sender,
"You don't own this token!"
);
// Transfer the token from the wallet to the Smart contract
nftCollection.transferFrom(msg.sender, address(this), _tokenId);
// Create StakedToken
StakedToken memory stakedToken = StakedToken(msg.sender, _tokenId);
// Add the token to the stakedTokens array
stakers[msg.sender].stakedTokens.push(stakedToken);
// Increment the amount staked for this wallet
stakers[msg.sender].amountStaked++;
// Update the mapping of the tokenId to the staker's address
stakerAddress[_tokenId] = msg.sender;
// Update the timeOfLastUpdate for the staker
stakers[msg.sender].timeOfLastUpdate = block.timestamp;
}
// Check if user has any ERC721 Tokens Staked and if they tried to withdraw,
// calculate the rewards and store them in the unclaimedRewards
// decrement the amountStaked of the user and transfer the ERC721 token back to them
function withdraw(uint256 _tokenId) external nonReentrant {
// Make sure the user has at least one token staked before withdrawing
require(
stakers[msg.sender].amountStaked > 0,
"You have no tokens staked"
);
// Wallet must own the token they are trying to withdraw
require(stakerAddress[_tokenId] == msg.sender, "You don't own this token!");
// Update the rewards for this user, as the amount of rewards decreases with less tokens.
uint256 rewards = calculateRewards(msg.sender);
stakers[msg.sender].unclaimedRewards += rewards;
// Find the index of this token id in the stakedTokens array
uint256 index = 0;
for (uint256 i = 0; i < stakers[msg.sender].stakedTokens.length; i++) {
if (stakers[msg.sender].stakedTokens[i].tokenId == _tokenId) {
index = i;
break;
}
}
// Set this token's .staker to be address 0 to mark it as no longer staked
stakers[msg.sender].stakedTokens[index].staker = address(0);
// Decrement the amount staked for this wallet
stakers[msg.sender].amountStaked--;
// Update the mapping of the tokenId to the be address(0) to indicate that the token is no longer staked
stakerAddress[_tokenId] = address(0);
// Transfer the token back to the withdrawer
nftCollection.transferFrom(address(this), msg.sender, _tokenId);
// Update the timeOfLastUpdate for the withdrawer
stakers[msg.sender].timeOfLastUpdate = block.timestamp;
}
// Calculate rewards for the msg.sender, check if there are any rewards
// claim, set unclaimedRewards to 0 and transfer the ERC20 Reward token
// to the user.
function claimRewards() external {
uint256 rewards = calculateRewards(msg.sender) +
stakers[msg.sender].unclaimedRewards;
require(rewards > 0, "You have no rewards to claim");
stakers[msg.sender].timeOfLastUpdate = block.timestamp;
stakers[msg.sender].unclaimedRewards = 0;
rewardsToken.safeTransfer(msg.sender, rewards);
}
function availableRewards(address _staker) public view returns (uint256) {
uint256 rewards = calculateRewards(_staker) +
stakers[_staker].unclaimedRewards;
return rewards;
}
function getStakedTokens(address _user) public view returns (StakedToken[] memory) {
// Check if we know this user
if (stakers[_user].amountStaked > 0) {
// Return all the tokens in the stakedToken Array for this user that are not -1
StakedToken[] memory _stakedTokens = new StakedToken[](stakers[_user].amountStaked);
uint256 _index = 0;
for (uint256 j = 0; j < stakers[_user].stakedTokens.length; j++) {
if (stakers[_user].stakedTokens[j].staker != (address(0))) {
_stakedTokens[_index] = stakers[_user].stakedTokens[j];
_index++;
}
}
return _stakedTokens;
}
// Otherwise, return empty array
else {
return new StakedToken[](0);
}
}
// Calculate rewards for param _staker by calculating the time passed
// since last update in hours and mulitplying it to ERC721 Tokens Staked
// and rewardsPerHour.
function calculateRewards(address _staker)
internal
view
returns (uint256 _rewards)
{
return (((
((block.timestamp - stakers[_staker].timeOfLastUpdate) *
stakers[_staker].amountStaked)
) * rewardsPerHour) / 3600);
}
}