-
Notifications
You must be signed in to change notification settings - Fork 10
/
ExitQueue.sol
172 lines (131 loc) · 5.83 KB
/
ExitQueue.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
pragma solidity ^0.8.4;
// SPDX-License-Identifier: GPL-3.0-or-later
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// import "hardhat/console.sol";
/**
* How all exit of TEMPLE rewards are managed.
*/
contract ExitQueue is Ownable {
struct User {
// Total currently in queue
uint256 Amount;
// First epoch for which the user is in the unstake queue
uint256 FirstExitEpoch;
// Last epoch for which the user has a pending unstake
uint256 LastExitEpoch;
// All epochs where the user has an exit allocation
mapping(uint256 => uint256) Exits;
}
// total queued to be exited in a given epoch
mapping(uint256 => uint256) public totalPerEpoch;
// The first unwithdrawn epoch for the user
mapping(address => User) public userData;
IERC20 public TEMPLE; // TEMPLE
// Limit of how much temple can exit per epoch
uint256 public maxPerEpoch;
// Limit of how much temple can exit per address per epoch
uint256 public maxPerAddress;
// epoch size, in blocks
uint256 public epochSize;
// the block we use to work out what epoch we are in
uint256 public firstBlock;
// The next free block on which a user can commence their unstake
uint256 public nextUnallocatedEpoch;
event JoinQueue(address exiter, uint256 amount);
event Withdrawal(address exiter, uint256 amount);
constructor(
address _TEMPLE,
uint256 _maxPerEpoch,
uint256 _maxPerAddress,
uint256 _epochSize) {
TEMPLE = IERC20(_TEMPLE);
maxPerEpoch = _maxPerEpoch;
maxPerAddress = _maxPerAddress;
epochSize = _epochSize;
firstBlock = block.number;
nextUnallocatedEpoch = 0;
}
function setMaxPerEpoch(uint256 _maxPerEpoch) external onlyOwner {
maxPerEpoch = _maxPerEpoch;
}
function setMaxPerAddress(uint256 _maxPerAddress) external onlyOwner {
maxPerAddress = _maxPerAddress;
}
function setEpochSize(uint256 _epochSize) external onlyOwner {
epochSize = _epochSize;
}
function setStartingBlock(uint256 _firstBlock) external onlyOwner {
require(_firstBlock < firstBlock, "Can only move start block back, not forward");
firstBlock = _firstBlock;
}
function currentEpoch() public view returns (uint256) {
return (block.number - firstBlock) / epochSize;
}
function currentEpochAllocation(address _exiter, uint256 _epoch) external view returns (uint256) {
return userData[_exiter].Exits[_epoch];
}
function join(address _exiter, uint256 _amount) external {
require(_amount > 0, "Amount must be > 0");
if (nextUnallocatedEpoch < currentEpoch()) {
nextUnallocatedEpoch = currentEpoch();
}
User storage user = userData[_exiter];
uint256 unallocatedAmount = _amount;
uint256 _nextUnallocatedEpoch = nextUnallocatedEpoch;
uint256 nextAvailableEpochForUser = _nextUnallocatedEpoch;
if (user.LastExitEpoch > nextAvailableEpochForUser) {
nextAvailableEpochForUser = user.LastExitEpoch;
}
while (unallocatedAmount > 0) {
// work out allocation for the next available epoch
uint256 allocationForEpoch = unallocatedAmount;
if (user.Exits[nextAvailableEpochForUser] + allocationForEpoch > maxPerAddress) {
allocationForEpoch = maxPerAddress - user.Exits[nextAvailableEpochForUser];
}
if (totalPerEpoch[nextAvailableEpochForUser] + allocationForEpoch > maxPerEpoch) {
allocationForEpoch = maxPerEpoch - totalPerEpoch[nextAvailableEpochForUser];
}
// Bookkeeping
if (allocationForEpoch > 0) {
if (user.Amount == 0) {
user.FirstExitEpoch = nextAvailableEpochForUser;
}
user.Amount += allocationForEpoch;
user.Exits[nextAvailableEpochForUser] += allocationForEpoch;
totalPerEpoch[nextAvailableEpochForUser] += allocationForEpoch;
user.LastExitEpoch = nextAvailableEpochForUser;
if (totalPerEpoch[nextAvailableEpochForUser] >= maxPerEpoch) {
_nextUnallocatedEpoch = nextAvailableEpochForUser;
}
unallocatedAmount -= allocationForEpoch;
}
nextAvailableEpochForUser += 1;
}
// update outside of main loop, so we spend gas once
nextUnallocatedEpoch = _nextUnallocatedEpoch;
SafeERC20.safeTransferFrom(TEMPLE, msg.sender, address(this), _amount);
emit JoinQueue(_exiter, _amount);
}
/**
* Withdraw processed allowance from a specific epoch
*/
function withdraw(uint256 epoch) external {
require(epoch < currentEpoch(), "Can only withdraw from past epochs");
User storage user = userData[msg.sender];
uint256 amount = user.Exits[epoch];
delete user.Exits[epoch];
totalPerEpoch[epoch] -= amount; // TODO: WHen this goes to 0, is it the same as the data being removed?
user.Amount -= amount;
// Once all allocations on queue have been claimed, reset user state
if (user.Amount == 0) {
// NOTE: triggers ExitQueue.withdraw(uint256) (contracts/ExitQueue.sol #150-167) deletes ExitQueue.User (contracts/ExitQueue.sol#15-27) which contains a mapping
// This is okay as if Amount is 0, we'd expect user.Exits to be empty as well
// TODO: Confirm this via tests
delete userData[msg.sender];
}
SafeERC20.safeTransfer(TEMPLE, msg.sender, amount);
emit Withdrawal(msg.sender, amount);
}
}