all files / plugins/assets/ AppreciatingFiatCollateral.sol

95.83% Statements 23/24
90% Branches 18/20
100% Functions 4/4
96.88% Lines 31/32
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                                                                              2118× 2118×                                           7024×     6921× 6921×   6921× 6921×               5076×   137× 137×     4939×           4939×     4939×     4939× 61× 61×   837×       4939×         4858× 4858× 4858× 4858×               4858× 62×   4796×       81× 50×     4908× 4908× 150×           5891×              
// SPDX-License-Identifier: BlueOak-1.0.0
pragma solidity 0.8.17;
 
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.
 *
 * 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 public 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) {
        Erequire(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 {
        if (alreadyDefaulted()) {
            // continue to update rates
            exposedReferencePrice = _underlyingRefPerTok().mul(revenueShowing);
            return;
        }
 
        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
        uint192 underlyingRefPerTok = _underlyingRefPerTok();
 
        // {ref/tok} = {ref/tok} * {1}
        uint192 hiddenReferencePrice = underlyingRefPerTok.mul(revenueShowing);
 
        // uint192(<) is equivalent to Fix.lt
        if (underlyingRefPerTok < exposedReferencePrice) {
            exposedReferencePrice = hiddenReferencePrice;
            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
            Eif (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);
        }
 
        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() internal view virtual returns (uint192);
}