-
Notifications
You must be signed in to change notification settings - Fork 0
/
RewardsUtils.sol
151 lines (141 loc) · 6.8 KB
/
RewardsUtils.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
// SPDX-FileCopyrightText: 2021 Lido <info@lido.fi>
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.10;
/// @author psirex
/// @notice Provides logic and data structure for convenient work with
/// staking rewards distributed in a time-based manner
library RewardsUtils {
/// @notice Keeps reward data for depositor
/// @param paidReward The reward paid to the depositor
/// @param upcomingReward The upcoming depositor's reward
/// @param accumulatedRewardPerTokenPaid The value of RewardsState.accumulatedRewardPerToken
/// has used to calculate the upcomingReward last time.
struct Reward {
uint256 paidReward;
uint256 upcomingReward;
uint256 accumulatedRewardPerTokenPaid;
}
/// @notice Stores state of rewards program
/// @param endDate End date of the reward program
/// @param updatedAt Last update timestamp
/// @param rewardPerSecond Amount of tokens distributed in one second
/// @param accumulatedRewardPerToken Sum of historical values
/// (PRECISION * timeDelta * rewardPerSecond) / totalStaked where timeDelta is a time passed from last update
/// @param rewards Rewards info of depositors
struct RewardsState {
uint256 endDate;
uint256 updatedAt;
uint256 rewardPerSecond;
uint256 accumulatedRewardPerToken;
mapping(address => Reward) rewards;
}
uint256 private constant PRECISION = 1e18;
/// @notice Updates current state of the reward program
/// @param state State of the reward program
/// @param totalStaked The total staked amount of tokens
/// @param rewardPerSecond Amount of tokens to distribute in one second
/// @param endDate End date of the reward program
/// @dev endDate value must be greater or equal to the current block.timestamp value
function updateRewardPeriod(
RewardsState storage state,
uint256 totalStaked,
uint256 rewardPerSecond,
uint256 endDate
) internal {
require(endDate >= block.timestamp, "END_DATE_TOO_LOW");
state.accumulatedRewardPerToken = rewardPerToken(state, totalStaked);
state.endDate = endDate;
state.updatedAt = block.timestamp;
state.rewardPerSecond = rewardPerSecond;
}
/// @notice Returns reward depositor earned and able to retrieve
/// @param state State of the reward program
/// @param totalStaked The total staked amount of tokens at the current block timestamp
/// @param depositor Address of the depositor
/// @param staked Amount of tokens staked by the depositor at the current block timestamp
function earnedReward(
RewardsState storage state,
uint256 totalStaked,
address depositor,
uint256 staked
) internal view returns (uint256) {
Reward storage depositorReward = state.rewards[depositor];
return
depositorReward.upcomingReward +
(staked *
(rewardPerToken(state, totalStaked) -
depositorReward.accumulatedRewardPerTokenPaid)) /
PRECISION;
}
/// @notice Updates reward of depositor stores this value in the upcoming reward and return
/// the new value of unpaid earned reward
/// @param state State of the reward program
/// @param prevTotalStaked The total amount of tokens has staked by all depositors before the current update
/// @param depositor Address of the depositor
/// @param prevStaked The amount of tokens staked by the depositor before the current update
/// @return depositorReward The new value of unpaid reward earned by the depositor
/// @dev This method must be called on every change of the depositor's balance with totalStaked
/// and staked equal to prev values of the balance of the depositor and totalStaked
function updateDepositorReward(
RewardsState storage state,
uint256 prevTotalStaked,
address depositor,
uint256 prevStaked
) internal returns (uint256 depositorReward) {
uint256 newRewardPerToken = _updateRewardPerToken(state, prevTotalStaked);
depositorReward = earnedReward(state, prevTotalStaked, depositor, prevStaked);
state.rewards[depositor].accumulatedRewardPerTokenPaid = newRewardPerToken;
state.rewards[depositor].upcomingReward = depositorReward;
return depositorReward;
}
/// @notice Marks upcoming reward as paid resets its value and return amount of paid reward
/// @param state State of the reward program
/// @param totalStaked The total staked amount of tokens at the current block timestamp
/// @param depositor Address of the depositor
/// @param staked Amount of tokens staked by the depositor at the current block timestamp
/// @return paidReward The amount of reward paid to the depositor
function payDepositorReward(
RewardsState storage state,
uint256 totalStaked,
address depositor,
uint256 staked
) internal returns (uint256 paidReward) {
paidReward = updateDepositorReward(state, totalStaked, depositor, staked);
state.rewards[depositor].upcomingReward = 0;
state.rewards[depositor].paidReward += paidReward;
}
/// @notice Returns value of accumulated reward per token at the time equal to
/// minimum between current block timestamp or reward period end date
/// @param state State of the reward program
/// @param totalStaked The total staked amount of tokens at the current block timestamp
function rewardPerToken(RewardsState storage state, uint256 totalStaked)
internal
view
returns (uint256)
{
if (totalStaked == 0) {
return state.accumulatedRewardPerToken;
}
uint256 timeDelta = _blockTimestampOrEndDate(state) - state.updatedAt;
uint256 unaccountedRewardPerToken = (PRECISION * timeDelta * state.rewardPerSecond) /
totalStaked;
return state.accumulatedRewardPerToken + unaccountedRewardPerToken;
}
/// @notice Updates the accumulated reward per token value
/// @param state State of the reward program
/// @param totalStaked The total staked amount of tokens at the current block timestamp
function _updateRewardPerToken(RewardsState storage state, uint256 totalStaked)
private
returns (uint256)
{
uint256 newRewardPerToken = rewardPerToken(state, totalStaked);
state.accumulatedRewardPerToken = newRewardPerToken;
state.updatedAt = _blockTimestampOrEndDate(state);
return newRewardPerToken;
}
/// @notice Returns the minimum between block.timestamp and endDate
/// @param state State of the reward program
function _blockTimestampOrEndDate(RewardsState storage state) private view returns (uint256) {
return state.endDate > block.timestamp ? block.timestamp : state.endDate;
}
}