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 | 4309× 4309× 4296× 4296× 4282× 4269× 4256× 4256× 4256× 4256× 4256× 4256× 4256× 5386× 5358× 5358× 4186× 4167× 4167× 4167× 4167× 19× 3878× 3808× 3808× 70× 60× 1918× 1879× 1879× 39× 39× 39× 26× 26× 26× 1905× 6347× 954× | // SPDX-License-Identifier: BlueOak-1.0.0 pragma solidity 0.8.17; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "../../interfaces/IAsset.sol"; import "./OracleLib.sol"; contract Asset is IAsset { 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_ ) { Erequire(priceTimeout_ > 0, "price timeout zero"); require(address(chainlinkFeed_) != address(0), "missing chainlink feed"); Erequire(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 Eif (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 Iif (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 Iif (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 >= priceTimeout) return (0, 0); // no price after timeout elapses // {1} = {s} / {s} uint192 lotMultiplier = divuu(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 /// @dev Use delegatecall function claimRewards() external virtual {} // solhint-enable no-empty-blocks } |