-
Notifications
You must be signed in to change notification settings - Fork 2
/
Collateral.sol
344 lines (303 loc) · 10.6 KB
/
Collateral.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
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
333
334
335
336
337
338
339
340
341
342
343
344
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.7;
import "./interfaces/ICollateral.sol";
import "./interfaces/IStrategyController.sol";
import "./interfaces/IHook.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
contract Collateral is
ICollateral,
ERC20Upgradeable,
OwnableUpgradeable,
ReentrancyGuardUpgradeable
{
using SafeERC20Upgradeable for IERC20Upgradeable;
bool private _depositsAllowed;
bool private _withdrawalsAllowed;
address private _treasury;
uint256 private _mintingFee;
uint256 private _redemptionFee;
IERC20Upgradeable private _baseToken;
IStrategyController private _strategyController;
uint256 private _delayedWithdrawalExpiry;
mapping(address => WithdrawalRequest) private _accountToWithdrawalRequest;
IHook private _depositHook;
IHook private _withdrawHook;
uint256 private constant FEE_DENOMINATOR = 1000000;
uint256 private constant FEE_LIMIT = 50000;
function initialize(address _newBaseToken, address _newTreasury)
public
initializer
{
__Ownable_init_unchained();
__ReentrancyGuard_init_unchained();
__ERC20_init_unchained(
string("prePO Collateral Token"),
string("preCT")
);
_baseToken = IERC20Upgradeable(_newBaseToken);
_treasury = _newTreasury;
}
function deposit(uint256 _amount)
external
override
nonReentrant
returns (uint256)
{
require(_depositsAllowed, "Deposits not allowed");
_baseToken.safeTransferFrom(msg.sender, address(this), _amount);
// Calculate fees and shares to mint including latent contract funds
uint256 _amountToDeposit = _baseToken.balanceOf(address(this));
// Record deposit before fee is taken
if (address(_depositHook) != address(0)) {
_depositHook.hook(msg.sender, _amount, _amountToDeposit);
}
/**
* Add 1 to avoid rounding to zero, only process deposit if user is
* depositing an amount large enough to pay a fee.
*/
uint256 _fee = (_amountToDeposit * _mintingFee) / FEE_DENOMINATOR + 1;
require(_amountToDeposit > _fee, "Deposit amount too small");
_baseToken.safeTransfer(_treasury, _fee);
_amountToDeposit -= _fee;
uint256 _valueBefore = _strategyController.totalValue();
_baseToken.approve(address(_strategyController), _amountToDeposit);
_strategyController.deposit(_amountToDeposit);
uint256 _valueAfter = _strategyController.totalValue();
_amountToDeposit = _valueAfter - _valueBefore;
uint256 _shares = 0;
if (totalSupply() == 0) {
_shares = _amountToDeposit;
} else {
/**
* # of shares owed = amount deposited / cost per share, cost per
* share = total supply / total value.
*/
_shares = (_amountToDeposit * totalSupply()) / (_valueBefore);
}
_mint(msg.sender, _shares);
return _shares;
}
function initiateWithdrawal(uint256 _amount) external override {
/**
* Checking the balance before initiation is necessary since a user
* could initiate an unlimited withdrawal amount ahead of time,
* negating the protection a delayed withdrawal offers.
*/
require(balanceOf(msg.sender) >= _amount, "Insufficient balance");
_accountToWithdrawalRequest[msg.sender].amount = _amount;
_accountToWithdrawalRequest[msg.sender].blockNumber = block.number;
}
function uninitiateWithdrawal() external override {
_accountToWithdrawalRequest[msg.sender].amount = 0;
_accountToWithdrawalRequest[msg.sender].blockNumber = 0;
}
function _processDelayedWithdrawal(address _account, uint256 _amount)
internal
{
/**
* Verify that the withdrawal being processed matches what was
* recorded during initiation.
*/
require(
_accountToWithdrawalRequest[_account].amount == _amount,
"Initiated amount does not match"
);
uint256 _recordedBlock = _accountToWithdrawalRequest[_account]
.blockNumber;
require(
_recordedBlock + _delayedWithdrawalExpiry >= block.number,
"Must withdraw before expiry"
);
require(
block.number > _recordedBlock,
"Must withdraw in a later block"
);
// Reset the initiation prior to withdrawal.
_accountToWithdrawalRequest[_account].amount = 0;
_accountToWithdrawalRequest[_account].blockNumber = 0;
}
function withdraw(uint256 _amount)
external
override
nonReentrant
returns (uint256)
{
require(_withdrawalsAllowed, "Withdrawals not allowed");
if (_delayedWithdrawalExpiry != 0) {
_processDelayedWithdrawal(msg.sender, _amount);
}
uint256 _owed = (_strategyController.totalValue() * _amount) /
totalSupply();
_burn(msg.sender, _amount);
uint256 _balanceBefore = _baseToken.balanceOf(address(this));
_strategyController.withdraw(address(this), _owed);
uint256 _balanceAfter = _baseToken.balanceOf(address(this));
uint256 _amountWithdrawn = _balanceAfter - _balanceBefore;
// Record withdrawal before fee is taken
if (address(_withdrawHook) != address(0)) {
_withdrawHook.hook(msg.sender, _amount, _amountWithdrawn);
}
/**
* Send redemption fee to the protocol treasury. Add 1 to avoid
* rounding to zero, only process withdrawal if user is
* withdrawing an amount large enough to pay a fee.
*/
uint256 _fee = (_amountWithdrawn * _redemptionFee) /
FEE_DENOMINATOR +
1;
require(_amountWithdrawn > _fee, "Withdrawal amount too small");
_baseToken.safeTransfer(_treasury, _fee);
_amountWithdrawn -= _fee;
_baseToken.safeTransfer(msg.sender, _amountWithdrawn);
return _amountWithdrawn;
}
function setDepositsAllowed(bool _allowed) external override onlyOwner {
_depositsAllowed = _allowed;
emit DepositsAllowedChanged(_allowed);
}
function setWithdrawalsAllowed(bool _allowed) external override onlyOwner {
_withdrawalsAllowed = _allowed;
emit WithdrawalsAllowedChanged(_allowed);
}
function setStrategyController(IStrategyController _newStrategyController)
external
override
onlyOwner
{
_strategyController = _newStrategyController;
emit StrategyControllerChanged(address(_strategyController));
}
function setDelayedWithdrawalExpiry(uint256 _newDelayedWithdrawalExpiry)
external
override
onlyOwner
{
_delayedWithdrawalExpiry = _newDelayedWithdrawalExpiry;
emit DelayedWithdrawalExpiryChanged(_delayedWithdrawalExpiry);
}
function setMintingFee(uint256 _newMintingFee)
external
override
onlyOwner
{
require(_newMintingFee <= FEE_LIMIT, "Exceeds fee limit");
_mintingFee = _newMintingFee;
emit MintingFeeChanged(_mintingFee);
}
function setRedemptionFee(uint256 _newRedemptionFee)
external
override
onlyOwner
{
require(_newRedemptionFee <= FEE_LIMIT, "Exceeds fee limit");
_redemptionFee = _newRedemptionFee;
emit RedemptionFeeChanged(_redemptionFee);
}
function setDepositHook(IHook _newDepositHook)
external
override
onlyOwner
{
_depositHook = _newDepositHook;
emit DepositHookChanged(address(_depositHook));
}
function setWithdrawHook(IHook _newWithdrawHook)
external
override
onlyOwner
{
_withdrawHook = _newWithdrawHook;
emit WithdrawHookChanged(address(_withdrawHook));
}
function getDepositsAllowed() external view override returns (bool) {
return _depositsAllowed;
}
function getWithdrawalsAllowed() external view override returns (bool) {
return _withdrawalsAllowed;
}
function getTreasury() external view override returns (address) {
return _treasury;
}
function getMintingFee() external view override returns (uint256) {
return _mintingFee;
}
function getRedemptionFee() external view override returns (uint256) {
return _redemptionFee;
}
function getBaseToken()
external
view
override
returns (IERC20Upgradeable)
{
return _baseToken;
}
function getStrategyController()
external
view
override
returns (IStrategyController)
{
return _strategyController;
}
function getDelayedWithdrawalExpiry()
external
view
override
returns (uint256)
{
return _delayedWithdrawalExpiry;
}
function getWithdrawalRequest(address _account)
external
view
override
returns (WithdrawalRequest memory)
{
return _accountToWithdrawalRequest[_account];
}
function getDepositHook() external view override returns (IHook) {
return _depositHook;
}
function getWithdrawHook() external view override returns (IHook) {
return _withdrawHook;
}
function getAmountForShares(uint256 _shares)
external
view
override
returns (uint256)
{
if (totalSupply() == 0) {
return _shares;
}
return (_shares * totalAssets()) / totalSupply();
}
function getSharesForAmount(uint256 _amount)
external
view
override
returns (uint256)
{
uint256 _totalAssets = totalAssets();
return
(_totalAssets > 0)
? ((_amount * totalSupply()) / _totalAssets)
: 0;
}
function getFeeDenominator() external pure override returns (uint256) {
return FEE_DENOMINATOR;
}
function getFeeLimit() external pure override returns (uint256) {
return FEE_LIMIT;
}
function totalAssets() public view override returns (uint256) {
return
_baseToken.balanceOf(address(this)) +
_strategyController.totalValue();
}
}