forked from Synthetixio/synthetix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTradingRewards.sol
296 lines (215 loc) · 10.4 KB
/
TradingRewards.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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
pragma solidity ^0.5.16;
// Internal dependencies.
import "./Pausable.sol";
import "./MixinResolver.sol";
import "./Owned.sol";
// External dependencies.
import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/ERC20Detailed.sol";
import "openzeppelin-solidity-2.3.0/contracts/token/ERC20/SafeERC20.sol";
import "openzeppelin-solidity-2.3.0/contracts/utils/ReentrancyGuard.sol";
// Libraries.
import "./SafeDecimalMath.sol";
// Internal references.
import "./interfaces/ITradingRewards.sol";
import "./interfaces/IExchanger.sol";
// https://docs.synthetix.io/contracts/source/contracts/tradingrewards
contract TradingRewards is ITradingRewards, ReentrancyGuard, Owned, Pausable, MixinResolver {
using SafeMath for uint;
using SafeDecimalMath for uint;
using SafeERC20 for IERC20;
/* ========== STATE VARIABLES ========== */
uint private _currentPeriodID;
uint private _balanceAssignedToRewards;
mapping(uint => Period) private _periods;
struct Period {
bool isFinalized;
uint recordedFees;
uint totalRewards;
uint availableRewards;
mapping(address => uint) unaccountedFeesForAccount;
}
address private _periodController;
/* ========== ADDRESS RESOLVER CONFIGURATION ========== */
bytes32 private constant CONTRACT_EXCHANGER = "Exchanger";
bytes32 private constant CONTRACT_SYNTHETIX = "Synthetix";
/* ========== CONSTRUCTOR ========== */
constructor(
address owner,
address periodController,
address resolver
) public Owned(owner) MixinResolver(resolver) {
require(periodController != address(0), "Invalid period controller");
_periodController = periodController;
}
/* ========== VIEWS ========== */
function resolverAddressesRequired() public view returns (bytes32[] memory addresses) {
addresses = new bytes32[](2);
addresses[0] = CONTRACT_EXCHANGER;
addresses[1] = CONTRACT_SYNTHETIX;
}
function synthetix() internal view returns (IERC20) {
return IERC20(requireAndGetAddress(CONTRACT_SYNTHETIX));
}
function exchanger() internal view returns (IExchanger) {
return IExchanger(requireAndGetAddress(CONTRACT_EXCHANGER));
}
function getAvailableRewards() external view returns (uint) {
return _balanceAssignedToRewards;
}
function getUnassignedRewards() external view returns (uint) {
return synthetix().balanceOf(address(this)).sub(_balanceAssignedToRewards);
}
function getRewardsToken() external view returns (address) {
return address(synthetix());
}
function getPeriodController() external view returns (address) {
return _periodController;
}
function getCurrentPeriod() external view returns (uint) {
return _currentPeriodID;
}
function getPeriodIsClaimable(uint periodID) external view returns (bool) {
return _periods[periodID].isFinalized;
}
function getPeriodIsFinalized(uint periodID) external view returns (bool) {
return _periods[periodID].isFinalized;
}
function getPeriodRecordedFees(uint periodID) external view returns (uint) {
return _periods[periodID].recordedFees;
}
function getPeriodTotalRewards(uint periodID) external view returns (uint) {
return _periods[periodID].totalRewards;
}
function getPeriodAvailableRewards(uint periodID) external view returns (uint) {
return _periods[periodID].availableRewards;
}
function getUnaccountedFeesForAccountForPeriod(address account, uint periodID) external view returns (uint) {
return _periods[periodID].unaccountedFeesForAccount[account];
}
function getAvailableRewardsForAccountForPeriod(address account, uint periodID) external view returns (uint) {
return _calculateRewards(account, periodID);
}
function getAvailableRewardsForAccountForPeriods(address account, uint[] calldata periodIDs)
external
view
returns (uint totalRewards)
{
for (uint i = 0; i < periodIDs.length; i++) {
uint periodID = periodIDs[i];
totalRewards = totalRewards.add(_calculateRewards(account, periodID));
}
}
function _calculateRewards(address account, uint periodID) internal view returns (uint) {
Period storage period = _periods[periodID];
if (period.availableRewards == 0 || period.recordedFees == 0 || !period.isFinalized) {
return 0;
}
uint accountFees = period.unaccountedFeesForAccount[account];
if (accountFees == 0) {
return 0;
}
uint participationRatio = accountFees.divideDecimal(period.recordedFees);
return participationRatio.multiplyDecimal(period.totalRewards);
}
/* ========== MUTATIVE FUNCTIONS ========== */
function claimRewardsForPeriod(uint periodID) external nonReentrant notPaused {
_claimRewards(msg.sender, periodID);
}
function claimRewardsForPeriods(uint[] calldata periodIDs) external nonReentrant notPaused {
for (uint i = 0; i < periodIDs.length; i++) {
uint periodID = periodIDs[i];
// Will revert if any independent claim reverts.
_claimRewards(msg.sender, periodID);
}
}
function _claimRewards(address account, uint periodID) internal {
Period storage period = _periods[periodID];
require(period.isFinalized, "Period is not finalized");
uint amountToClaim = _calculateRewards(account, periodID);
require(amountToClaim > 0, "No rewards available");
period.unaccountedFeesForAccount[account] = 0;
period.availableRewards = period.availableRewards.sub(amountToClaim);
_balanceAssignedToRewards = _balanceAssignedToRewards.sub(amountToClaim);
synthetix().safeTransfer(account, amountToClaim);
emit RewardsClaimed(account, amountToClaim, periodID);
}
/* ========== RESTRICTED FUNCTIONS ========== */
function recordExchangeFeeForAccount(uint usdFeeAmount, address account) external onlyExchanger {
Period storage period = _periods[_currentPeriodID];
// Note: In theory, the current period will never be finalized.
// Such a require could be added here, but it would just spend gas, since it should always satisfied.
period.unaccountedFeesForAccount[account] = period.unaccountedFeesForAccount[account].add(usdFeeAmount);
period.recordedFees = period.recordedFees.add(usdFeeAmount);
emit ExchangeFeeRecorded(account, usdFeeAmount, _currentPeriodID);
}
function closeCurrentPeriodWithRewards(uint rewards) external onlyPeriodController {
uint currentBalance = synthetix().balanceOf(address(this));
uint availableForNewRewards = currentBalance.sub(_balanceAssignedToRewards);
require(rewards <= availableForNewRewards, "Insufficient free rewards");
Period storage period = _periods[_currentPeriodID];
period.totalRewards = rewards;
period.availableRewards = rewards;
period.isFinalized = true;
_balanceAssignedToRewards = _balanceAssignedToRewards.add(rewards);
emit PeriodFinalizedWithRewards(_currentPeriodID, rewards);
_currentPeriodID = _currentPeriodID.add(1);
emit NewPeriodStarted(_currentPeriodID);
}
function recoverTokens(address tokenAddress, address recoverAddress) external onlyOwner {
_validateRecoverAddress(recoverAddress);
require(tokenAddress != address(synthetix()), "Must use another function");
IERC20 token = IERC20(tokenAddress);
uint tokenBalance = token.balanceOf(address(this));
require(tokenBalance > 0, "No tokens to recover");
token.safeTransfer(recoverAddress, tokenBalance);
emit TokensRecovered(tokenAddress, recoverAddress, tokenBalance);
}
function recoverUnassignedRewardTokens(address recoverAddress) external onlyOwner {
_validateRecoverAddress(recoverAddress);
uint tokenBalance = synthetix().balanceOf(address(this));
require(tokenBalance > 0, "No tokens to recover");
uint unassignedBalance = tokenBalance.sub(_balanceAssignedToRewards);
require(unassignedBalance > 0, "No tokens to recover");
synthetix().safeTransfer(recoverAddress, unassignedBalance);
emit UnassignedRewardTokensRecovered(recoverAddress, unassignedBalance);
}
function recoverAssignedRewardTokensAndDestroyPeriod(address recoverAddress, uint periodID) external onlyOwner {
_validateRecoverAddress(recoverAddress);
require(periodID < _currentPeriodID, "Cannot recover from active");
Period storage period = _periods[periodID];
require(period.availableRewards > 0, "No rewards available to recover");
uint amount = period.availableRewards;
synthetix().safeTransfer(recoverAddress, amount);
_balanceAssignedToRewards = _balanceAssignedToRewards.sub(amount);
delete _periods[periodID];
emit AssignedRewardTokensRecovered(recoverAddress, amount, periodID);
}
function _validateRecoverAddress(address recoverAddress) internal view {
if (recoverAddress == address(0) || recoverAddress == address(this)) {
revert("Invalid recover address");
}
}
function setPeriodController(address newPeriodController) external onlyOwner {
require(newPeriodController != address(0), "Invalid period controller");
_periodController = newPeriodController;
emit PeriodControllerChanged(newPeriodController);
}
/* ========== MODIFIERS ========== */
modifier onlyPeriodController() {
require(msg.sender == _periodController, "Caller not period controller");
_;
}
modifier onlyExchanger() {
require(msg.sender == address(exchanger()), "Only Exchanger can invoke this");
_;
}
/* ========== EVENTS ========== */
event ExchangeFeeRecorded(address indexed account, uint amount, uint periodID);
event RewardsClaimed(address indexed account, uint amount, uint periodID);
event NewPeriodStarted(uint periodID);
event PeriodFinalizedWithRewards(uint periodID, uint rewards);
event TokensRecovered(address tokenAddress, address recoverAddress, uint amount);
event UnassignedRewardTokensRecovered(address recoverAddress, uint amount);
event AssignedRewardTokensRecovered(address recoverAddress, uint amount, uint periodID);
event PeriodControllerChanged(address newPeriodController);
}