-
Notifications
You must be signed in to change notification settings - Fork 111
/
Copy pathCurveStableRTokenMetapoolCollateral.sol
143 lines (129 loc) · 5.71 KB
/
CurveStableRTokenMetapoolCollateral.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
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "./CurveStableMetapoolCollateral.sol";
/**
* @title CurveStableRTokenMetapoolCollateral
* This plugin contract is intended for 2-fiattoken stable metapools that
* involve RTokens, such as eUSD-fraxBP.
*
* tok = ConvexStakingWrapper(pairedUSDRToken/USDBasePool)
* ref = PairedUSDRToken/USDBasePool pool invariant
* tar = USD
* UoA = USD
*
* @notice Curve pools with native ETH or ERC777 should be avoided,
* see docs/collateral.md for information
*/
contract CurveStableRTokenMetapoolCollateral is CurveStableMetapoolCollateral {
using FixLib for uint192;
IAssetRegistry internal immutable pairedAssetRegistry; // AssetRegistry of paired RToken
IBasketHandler internal immutable pairedBasketHandler; // BasketHandler of paired RToken
/// @param config.chainlinkFeed Feed units: {UoA/pairedTok}
/// @dev config.chainlinkFeed/oracleError/oracleTimeout are unused; set chainlinkFeed to 0x1
/// @dev config.erc20 should be a RewardableERC20
constructor(
CollateralConfig memory config,
uint192 revenueHiding,
PTConfiguration memory ptConfig,
ICurveMetaPool metapoolToken_,
uint192 pairedTokenDefaultThreshold_
)
CurveStableMetapoolCollateral(
config,
revenueHiding,
ptConfig,
metapoolToken_,
pairedTokenDefaultThreshold_
)
{
IMain main = IRToken(address(pairedToken)).main();
pairedAssetRegistry = main.assetRegistry();
pairedBasketHandler = main.basketHandler();
}
/// Should not revert
/// Refresh exchange rates and update default status.
/// Have to override to add custom default checks
function refresh() public virtual override {
// solhint-disable-next-line no-empty-blocks
try pairedAssetRegistry.refresh() {} catch {
// must allow failure since cannot brick refresh()
}
CollateralStatus oldStatus = status();
// Check for hard default
// must happen before tryPrice() call since `refPerTok()` returns a stored value
// revenue hiding: do not DISABLE if drawdown is small
try this.underlyingRefPerTok() returns (uint192 underlyingRefPerTok_) {
// {ref/tok} = {ref/tok} * {1}
uint192 hiddenReferencePrice = underlyingRefPerTok_.mul(revenueShowing);
// uint192(<) is equivalent to Fix.lt
if (underlyingRefPerTok_ < exposedReferencePrice) {
exposedReferencePrice = underlyingRefPerTok_;
markStatus(CollateralStatus.DISABLED);
} else if (hiddenReferencePrice > exposedReferencePrice) {
exposedReferencePrice = hiddenReferencePrice;
}
// Check for soft default + save prices
try this.tryPrice() returns (uint192 low, uint192 high, uint192) {
// {UoA/tok}, {UoA/tok}, {UoA/tok}
// (0, 0) is a valid price; (0, FIX_MAX) is unpriced
// Save prices if priced
if (high != FIX_MAX) {
savedLowPrice = low;
savedHighPrice = high;
lastSave = uint48(block.timestamp);
} else {
// must be unpriced
// untested:
// validated in other plugins, cost to test here is high
assert(low == 0);
}
// Check pool status: inner RToken must be both isReady() and
// fullyCollateralized() to prevent injection of bad debt.
try pairedBasketHandler.isReady() returns (bool isReady) {
if (
!isReady ||
low == 0 ||
_anyDepeggedInPool() ||
_anyDepeggedOutsidePool() ||
!pairedBasketHandler.fullyCollateralized()
) {
// If the price is below the default-threshold price, default eventually
// uint192(+/-) is the same as Fix.plus/minus
markStatus(CollateralStatus.IFFY);
} else {
markStatus(CollateralStatus.SOUND);
}
} catch {
// prefer NOT to revert on empty data here: an RToken missing the `isReady()`
// function would error out with empty data just like an OOG error.
markStatus(CollateralStatus.IFFY);
}
} catch (bytes memory errData) {
// see: docs/solidity-style.md#Catching-Empty-Data
if (errData.length == 0) revert(); // solhint-disable-line reason-string
markStatus(CollateralStatus.IFFY);
}
} catch (bytes memory errData) {
// see: docs/solidity-style.md#Catching-Empty-Data
if (errData.length == 0) revert(); // solhint-disable-line reason-string
markStatus(CollateralStatus.DISABLED);
}
CollateralStatus newStatus = status();
if (oldStatus != newStatus) {
emit CollateralStatusChanged(oldStatus, newStatus);
}
}
/// Can revert, used by `_anyDepeggedOutsidePool()`
/// Should not return FIX_MAX for low
/// @return lowPaired {UoA/pairedTok} The low price estimate of the paired token
/// @return highPaired {UoA/pairedTok} The high price estimate of the paired token
function tryPairedPrice()
public
view
virtual
override
returns (uint192 lowPaired, uint192 highPaired)
{
return pairedAssetRegistry.toAsset(pairedToken).price();
}
}