-
Notifications
You must be signed in to change notification settings - Fork 111
/
AppreciatingFiatCollateral.sol
144 lines (126 loc) · 5.86 KB
/
AppreciatingFiatCollateral.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
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../../interfaces/IAsset.sol";
import "../../libraries/Fixed.sol";
import "./FiatCollateral.sol";
import "./Asset.sol";
import "./OracleLib.sol";
/**
* @title AppreciatingFiatCollateral
* Collateral that may need revenue hiding to become truly "up only"
*
* For: {tok} != {ref}, {ref} != {target}, {target} == {UoA}
* Inheritors _must_ implement underlyingRefPerTok()
* Can be easily extended by (optionally) re-implementing:
* - tryPrice()
* - refPerTok()
* - targetPerRef()
* - claimRewards()
* Should not have to re-implement any other methods.
* Should also set `oracleTimeout` in constructor if adding a new oracle source with timeout.
*
* Can intentionally disable default checks by setting config.defaultThreshold to 0
* Can intentionally do no revenue hiding by setting revenueHiding to 0
*/
abstract contract AppreciatingFiatCollateral is FiatCollateral {
using FixLib for uint192;
using OracleLib for AggregatorV3Interface;
// revenueShowing = FIX_ONE.minus(revenueHiding)
uint192 public immutable revenueShowing; // {1} The maximum fraction of refPerTok to show
// does not become nonzero until after first refresh()
uint192 internal exposedReferencePrice; // {ref/tok} max ref price observed, sub revenue hiding
/// @param config.chainlinkFeed Feed units: {UoA/ref}
/// @param revenueHiding {1} A value like 1e-6 that represents the maximum refPerTok to hide
constructor(CollateralConfig memory config, uint192 revenueHiding) FiatCollateral(config) {
require(revenueHiding < FIX_ONE, "revenueHiding out of range");
revenueShowing = FIX_ONE.minus(revenueHiding);
}
/// Can revert, used by other contract functions in order to catch errors
/// Should not return FIX_MAX for low
/// Should only return FIX_MAX for high if low is 0
/// @dev Override this when pricing is more complicated than just a single oracle
/// @return low {UoA/tok} The low price estimate
/// @return high {UoA/tok} The high price estimate
/// @return pegPrice {target/ref} The actual price observed in the peg
function tryPrice()
external
view
virtual
override
returns (
uint192 low,
uint192 high,
uint192 pegPrice
)
{
// {target/ref} = {UoA/ref} / {UoA/target} (1)
pegPrice = chainlinkFeed.price(oracleTimeout);
// {UoA/tok} = {target/ref} * {ref/tok} * {UoA/target} (1)
uint192 p = pegPrice.mul(underlyingRefPerTok());
uint192 err = p.mul(oracleError, CEIL);
low = p - err;
high = p + err;
// assert(low <= high); obviously true just by inspection
}
/// Should not revert
/// Refresh exchange rates and update default status.
/// @dev Should not need to override: can handle collateral with variable refPerTok()
function refresh() public virtual override {
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 pegPrice) {
// {UoA/tok}, {UoA/tok}, {target/ref}
// (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
assert(low == 0);
}
// If the price is below the default-threshold price, default eventually
// uint192(+/-) is the same as Fix.plus/minus
if (pegPrice < pegBottom || pegPrice > pegTop || low == 0) {
markStatus(CollateralStatus.IFFY);
} else {
markStatus(CollateralStatus.SOUND);
}
} 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);
}
}
/// @return {ref/tok} Exposed quantity of whole reference units per whole collateral tokens
function refPerTok() public view virtual override returns (uint192) {
return exposedReferencePrice;
}
/// Should update in inheritors
/// @return {ref/tok} Actual quantity of whole reference units per whole collateral tokens
function underlyingRefPerTok() public view virtual returns (uint192);
}