-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathETHAutoStaking.sol
More file actions
executable file
·332 lines (274 loc) · 9.37 KB
/
ETHAutoStaking.sol
File metadata and controls
executable file
·332 lines (274 loc) · 9.37 KB
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import '@openzeppelin/contracts/access/AccessControl.sol';
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol';
import './interfaces/IAutoStaking.sol';
import './interfaces/IPYXStaking.sol';
import './interfaces/IPYXToken.sol';
/**
* this contract is to convert ETH into PYX and then stake interfaces
* the PYX is purchased from pancakeswap for the market price and then staked.
*/
contract ETHAutoStaking is AccessControl, IAutoStaking {
using SafeMath for uint256;
struct Settings {
uint256 MIN_AUTO_STAKE_STEPS; // 90
uint256 STAKE_BONUS; // 20
uint256 INFLATION_RATE; // 12
uint256 INFLATION_RATE_DIVIDER; // 364
uint256 STEP_SECONDS; // 86400
}
struct Addresses {
IPYXToken PYX_TOKEN;
IPYXStaking PYX_STAKING;
IUniswapV2Router02 UNISWAP;
address RECIPIENT;
}
event ETHStake(
address indexed account,
uint256 indexed eth,
uint256 indexed buyBackPYX,
uint256 pyx
);
event AddForSalePYX(
address indexed account,
uint256 indexed pyx,
uint256 indexed totalForSalePYX
);
event AddInflatedForSalePYX(
address indexed account,
uint256 indexed pyx,
uint256 indexed totalForSalePYX
);
event WithdrawSlippagePYX(
uint256 indexed pyx,
address indexed recipient,
address indexed account
);
event UpdateSettings(
bytes32 indexed setting,
uint256 indexed newValue,
address indexed caller
);
// constants
bytes32 public constant SETTER_ROLE = keccak256('SETTER_ROLE');
bytes32 public constant PYX_ADDER_ROLE = keccak256('PYX_ADDER_ROLE');
bytes32 public constant SETTINGS_MANAGER_ROLE =
keccak256('SETTINGS_MANAGER_ROLE');
// settings
Settings public SETTINGS;
Addresses public ADDRESSES;
// numDay => open
mapping(uint256 => bool) public IS_OPEN_OF;
// states
uint256 public totalForSalePYX;
uint256 public totalSoldPYX;
uint256 public totalSlippagePYX;
uint256 public withdrawnSlippagePYX;
modifier onlySetter() {
require(
hasRole(SETTER_ROLE, msg.sender),
'ETHAutoStaking: Caller is not a setter'
);
_;
}
modifier onlyPYXAdder() {
require(
hasRole(PYX_ADDER_ROLE, msg.sender),
'ETHAutoStaking: Caller is not a PYX adder'
);
_;
}
modifier onlySettingsManager() {
require(
hasRole(SETTINGS_MANAGER_ROLE, msg.sender),
'ETHAutoStaking: Caller is not a settings manager'
);
_;
}
constructor() public {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(SETTER_ROLE, msg.sender);
}
function init(
uint256 _minimumAutoStakeSteps,
uint256 _stakeBonus,
uint256 _inflationRate,
uint256 _inflationRateDivider,
uint256 _stepSeconds,
address _pyxToken,
address _pyxStaking,
address _uniswap,
address _recipient,
address[] calldata _pyxAdderAccounts
) external onlySetter {
SETTINGS.MIN_AUTO_STAKE_STEPS = _minimumAutoStakeSteps;
SETTINGS.STAKE_BONUS = _stakeBonus;
SETTINGS.INFLATION_RATE = _inflationRate;
SETTINGS.INFLATION_RATE_DIVIDER = _inflationRateDivider;
SETTINGS.STEP_SECONDS = _stepSeconds;
IS_OPEN_OF[6] = true; // wednesday
IS_OPEN_OF[1] = true; // friday
IS_OPEN_OF[3] = true; // sunday
ADDRESSES.PYX_TOKEN = IPYXToken(_pyxToken);
ADDRESSES.PYX_STAKING = IPYXStaking(_pyxStaking);
ADDRESSES.UNISWAP = IUniswapV2Router02(_uniswap);
ADDRESSES.RECIPIENT = _recipient;
for (
uint256 idx = 0;
idx < _pyxAdderAccounts.length;
idx = idx.add(1)
) {
_setupRole(PYX_ADDER_ROLE, _pyxAdderAccounts[idx]);
}
renounceRole(SETTER_ROLE, msg.sender);
}
function ethStake(uint256 _stakeSteps, uint256 _pyxToGet) external payable {
require(
_stakeSteps >= SETTINGS.MIN_AUTO_STAKE_STEPS,
'ETHAutoStaking[ethStake]: _stakeSteps < SETTINGS.MIN_AUTO_STAKE_STEPS'
);
uint256 numDayInWeek = getNumDayInWeek();
require(
IS_OPEN_OF[numDayInWeek],
'ETHAutoStaking[ethStake]: staking closed'
);
uint256 pyxAvailableForSale = totalForSalePYX.sub(totalSoldPYX);
require(
_pyxToGet <= pyxAvailableForSale,
'ETHAutoStaking[ethStake]: _pyxToGet > pyxAvailableForSale'
);
// use eth to buy from uniswap
uint256 buyBackPYX = _buyBack(msg.value, _pyxToGet);
// calculate fees and ref bonus
uint256 slippagePYX = buyBackPYX.sub(_pyxToGet);
// transfer the slippage token to the recipient
if (slippagePYX > 0) {
totalSlippagePYX = totalSlippagePYX.add(slippagePYX);
}
// state - update
totalSoldPYX = totalSoldPYX.add(_pyxToGet);
ADDRESSES.PYX_STAKING.contractStake(
msg.sender,
_stakeSteps,
_pyxToGet,
_pyxToGet,
SETTINGS.STAKE_BONUS
);
_addInflatedForSalePYX(_stakeSteps, _pyxToGet);
// [event]
emit ETHStake(msg.sender, msg.value, buyBackPYX, _pyxToGet);
}
function withdrawSlippagePYX() external {
uint256 slippagePYXLeft = totalSlippagePYX.sub(withdrawnSlippagePYX);
require(
slippagePYXLeft > 0,
'ETHAutoStaking[withdrawSlippagePYX]: slippagePYXLeft <= 0'
);
ADDRESSES.PYX_TOKEN.mint(ADDRESSES.RECIPIENT, slippagePYXLeft);
withdrawnSlippagePYX = withdrawnSlippagePYX.add(slippagePYXLeft);
// [event]
emit WithdrawSlippagePYX(
slippagePYXLeft,
ADDRESSES.RECIPIENT,
msg.sender
);
}
function addForSalePYX(uint256 _pyx) external {
ADDRESSES.PYX_TOKEN.burn(msg.sender, _pyx);
totalForSalePYX = totalForSalePYX.add(_pyx);
// [event]
emit AddForSalePYX(msg.sender, _pyx, totalForSalePYX);
}
/* settings */
function addStakedDay(uint256 _day) external onlySettingsManager {
IS_OPEN_OF[_day] = true;
emit UpdateSettings('IS_OPEN_OF:add', _day, msg.sender);
}
function removeStakedDay(uint256 _day) external onlySettingsManager {
delete IS_OPEN_OF[_day];
emit UpdateSettings('IS_OPEN_OF:remove', _day, msg.sender);
}
function setMinAutoStakeSteps(uint256 _steps) external onlySettingsManager {
SETTINGS.MIN_AUTO_STAKE_STEPS = _steps;
emit UpdateSettings('MIN_AUTO_STAKE_STEPS', _steps, msg.sender);
}
function setStakeBonus(uint256 _bonus) external onlySettingsManager {
SETTINGS.STAKE_BONUS = _bonus;
emit UpdateSettings('STAKE_BONUS', _bonus, msg.sender);
}
function setInflationRate(uint256 _inflationRate)
external
onlySettingsManager
{
SETTINGS.INFLATION_RATE = _inflationRate;
emit UpdateSettings('INFLATION_RATE', _inflationRate, msg.sender);
}
function setInflationRateDivider(uint256 _inflationRateDivider)
external
onlySettingsManager
{
SETTINGS.INFLATION_RATE_DIVIDER = _inflationRateDivider;
emit UpdateSettings(
'INFLATION_RATE_DIVIDER',
_inflationRateDivider,
msg.sender
);
}
/* end settings */
function addInflatedForSalePYX(uint256 _stakeSteps, uint256 _pyx)
external
override
onlyPYXAdder
{
_addInflatedForSalePYX(_stakeSteps, _pyx);
}
function _addInflatedForSalePYX(uint256 _stakeSteps, uint256 _pyx) private {
uint256 inflatedPYX = getInflatedPYXAmount(_stakeSteps, _pyx);
totalForSalePYX = totalForSalePYX.add(inflatedPYX);
// [event]
emit AddInflatedForSalePYX(msg.sender, inflatedPYX, totalForSalePYX);
}
function getInflatedPYXAmount(uint256 _stakeSteps, uint256 _pyx)
public
view
returns (uint256)
{
return
(_pyx.mul(_stakeSteps).mul(SETTINGS.INFLATION_RATE)).div(
SETTINGS.INFLATION_RATE_DIVIDER.mul(100)
);
}
/** timestamp 0 = 00:00:00 UTC Thursday, 1 January 1970
* 0 - Thursday
* 1 - Friday
* 2 - Saturday
* 3 - Sunday
* 4 - Monday
* 5 - Tuesday
* 6 - Wednesday
*/
function getNumDayInWeek() public view returns (uint256) {
return (block.timestamp / SETTINGS.STEP_SECONDS) % 7;
}
function _buyBack(uint256 _eth, uint256 _pyxOutMin)
private
returns (uint256)
{
uint256 deadline = block.timestamp.add(uint256(60).mul(30)); // + 30 minutes
address[] memory path = new address[](2);
path[0] = ADDRESSES.UNISWAP.WETH();
path[1] = address(ADDRESSES.PYX_TOKEN);
ADDRESSES.UNISWAP.swapExactETHForTokens{value: _eth}(
_pyxOutMin,
path,
address(this),
deadline
);
uint256 buyBackPYX = ADDRESSES.PYX_TOKEN.getBalanceOf(address(this));
// pyx - burn
ADDRESSES.PYX_TOKEN.burn(address(this), buyBackPYX);
return buyBackPYX;
}
}