-
Notifications
You must be signed in to change notification settings - Fork 109
/
Copy pathStaking.sol
132 lines (115 loc) · 4.26 KB
/
Staking.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
// SPDX-License-Identifier: MIT
// Inspired by https://solidity-by-example.org/defi/staking-rewards/
pragma solidity ^0.8.7;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
error TransferFailed();
error NeedsMoreThanZero();
contract Staking is ReentrancyGuard {
IERC20 public s_rewardsToken;
IERC20 public s_stakingToken;
// This is the reward token per second
// Which will be multiplied by the tokens the user staked divided by the total
// This ensures a steady reward rate of the platform
// So the more users stake, the less for everyone who is staking.
uint256 public constant REWARD_RATE = 100;
uint256 public s_lastUpdateTime;
uint256 public s_rewardPerTokenStored;
mapping(address => uint256) public s_userRewardPerTokenPaid;
mapping(address => uint256) public s_rewards;
uint256 private s_totalSupply;
mapping(address => uint256) public s_balances;
event Staked(address indexed user, uint256 indexed amount);
event WithdrewStake(address indexed user, uint256 indexed amount);
event RewardsClaimed(address indexed user, uint256 indexed amount);
constructor(address stakingToken, address rewardsToken) {
s_stakingToken = IERC20(stakingToken);
s_rewardsToken = IERC20(rewardsToken);
}
/**
* @notice How much reward a token gets based on how long it's been in and during which "snapshots"
*/
function rewardPerToken() public view returns (uint256) {
if (s_totalSupply == 0) {
return s_rewardPerTokenStored;
}
return
s_rewardPerTokenStored +
(((block.timestamp - s_lastUpdateTime) * REWARD_RATE * 1e18) / s_totalSupply);
}
/**
* @notice How much reward a user has earned
*/
function earned(address account) public view returns (uint256) {
return
((s_balances[account] * (rewardPerToken() - s_userRewardPerTokenPaid[account])) /
1e18) + s_rewards[account];
}
/**
* @notice Deposit tokens into this contract
* @param amount | How much to stake
*/
function stake(uint256 amount)
external
updateReward(msg.sender)
nonReentrant
moreThanZero(amount)
{
s_totalSupply += amount;
s_balances[msg.sender] += amount;
emit Staked(msg.sender, amount);
bool success = s_stakingToken.transferFrom(msg.sender, address(this), amount);
if (!success) {
revert TransferFailed();
}
}
/**
* @notice Withdraw tokens from this contract
* @param amount | How much to withdraw
*/
function withdraw(uint256 amount) external updateReward(msg.sender) nonReentrant {
s_totalSupply -= amount;
s_balances[msg.sender] -= amount;
emit WithdrewStake(msg.sender, amount);
bool success = s_stakingToken.transfer(msg.sender, amount);
if (!success) {
revert TransferFailed();
}
}
/**
* @notice User claims their tokens
*/
function claimReward() external updateReward(msg.sender) nonReentrant {
uint256 reward = s_rewards[msg.sender];
s_rewards[msg.sender] = 0;
emit RewardsClaimed(msg.sender, reward);
bool success = s_rewardsToken.transfer(msg.sender, reward);
if (!success) {
revert TransferFailed();
}
}
/********************/
/* Modifiers Functions */
/********************/
modifier updateReward(address account) {
s_rewardPerTokenStored = rewardPerToken();
s_lastUpdateTime = block.timestamp;
s_rewards[account] = earned(account);
s_userRewardPerTokenPaid[account] = s_rewardPerTokenStored;
_;
}
modifier moreThanZero(uint256 amount) {
if (amount == 0) {
revert NeedsMoreThanZero();
}
_;
}
/********************/
/* Getter Functions */
/********************/
// Ideally, we'd have getter functions for all our s_ variables we want exposed, and set them all to private.
// But, for the purpose of this demo, we've left them public for simplicity.
function getStaked(address account) public view returns (uint256) {
return s_balances[account];
}
}