-
Notifications
You must be signed in to change notification settings - Fork 201
/
FraxPoolV3.sol
executable file
·699 lines (563 loc) · 30.7 KB
/
FraxPoolV3.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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.0;
// ====================================================================
// | ______ _______ |
// | / _____________ __ __ / ____(_____ ____ _____ ________ |
// | / /_ / ___/ __ `| |/_/ / /_ / / __ \/ __ `/ __ \/ ___/ _ \ |
// | / __/ / / / /_/ _> < / __/ / / / / / /_/ / / / / /__/ __/ |
// | /_/ /_/ \__,_/_/|_| /_/ /_/_/ /_/\__,_/_/ /_/\___/\___/ |
// | |
// ====================================================================
// ============================ FraxPoolV3 ============================
// ====================================================================
// Allows multiple stablecoins (fixed amount at initialization) as collateral
// LUSD, sUSD, USDP, Wrapped UST, and FEI initially
// For this pool, the goal is to accept crypto-backed / overcollateralized stablecoins to limit
// government / regulatory risk (e.g. USDC blacklisting until holders KYC)
// Frax Finance: https://github.com/FraxFinance
// Primary Author(s)
// Travis Moore: https://github.com/FortisFortuna
// Reviewer(s) / Contributor(s)
// Jason Huan: https://github.com/jasonhuan
// Sam Kazemian: https://github.com/samkazemian
// Dennis: github.com/denett
// Hameed
import "../../Math/SafeMath.sol";
import '../../Uniswap/TransferHelper.sol';
import "../../Staking/Owned.sol";
import "../../FXS/IFxs.sol";
import "../../Frax/IFrax.sol";
import "../../Oracle/AggregatorV3Interface.sol";
import "../../Frax/IFraxAMOMinter.sol";
import "../../ERC20/ERC20.sol";
contract FraxPoolV3 is Owned {
using SafeMath for uint256;
// SafeMath automatically included in Solidity >= 8.0.0
/* ========== STATE VARIABLES ========== */
// Core
address public timelock_address;
address public custodian_address; // Custodian is an EOA (or msig) with pausing privileges only, in case of an emergency
IFrax private FRAX = IFrax(0x853d955aCEf822Db058eb8505911ED77F175b99e);
IFxs private FXS = IFxs(0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0);
mapping(address => bool) public amo_minter_addresses; // minter address -> is it enabled
AggregatorV3Interface public priceFeedFRAXUSD = AggregatorV3Interface(0xB9E1E3A9feFf48998E45Fa90847ed4D467E8BcfD);
AggregatorV3Interface public priceFeedFXSUSD = AggregatorV3Interface(0x6Ebc52C8C1089be9eB3945C4350B68B8E4C2233f);
uint256 private chainlink_frax_usd_decimals;
uint256 private chainlink_fxs_usd_decimals;
// Collateral
address[] public collateral_addresses;
string[] public collateral_symbols;
uint256[] public missing_decimals; // Number of decimals needed to get to E18. collateral index -> missing_decimals
uint256[] public pool_ceilings; // Total across all collaterals. Accounts for missing_decimals
uint256[] public collateral_prices; // Stores price of the collateral, if price is paused. CONSIDER ORACLES EVENTUALLY!!!
mapping(address => uint256) public collateralAddrToIdx; // collateral addr -> collateral index
mapping(address => bool) public enabled_collaterals; // collateral address -> is it enabled
// Redeem related
mapping (address => uint256) public redeemFXSBalances;
mapping (address => mapping(uint256 => uint256)) public redeemCollateralBalances; // Address -> collateral index -> balance
uint256[] public unclaimedPoolCollateral; // collateral index -> balance
uint256 public unclaimedPoolFXS;
mapping (address => uint256) public lastRedeemed; // Collateral independent
uint256 public redemption_delay = 2; // Number of blocks to wait before being able to collectRedemption()
uint256 public redeem_price_threshold = 990000; // $0.99
uint256 public mint_price_threshold = 1010000; // $1.01
// Buyback related
mapping(uint256 => uint256) public bbkHourlyCum; // Epoch hour -> Collat out in that hour (E18)
uint256 public bbkMaxColE18OutPerHour = 1000e18;
// Recollat related
mapping(uint256 => uint256) public rctHourlyCum; // Epoch hour -> FXS out in that hour
uint256 public rctMaxFxsOutPerHour = 1000e18;
// Fees and rates
// getters are in collateral_information()
uint256[] private minting_fee;
uint256[] private redemption_fee;
uint256[] private buyback_fee;
uint256[] private recollat_fee;
uint256 public bonus_rate; // Bonus rate on FXS minted during recollateralize(); 6 decimals of precision, set to 0.75% on genesis
// Constants for various precisions
uint256 private constant PRICE_PRECISION = 1e6;
// Pause variables
// getters are in collateral_information()
bool[] private mintPaused; // Collateral-specific
bool[] private redeemPaused; // Collateral-specific
bool[] private recollateralizePaused; // Collateral-specific
bool[] private buyBackPaused; // Collateral-specific
bool[] private borrowingPaused; // Collateral-specific
/* ========== MODIFIERS ========== */
modifier onlyByOwnGov() {
require(msg.sender == timelock_address || msg.sender == owner, "Not owner or timelock");
_;
}
modifier onlyByOwnGovCust() {
require(msg.sender == timelock_address || msg.sender == owner || msg.sender == custodian_address, "Not owner, tlck, or custd");
_;
}
modifier onlyAMOMinters() {
require(amo_minter_addresses[msg.sender], "Not an AMO Minter");
_;
}
modifier collateralEnabled(uint256 col_idx) {
require(enabled_collaterals[collateral_addresses[col_idx]], "Collateral disabled");
_;
}
/* ========== CONSTRUCTOR ========== */
constructor (
address _pool_manager_address,
address _custodian_address,
address _timelock_address,
address[] memory _collateral_addresses,
uint256[] memory _pool_ceilings,
uint256[] memory _initial_fees
) Owned(_pool_manager_address){
// Core
timelock_address = _timelock_address;
custodian_address = _custodian_address;
// Fill collateral info
collateral_addresses = _collateral_addresses;
for (uint256 i = 0; i < _collateral_addresses.length; i++){
// For fast collateral address -> collateral idx lookups later
collateralAddrToIdx[_collateral_addresses[i]] = i;
// Set all of the collaterals initially to disabled
enabled_collaterals[_collateral_addresses[i]] = false;
// Add in the missing decimals
missing_decimals.push(uint256(18).sub(ERC20(_collateral_addresses[i]).decimals()));
// Add in the collateral symbols
collateral_symbols.push(ERC20(_collateral_addresses[i]).symbol());
// Initialize unclaimed pool collateral
unclaimedPoolCollateral.push(0);
// Initialize paused prices to $1 as a backup
collateral_prices.push(PRICE_PRECISION);
// Handle the fees
minting_fee.push(_initial_fees[0]);
redemption_fee.push(_initial_fees[1]);
buyback_fee.push(_initial_fees[2]);
recollat_fee.push(_initial_fees[3]);
// Handle the pauses
mintPaused.push(false);
redeemPaused.push(false);
recollateralizePaused.push(false);
buyBackPaused.push(false);
borrowingPaused.push(false);
}
// Pool ceiling
pool_ceilings = _pool_ceilings;
// Set the decimals
chainlink_frax_usd_decimals = priceFeedFRAXUSD.decimals();
chainlink_fxs_usd_decimals = priceFeedFXSUSD.decimals();
}
/* ========== STRUCTS ========== */
struct CollateralInformation {
uint256 index;
string symbol;
address col_addr;
bool is_enabled;
uint256 missing_decs;
uint256 price;
uint256 pool_ceiling;
bool mint_paused;
bool redeem_paused;
bool recollat_paused;
bool buyback_paused;
bool borrowing_paused;
uint256 minting_fee;
uint256 redemption_fee;
uint256 buyback_fee;
uint256 recollat_fee;
}
/* ========== VIEWS ========== */
// Helpful for UIs
function collateral_information(address collat_address) external view returns (CollateralInformation memory return_data){
require(enabled_collaterals[collat_address], "Invalid collateral");
// Get the index
uint256 idx = collateralAddrToIdx[collat_address];
return_data = CollateralInformation(
idx, // [0]
collateral_symbols[idx], // [1]
collat_address, // [2]
enabled_collaterals[collat_address], // [3]
missing_decimals[idx], // [4]
collateral_prices[idx], // [5]
pool_ceilings[idx], // [6]
mintPaused[idx], // [7]
redeemPaused[idx], // [8]
recollateralizePaused[idx], // [9]
buyBackPaused[idx], // [10]
borrowingPaused[idx], // [11]
minting_fee[idx], // [12]
redemption_fee[idx], // [13]
buyback_fee[idx], // [14]
recollat_fee[idx] // [15]
);
}
function allCollaterals() external view returns (address[] memory) {
return collateral_addresses;
}
function getFRAXPrice() public view returns (uint256) {
(uint80 roundID, int price, , uint256 updatedAt, uint80 answeredInRound) = priceFeedFRAXUSD.latestRoundData();
require(price >= 0 && updatedAt!= 0 && answeredInRound >= roundID, "Invalid chainlink price");
return uint256(price).mul(PRICE_PRECISION).div(10 ** chainlink_frax_usd_decimals);
}
function getFXSPrice() public view returns (uint256) {
(uint80 roundID, int price, , uint256 updatedAt, uint80 answeredInRound) = priceFeedFXSUSD.latestRoundData();
require(price >= 0 && updatedAt!= 0 && answeredInRound >= roundID, "Invalid chainlink price");
return uint256(price).mul(PRICE_PRECISION).div(10 ** chainlink_fxs_usd_decimals);
}
// Returns the FRAX value in collateral tokens
function getFRAXInCollateral(uint256 col_idx, uint256 frax_amount) public view returns (uint256) {
return frax_amount.mul(PRICE_PRECISION).div(10 ** missing_decimals[col_idx]).div(collateral_prices[col_idx]);
}
// Used by some functions.
function freeCollatBalance(uint256 col_idx) public view returns (uint256) {
return ERC20(collateral_addresses[col_idx]).balanceOf(address(this)).sub(unclaimedPoolCollateral[col_idx]);
}
// Returns dollar value of collateral held in this Frax pool, in E18
function collatDollarBalance() external view returns (uint256 balance_tally) {
balance_tally = 0;
// Test 1
for (uint256 i = 0; i < collateral_addresses.length; i++){
balance_tally += freeCollatBalance(i).mul(10 ** missing_decimals[i]).mul(collateral_prices[i]).div(PRICE_PRECISION);
}
}
function comboCalcBbkRct(uint256 cur, uint256 max, uint256 theo) internal pure returns (uint256) {
if (cur >= max) {
// If the hourly limit has already been reached, return 0;
return 0;
}
else {
// Get the available amount
uint256 available = max.sub(cur);
if (theo >= available) {
// If the the theoretical is more than the available, return the available
return available;
}
else {
// Otherwise, return the theoretical amount
return theo;
}
}
}
// Returns the value of excess collateral (in E18) held globally, compared to what is needed to maintain the global collateral ratio
// Also has throttling to avoid dumps during large price movements
function buybackAvailableCollat() public view returns (uint256) {
uint256 total_supply = FRAX.totalSupply();
uint256 global_collateral_ratio = FRAX.global_collateral_ratio();
uint256 global_collat_value = FRAX.globalCollateralValue();
if (global_collateral_ratio > PRICE_PRECISION) global_collateral_ratio = PRICE_PRECISION; // Handles an overcollateralized contract with CR > 1
uint256 required_collat_dollar_value_d18 = (total_supply.mul(global_collateral_ratio)).div(PRICE_PRECISION); // Calculates collateral needed to back each 1 FRAX with $1 of collateral at current collat ratio
if (global_collat_value > required_collat_dollar_value_d18) {
// Get the theoretical buyback amount
uint256 theoretical_bbk_amt = global_collat_value.sub(required_collat_dollar_value_d18);
// See how much has collateral has been issued this hour
uint256 current_hr_bbk = bbkHourlyCum[curEpochHr()];
// Account for the throttling
return comboCalcBbkRct(current_hr_bbk, bbkMaxColE18OutPerHour, theoretical_bbk_amt);
}
else return 0;
}
// Returns the missing amount of collateral (in E18) needed to maintain the collateral ratio
function recollatTheoColAvailableE18() public view returns (uint256) {
uint256 frax_total_supply = FRAX.totalSupply();
uint256 effective_collateral_ratio = FRAX.globalCollateralValue().mul(PRICE_PRECISION).div(frax_total_supply); // Returns it in 1e6
uint256 desired_collat_e24 = (FRAX.global_collateral_ratio()).mul(frax_total_supply);
uint256 effective_collat_e24 = effective_collateral_ratio.mul(frax_total_supply);
// Return 0 if already overcollateralized
// Otherwise, return the deficiency
if (effective_collat_e24 >= desired_collat_e24) return 0;
else {
return (desired_collat_e24.sub(effective_collat_e24)).div(PRICE_PRECISION);
}
}
// Returns the value of FXS available to be used for recollats
// Also has throttling to avoid dumps during large price movements
function recollatAvailableFxs() public view returns (uint256) {
uint256 fxs_price = getFXSPrice();
// Get the amount of collateral theoretically available
uint256 recollat_theo_available_e18 = recollatTheoColAvailableE18();
// Get the amount of FXS theoretically outputtable
uint256 fxs_theo_out = recollat_theo_available_e18.mul(PRICE_PRECISION).div(fxs_price);
// See how much FXS has been issued this hour
uint256 current_hr_rct = rctHourlyCum[curEpochHr()];
// Account for the throttling
return comboCalcBbkRct(current_hr_rct, rctMaxFxsOutPerHour, fxs_theo_out);
}
// Returns the current epoch hour
function curEpochHr() public view returns (uint256) {
return (block.timestamp / 3600); // Truncation desired
}
/* ========== PUBLIC FUNCTIONS ========== */
function mintFrax(
uint256 col_idx,
uint256 frax_amt,
uint256 frax_out_min,
uint256 max_collat_in,
uint256 max_fxs_in,
bool one_to_one_override
) external collateralEnabled(col_idx) returns (
uint256 total_frax_mint,
uint256 collat_needed,
uint256 fxs_needed
) {
require(mintPaused[col_idx] == false, "Minting is paused");
// Prevent unneccessary mints
require(getFRAXPrice() >= mint_price_threshold, "Frax price too low");
uint256 global_collateral_ratio = FRAX.global_collateral_ratio();
if (one_to_one_override || global_collateral_ratio >= PRICE_PRECISION) {
// 1-to-1, overcollateralized, or user selects override
collat_needed = getFRAXInCollateral(col_idx, frax_amt);
fxs_needed = 0;
} else if (global_collateral_ratio == 0) {
// Algorithmic
collat_needed = 0;
fxs_needed = frax_amt.mul(PRICE_PRECISION).div(getFXSPrice());
} else {
// Fractional
uint256 frax_for_collat = frax_amt.mul(global_collateral_ratio).div(PRICE_PRECISION);
uint256 frax_for_fxs = frax_amt.sub(frax_for_collat);
collat_needed = getFRAXInCollateral(col_idx, frax_for_collat);
fxs_needed = frax_for_fxs.mul(PRICE_PRECISION).div(getFXSPrice());
}
// Subtract the minting fee
total_frax_mint = (frax_amt.mul(PRICE_PRECISION.sub(minting_fee[col_idx]))).div(PRICE_PRECISION);
// Check slippages
require((total_frax_mint >= frax_out_min), "FRAX slippage");
require((collat_needed <= max_collat_in), "Collat slippage");
require((fxs_needed <= max_fxs_in), "FXS slippage");
// Check the pool ceiling
require(freeCollatBalance(col_idx).add(collat_needed) <= pool_ceilings[col_idx], "Pool ceiling");
// Take the FXS and collateral first
FXS.pool_burn_from(msg.sender, fxs_needed);
TransferHelper.safeTransferFrom(collateral_addresses[col_idx], msg.sender, address(this), collat_needed);
// Mint the FRAX
FRAX.pool_mint(msg.sender, total_frax_mint);
}
function redeemFrax(
uint256 col_idx,
uint256 frax_amount,
uint256 fxs_out_min,
uint256 col_out_min
) external collateralEnabled(col_idx) returns (
uint256 collat_out,
uint256 fxs_out
) {
require(redeemPaused[col_idx] == false, "Redeeming is paused");
// Prevent unnecessary redemptions that could adversely affect the FXS price
require(getFRAXPrice() <= redeem_price_threshold, "Frax price too high");
uint256 global_collateral_ratio = FRAX.global_collateral_ratio();
uint256 frax_after_fee = (frax_amount.mul(PRICE_PRECISION.sub(redemption_fee[col_idx]))).div(PRICE_PRECISION);
// Assumes $1 FRAX in all cases
if(global_collateral_ratio >= PRICE_PRECISION) {
// 1-to-1 or overcollateralized
collat_out = getFRAXInCollateral(col_idx, frax_after_fee);
fxs_out = 0;
} else if (global_collateral_ratio == 0) {
// Algorithmic
fxs_out = frax_after_fee
.mul(PRICE_PRECISION)
.div(getFXSPrice());
collat_out = 0;
} else {
// Fractional
collat_out = getFRAXInCollateral(col_idx, frax_after_fee)
.mul(global_collateral_ratio)
.div(PRICE_PRECISION);
fxs_out = frax_after_fee
.mul(PRICE_PRECISION.sub(global_collateral_ratio))
.div(getFXSPrice()); // PRICE_PRECISIONS CANCEL OUT
}
// Checks
require(collat_out <= (ERC20(collateral_addresses[col_idx])).balanceOf(address(this)).sub(unclaimedPoolCollateral[col_idx]), "Insufficient pool collateral");
require(collat_out >= col_out_min, "Collateral slippage");
require(fxs_out >= fxs_out_min, "FXS slippage");
// Account for the redeem delay
redeemCollateralBalances[msg.sender][col_idx] = redeemCollateralBalances[msg.sender][col_idx].add(collat_out);
unclaimedPoolCollateral[col_idx] = unclaimedPoolCollateral[col_idx].add(collat_out);
redeemFXSBalances[msg.sender] = redeemFXSBalances[msg.sender].add(fxs_out);
unclaimedPoolFXS = unclaimedPoolFXS.add(fxs_out);
lastRedeemed[msg.sender] = block.number;
FRAX.pool_burn_from(msg.sender, frax_amount);
FXS.pool_mint(address(this), fxs_out);
}
// After a redemption happens, transfer the newly minted FXS and owed collateral from this pool
// contract to the user. Redemption is split into two functions to prevent flash loans from being able
// to take out FRAX/collateral from the system, use an AMM to trade the new price, and then mint back into the system.
function collectRedemption(uint256 col_idx) external returns (uint256 fxs_amount, uint256 collateral_amount) {
require(redeemPaused[col_idx] == false, "Redeeming is paused");
require((lastRedeemed[msg.sender].add(redemption_delay)) <= block.number, "Too soon");
bool sendFXS = false;
bool sendCollateral = false;
// Use Checks-Effects-Interactions pattern
if(redeemFXSBalances[msg.sender] > 0){
fxs_amount = redeemFXSBalances[msg.sender];
redeemFXSBalances[msg.sender] = 0;
unclaimedPoolFXS = unclaimedPoolFXS.sub(fxs_amount);
sendFXS = true;
}
if(redeemCollateralBalances[msg.sender][col_idx] > 0){
collateral_amount = redeemCollateralBalances[msg.sender][col_idx];
redeemCollateralBalances[msg.sender][col_idx] = 0;
unclaimedPoolCollateral[col_idx] = unclaimedPoolCollateral[col_idx].sub(collateral_amount);
sendCollateral = true;
}
// Send out the tokens
if(sendFXS){
TransferHelper.safeTransfer(address(FXS), msg.sender, fxs_amount);
}
if(sendCollateral){
TransferHelper.safeTransfer(collateral_addresses[col_idx], msg.sender, collateral_amount);
}
}
// Function can be called by an FXS holder to have the protocol buy back FXS with excess collateral value from a desired collateral pool
// This can also happen if the collateral ratio > 1
function buyBackFxs(uint256 col_idx, uint256 fxs_amount, uint256 col_out_min) external collateralEnabled(col_idx) returns (uint256 col_out) {
require(buyBackPaused[col_idx] == false, "Buyback is paused");
uint256 fxs_price = getFXSPrice();
uint256 available_excess_collat_dv = buybackAvailableCollat();
// If the total collateral value is higher than the amount required at the current collateral ratio then buy back up to the possible FXS with the desired collateral
require(available_excess_collat_dv > 0, "Insuf Collat Avail For BBK");
// Make sure not to take more than is available
uint256 fxs_dollar_value_d18 = fxs_amount.mul(fxs_price).div(PRICE_PRECISION);
require(fxs_dollar_value_d18 <= available_excess_collat_dv, "Insuf Collat Avail For BBK");
// Get the equivalent amount of collateral based on the market value of FXS provided
uint256 collateral_equivalent_d18 = fxs_dollar_value_d18.mul(PRICE_PRECISION).div(collateral_prices[col_idx]);
col_out = collateral_equivalent_d18.div(10 ** missing_decimals[col_idx]); // In its natural decimals()
// Subtract the buyback fee
col_out = (col_out.mul(PRICE_PRECISION.sub(buyback_fee[col_idx]))).div(PRICE_PRECISION);
// Check for slippage
require(col_out >= col_out_min, "Collateral slippage");
// Take in and burn the FXS, then send out the collateral
FXS.pool_burn_from(msg.sender, fxs_amount);
TransferHelper.safeTransfer(collateral_addresses[col_idx], msg.sender, col_out);
// Increment the outbound collateral, in E18, for that hour
// Used for buyback throttling
bbkHourlyCum[curEpochHr()] += collateral_equivalent_d18;
}
// When the protocol is recollateralizing, we need to give a discount of FXS to hit the new CR target
// Thus, if the target collateral ratio is higher than the actual value of collateral, minters get FXS for adding collateral
// This function simply rewards anyone that sends collateral to a pool with the same amount of FXS + the bonus rate
// Anyone can call this function to recollateralize the protocol and take the extra FXS value from the bonus rate as an arb opportunity
function recollateralize(uint256 col_idx, uint256 collateral_amount, uint256 fxs_out_min) external collateralEnabled(col_idx) returns (uint256 fxs_out) {
require(recollateralizePaused[col_idx] == false, "Recollat is paused");
uint256 collateral_amount_d18 = collateral_amount * (10 ** missing_decimals[col_idx]);
uint256 fxs_price = getFXSPrice();
// Get the amount of FXS actually available (accounts for throttling)
uint256 fxs_actually_available = recollatAvailableFxs();
// Calculated the attempted amount of FXS
fxs_out = collateral_amount_d18.mul(PRICE_PRECISION.add(bonus_rate).sub(recollat_fee[col_idx])).div(fxs_price);
// Make sure there is FXS available
require(fxs_out <= fxs_actually_available, "Insuf FXS Avail For RCT");
// Check slippage
require(fxs_out >= fxs_out_min, "FXS slippage");
// Don't take in more collateral than the pool ceiling for this token allows
require(freeCollatBalance(col_idx).add(collateral_amount) <= pool_ceilings[col_idx], "Pool ceiling");
// Take in the collateral and pay out the FXS
TransferHelper.safeTransferFrom(collateral_addresses[col_idx], msg.sender, address(this), collateral_amount);
FXS.pool_mint(msg.sender, fxs_out);
// Increment the outbound FXS, in E18
// Used for recollat throttling
rctHourlyCum[curEpochHr()] += fxs_out;
}
// Bypasses the gassy mint->redeem cycle for AMOs to borrow collateral
function amoMinterBorrow(uint256 collateral_amount) external onlyAMOMinters {
// Checks the col_idx of the minter as an additional safety check
uint256 minter_col_idx = IFraxAMOMinter(msg.sender).col_idx();
// Checks to see if borrowing is paused
require(borrowingPaused[minter_col_idx] == false, "Borrowing is paused");
// Ensure collateral is enabled
require(enabled_collaterals[collateral_addresses[minter_col_idx]], "Collateral disabled");
// Transfer
TransferHelper.safeTransfer(collateral_addresses[minter_col_idx], msg.sender, collateral_amount);
}
/* ========== RESTRICTED FUNCTIONS, CUSTODIAN CAN CALL TOO ========== */
function toggleMRBR(uint256 col_idx, uint8 tog_idx) external onlyByOwnGovCust {
if (tog_idx == 0) mintPaused[col_idx] = !mintPaused[col_idx];
else if (tog_idx == 1) redeemPaused[col_idx] = !redeemPaused[col_idx];
else if (tog_idx == 2) buyBackPaused[col_idx] = !buyBackPaused[col_idx];
else if (tog_idx == 3) recollateralizePaused[col_idx] = !recollateralizePaused[col_idx];
else if (tog_idx == 4) borrowingPaused[col_idx] = !borrowingPaused[col_idx];
emit MRBRToggled(col_idx, tog_idx);
}
/* ========== RESTRICTED FUNCTIONS, GOVERNANCE ONLY ========== */
// Add an AMO Minter
function addAMOMinter(address amo_minter_addr) external onlyByOwnGov {
require(amo_minter_addr != address(0), "Zero address detected");
// Make sure the AMO Minter has collatDollarBalance()
uint256 collat_val_e18 = IFraxAMOMinter(amo_minter_addr).collatDollarBalance();
require(collat_val_e18 >= 0, "Invalid AMO");
amo_minter_addresses[amo_minter_addr] = true;
emit AMOMinterAdded(amo_minter_addr);
}
// Remove an AMO Minter
function removeAMOMinter(address amo_minter_addr) external onlyByOwnGov {
amo_minter_addresses[amo_minter_addr] = false;
emit AMOMinterRemoved(amo_minter_addr);
}
function setCollateralPrice(uint256 col_idx, uint256 _new_price) external onlyByOwnGov {
// CONSIDER ORACLES EVENTUALLY!!!
collateral_prices[col_idx] = _new_price;
emit CollateralPriceSet(col_idx, _new_price);
}
// Could also be called toggleCollateral
function toggleCollateral(uint256 col_idx) external onlyByOwnGov {
address col_address = collateral_addresses[col_idx];
enabled_collaterals[col_address] = !enabled_collaterals[col_address];
emit CollateralToggled(col_idx, enabled_collaterals[col_address]);
}
function setPoolCeiling(uint256 col_idx, uint256 new_ceiling) external onlyByOwnGov {
pool_ceilings[col_idx] = new_ceiling;
emit PoolCeilingSet(col_idx, new_ceiling);
}
function setFees(uint256 col_idx, uint256 new_mint_fee, uint256 new_redeem_fee, uint256 new_buyback_fee, uint256 new_recollat_fee) external onlyByOwnGov {
minting_fee[col_idx] = new_mint_fee;
redemption_fee[col_idx] = new_redeem_fee;
buyback_fee[col_idx] = new_buyback_fee;
recollat_fee[col_idx] = new_recollat_fee;
emit FeesSet(col_idx, new_mint_fee, new_redeem_fee, new_buyback_fee, new_recollat_fee);
}
function setPoolParameters(uint256 new_bonus_rate, uint256 new_redemption_delay) external onlyByOwnGov {
bonus_rate = new_bonus_rate;
redemption_delay = new_redemption_delay;
emit PoolParametersSet(new_bonus_rate, new_redemption_delay);
}
function setPriceThresholds(uint256 new_mint_price_threshold, uint256 new_redeem_price_threshold) external onlyByOwnGov {
mint_price_threshold = new_mint_price_threshold;
redeem_price_threshold = new_redeem_price_threshold;
emit PriceThresholdsSet(new_mint_price_threshold, new_redeem_price_threshold);
}
function setBbkRctPerHour(uint256 _bbkMaxColE18OutPerHour, uint256 _rctMaxFxsOutPerHour) external onlyByOwnGov {
bbkMaxColE18OutPerHour = _bbkMaxColE18OutPerHour;
rctMaxFxsOutPerHour = _rctMaxFxsOutPerHour;
emit BbkRctPerHourSet(_bbkMaxColE18OutPerHour, _rctMaxFxsOutPerHour);
}
// Set the Chainlink oracles
function setOracles(address _frax_usd_chainlink_addr, address _fxs_usd_chainlink_addr) external onlyByOwnGov {
// Set the instances
priceFeedFRAXUSD = AggregatorV3Interface(_frax_usd_chainlink_addr);
priceFeedFXSUSD = AggregatorV3Interface(_fxs_usd_chainlink_addr);
// Set the decimals
chainlink_frax_usd_decimals = priceFeedFRAXUSD.decimals();
chainlink_fxs_usd_decimals = priceFeedFXSUSD.decimals();
emit OraclesSet(_frax_usd_chainlink_addr, _fxs_usd_chainlink_addr);
}
function setCustodian(address new_custodian) external onlyByOwnGov {
custodian_address = new_custodian;
emit CustodianSet(new_custodian);
}
function setTimelock(address new_timelock) external onlyByOwnGov {
timelock_address = new_timelock;
emit TimelockSet(new_timelock);
}
/* ========== EVENTS ========== */
event CollateralToggled(uint256 col_idx, bool new_state);
event PoolCeilingSet(uint256 col_idx, uint256 new_ceiling);
event FeesSet(uint256 col_idx, uint256 new_mint_fee, uint256 new_redeem_fee, uint256 new_buyback_fee, uint256 new_recollat_fee);
event PoolParametersSet(uint256 new_bonus_rate, uint256 new_redemption_delay);
event PriceThresholdsSet(uint256 new_bonus_rate, uint256 new_redemption_delay);
event BbkRctPerHourSet(uint256 bbkMaxColE18OutPerHour, uint256 rctMaxFxsOutPerHour);
event AMOMinterAdded(address amo_minter_addr);
event AMOMinterRemoved(address amo_minter_addr);
event OraclesSet(address frax_usd_chainlink_addr, address fxs_usd_chainlink_addr);
event CustodianSet(address new_custodian);
event TimelockSet(address new_timelock);
event MRBRToggled(uint256 col_idx, uint8 tog_idx);
event CollateralPriceSet(uint256 col_idx, uint256 new_price);
}