/
ThrottledExit.sol
109 lines (82 loc) · 3.77 KB
/
ThrottledExit.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
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "./interfaces/IERC20Detailed.sol";
import "./SafeERC20Detailed.sol";
abstract contract ThrottledExit {
using SafeMath for uint256;
using SafeERC20Detailed for IERC20Detailed;
uint256 public nextAvailableExitBlock;
uint256 public nextAvailableRoundExitVolume;
uint256 public throttleRoundBlocks;
uint256 public throttleRoundCap;
struct ExitInfo {
uint256 exitBlock;
uint256 exitStake;
uint256[] rewards;
}
mapping(address => ExitInfo) public exitInfo;
event ExitRequested(address user, uint256 exitBlock);
event ExitCompleted(address user, uint256 stake);
function setThrottleParams(uint256 _throttleRoundBlocks, uint256 _throttleRoundCap, uint256 throttleStart) internal {
require(_throttleRoundBlocks > 0, "setThrottle::throttle round blocks must be more than 0");
require(_throttleRoundCap > 0, "setThrottle::throttle round cap must be more than 0");
require(throttleRoundBlocks == 0 && throttleRoundCap == 0, "setThrottle::throttle parameters were already set");
throttleRoundBlocks = _throttleRoundBlocks;
throttleRoundCap = _throttleRoundCap;
nextAvailableExitBlock = throttleStart.add(throttleRoundBlocks);
}
function initiateExit(uint256 amountStaked, address[] memory _rewardsTokens, uint256[] memory _tokensOwed) virtual internal {
initialiseExitInfo(msg.sender, _rewardsTokens.length);
ExitInfo storage info = exitInfo[msg.sender];
info.exitBlock = getAvailableExitTime(amountStaked);
info.exitStake = info.exitStake.add(amountStaked);
for (uint256 i = 0; i < _rewardsTokens.length; i++) {
info.rewards[i] = info.rewards[i].add(_tokensOwed[i]);
}
emit ExitRequested(msg.sender, info.exitBlock);
}
function finalizeExit(address _stakingToken, address[] memory _rewardsTokens) virtual internal {
ExitInfo storage info = exitInfo[msg.sender];
require(block.number > info.exitBlock, "finalizeExit::Trying to exit too early");
IERC20Detailed(_stakingToken).safeTransfer(address(msg.sender), info.exitStake);
for (uint256 i = 0; i < _rewardsTokens.length; i++) {
IERC20Detailed(_rewardsTokens[i]).safeTransfer(msg.sender, info.rewards[i]);
info.rewards[i] = 0;
}
emit ExitCompleted(msg.sender, info.exitStake);
info.exitStake = 0;
}
function getAvailableExitTime(uint256 exitAmount) internal returns(uint256 exitBlock) {
if(block.number > nextAvailableExitBlock) { // We've passed the next available block and need to readjust
uint i = nextAvailableExitBlock; // Using i instead of nextAvailableExitBlock to avoid SSTORE
while(i < block.number) { // Find the next future round
i = i.add(throttleRoundBlocks);
}
nextAvailableExitBlock = i;
nextAvailableRoundExitVolume = exitAmount; // Reset volume
return i;
} else { // We are still before the next available block
nextAvailableRoundExitVolume = nextAvailableRoundExitVolume.add(exitAmount); // Add volume
}
exitBlock = nextAvailableExitBlock;
if (nextAvailableRoundExitVolume >= throttleRoundCap) { // If cap reached
nextAvailableExitBlock = nextAvailableExitBlock.add(throttleRoundBlocks); // update next exit block
nextAvailableRoundExitVolume = 0; // Reset volume
}
}
function getPendingReward(uint256 tokenIndex) public view returns(uint256) {
ExitInfo storage info = exitInfo[msg.sender];
return info.rewards[tokenIndex];
}
function initialiseExitInfo(address _userAddress, uint256 tokensLength) private {
ExitInfo storage info = exitInfo[_userAddress];
if (info.rewards.length == tokensLength) {
// Already initialised
return;
}
for (uint256 i = info.rewards.length; i < tokensLength; i++) {
info.rewards.push(0);
}
}
}