/
Asset.sol
176 lines (148 loc) · 6.83 KB
/
Asset.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
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.19;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../../interfaces/IAsset.sol";
import "./OracleLib.sol";
import "./VersionedAsset.sol";
contract Asset is IAsset, VersionedAsset {
using FixLib for uint192;
using OracleLib for AggregatorV3Interface;
AggregatorV3Interface public immutable chainlinkFeed; // {UoA/tok}
IERC20Metadata public immutable erc20;
uint8 public immutable erc20Decimals;
uint192 public immutable override maxTradeVolume; // {UoA}
uint48 public immutable oracleTimeout; // {s}
uint192 public immutable oracleError; // {1}
// === Lot price ===
uint48 public immutable priceTimeout; // {s} The period over which `savedHighPrice` decays to 0
uint192 public savedLowPrice; // {UoA/tok} The low price of the token during the last update
uint192 public savedHighPrice; // {UoA/tok} The high price of the token during the last update
uint48 public lastSave; // {s} The timestamp when prices were last saved
/// @param priceTimeout_ {s} The number of seconds over which savedHighPrice decays to 0
/// @param chainlinkFeed_ Feed units: {UoA/tok}
/// @param oracleError_ {1} The % the oracle feed can be off by
/// @param maxTradeVolume_ {UoA} The max trade volume, in UoA
/// @param oracleTimeout_ {s} The number of seconds until a oracle value becomes invalid
constructor(
uint48 priceTimeout_,
AggregatorV3Interface chainlinkFeed_,
uint192 oracleError_,
IERC20Metadata erc20_,
uint192 maxTradeVolume_,
uint48 oracleTimeout_
) {
require(priceTimeout_ > 0, "price timeout zero");
require(address(chainlinkFeed_) != address(0), "missing chainlink feed");
require(oracleError_ > 0 && oracleError_ < FIX_ONE, "oracle error out of range");
require(address(erc20_) != address(0), "missing erc20");
require(maxTradeVolume_ > 0, "invalid max trade volume");
require(oracleTimeout_ > 0, "oracleTimeout zero");
priceTimeout = priceTimeout_;
chainlinkFeed = chainlinkFeed_;
oracleError = oracleError_;
erc20 = erc20_;
erc20Decimals = erc20.decimals();
maxTradeVolume = maxTradeVolume_;
oracleTimeout = oracleTimeout_;
}
/// 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 The third (unused) variable is only here for compatibility with Collateral
/// @return low {UoA/tok} The low price estimate
/// @return high {UoA/tok} The high price estimate
function tryPrice()
external
view
virtual
returns (
uint192 low,
uint192 high,
uint192
)
{
uint192 p = chainlinkFeed.price(oracleTimeout); // {UoA/tok}
uint192 err = p.mul(oracleError, CEIL);
// assert(low <= high); obviously true just by inspection
return (p - err, p + err, 0);
}
/// Should not revert
/// Refresh saved prices
function refresh() public virtual override {
try this.tryPrice() returns (uint192 low, uint192 high, uint192) {
// {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
assert(low == 0);
}
} catch (bytes memory errData) {
// see: docs/solidity-style.md#Catching-Empty-Data
if (errData.length == 0) revert(); // solhint-disable-line reason-string
}
}
/// Should not revert
/// @dev Should be general enough to not need to be overridden
/// @return {UoA/tok} The lower end of the price estimate
/// @return {UoA/tok} The upper end of the price estimate
function price() public view virtual returns (uint192, uint192) {
try this.tryPrice() returns (uint192 low, uint192 high, uint192) {
assert(low <= high);
return (low, high);
} catch (bytes memory errData) {
// see: docs/solidity-style.md#Catching-Empty-Data
if (errData.length == 0) revert(); // solhint-disable-line reason-string
return (0, FIX_MAX);
}
}
/// Should not revert
/// lotLow should be nonzero when the asset might be worth selling
/// @dev Should be general enough to not need to be overridden
/// @return lotLow {UoA/tok} The lower end of the lot price estimate
/// @return lotHigh {UoA/tok} The upper end of the lot price estimate
function lotPrice() external view virtual returns (uint192 lotLow, uint192 lotHigh) {
try this.tryPrice() returns (uint192 low, uint192 high, uint192) {
// if the price feed is still functioning, use that
lotLow = low;
lotHigh = high;
} catch (bytes memory errData) {
// see: docs/solidity-style.md#Catching-Empty-Data
if (errData.length == 0) revert(); // solhint-disable-line reason-string
// if the price feed is broken, use a decayed historical value
uint48 delta = uint48(block.timestamp) - lastSave; // {s}
if (delta <= oracleTimeout) {
lotLow = savedLowPrice;
lotHigh = savedHighPrice;
} else if (delta >= oracleTimeout + priceTimeout) {
return (0, 0); // no price after full timeout
} else {
// oracleTimeout <= delta <= oracleTimeout + priceTimeout
// {1} = {s} / {s}
uint192 lotMultiplier = divuu(oracleTimeout + priceTimeout - delta, priceTimeout);
// {UoA/tok} = {UoA/tok} * {1}
lotLow = savedLowPrice.mul(lotMultiplier);
lotHigh = savedHighPrice.mul(lotMultiplier);
}
}
assert(lotLow <= lotHigh);
}
/// @return {tok} The balance of the ERC20 in whole tokens
function bal(address account) external view virtual returns (uint192) {
return shiftl_toFix(erc20.balanceOf(account), -int8(erc20Decimals));
}
/// @return If the asset is an instance of ICollateral or not
function isCollateral() external pure virtual returns (bool) {
return false;
}
// solhint-disable no-empty-blocks
/// Claim rewards earned by holding a balance of the ERC20 token
/// DEPRECATED: claimRewards() will be removed from all assets and collateral plugins
function claimRewards() external virtual {}
// solhint-enable no-empty-blocks
}