Skip to main content
PulseScanner.io

Address

0xc65abc8b9b4b3cee03430f6fc3d8a4760221a113
Current Holdings
$0.00
TXs sent
not counted
First Active
2023-12-01
block 18,971,009
Last Active
487 days ago
block 23,510,484
Funded By
not identified

Net worth historyi

2 snapshots · to block 27,580,936coverage change 26 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Aug
exact matchPriceFeedsolc 0.6.11+commit.5ef660b1runtime exact · creation not verified
// SPDX-License-Identifier: MIT
pragma solidity 0.6.11;

import "./Dependencies/Ownable.sol";
import "./Interfaces/IPriceFeed.sol";
import "./Interfaces/ISecondaryOracle.sol";
import "./Dependencies/PLSXLibrary.sol";
import "./Interfaces/IFetchCaller.sol";
import "./Dependencies/SafeMath.sol";
import "./Dependencies/BaseMath.sol";
import "./Dependencies/LiquidLoansMath.sol";
import "./Dependencies/console.sol";
import "./Dependencies/CheckContract.sol";

/*
* PriceFeed contract is for mainnet deployment, to be connected to an Oracle service
* This version of the contract should be deployed to staging (testnet), preprod (testnet) and prod (mainnet).
*
* The current PriceFeed implementation usese the TWAP price diractly from the PLSX LP contract
* as a temporary solution until an Oracle is available on PulseChain.
* 
* The PriceFeed contract should be deployed to staging (testnet), preprod (testnet) and prod (mainnet) environments ONLY.
*/
contract PriceFeed is Ownable, CheckContract, BaseMath, IPriceFeed {
    using SafeMath for uint256;

    string constant public NAME = "PriceFeed";

    IFetchCaller public fetchCaller;  // Wrapper contract that calls the Fetch system
    ISecondaryOracle public secondaryOracle; // Secondary Oracle contract

    /*
    Query Descriptor:
    { type: "SpotPrice", asset: "pls", currency: "usd" }
    Query Data (Bytes):
    0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000953706f745072696365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003706c73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037573640000000000000000000000000000000000000000000000000000000000
    Query ID (Hash):
    0x83245f6a6a2f6458558a706270fbcc35ac3a81917602c1313d3bfa998dcc2d4b
    */
    bytes32 public constant PLSUSD_FETCH_REQ_ID = 0x83245f6a6a2f6458558a706270fbcc35ac3a81917602c1313d3bfa998dcc2d4b;

    // Maximum time period allowed since Fetch's latest round data timestamp, beyond which Fetch is considered frozen.
    uint constant public TIMEOUT = 14400;  // 4 hours: 60 * 60 * 4

    /* 
    * The maximum relative price difference between two oracle responses allowed in order for the PriceFeed
    * to return to using the Fetch oracle. 18-digit precision.
    */
    uint constant public MAX_PRICE_DIFFERENCE_BETWEEN_ORACLES = 5e16; // 5%

    // Maximum deviation allowed between two consecutive Fetch oracle prices. 18-digit precision.
    uint constant public MAX_PRICE_DEVIATION_FROM_PREVIOUS_PRICE =  5e17; // 50%

    // The last good price seen from an oracle by LiquidLoans
    uint public lastGoodPrice;

    struct FetchResponse {
        bool ifRetrieve;
        uint256 value;
        uint256 timestamp;
        bool success;
    }

    struct SecondaryOracleResponse {
        bool ifRetrieve;
        uint256 value;
        uint256 timestamp;
        bool success;
    }

    // TODO implement the PriceFeedStatus functionality below. Left here for PriceFeedTestnet to compile:
    enum Status {
        fetchWorking,
        usingSecondaryFetchUntrusted, 
        bothOraclesUntrusted,
        usingSecondaryFetchFrozen, 
        usingFetchSecondaryUntrusted
    }

    // The current status of the PricFeed, which determines the conditions for the next price fetch attempt
    Status public status;

    event LastGoodPriceUpdated(uint _lastGoodPrice);
    event PriceFeedStatusChanged(Status newStatus);

    //--- Dependency setters ---
        
    function setAddresses(
        address _fetchCallerAddress,
        address _secondaryOracleAddress
    )
        external
        onlyOwner
    {
        checkContract(_fetchCallerAddress);
        checkContract(_secondaryOracleAddress);

        fetchCaller = IFetchCaller(_fetchCallerAddress);
        secondaryOracle = ISecondaryOracle(_secondaryOracleAddress);

        //Explicitly set initial system status
        status = Status.fetchWorking;

        //Get an initial price from Fetch to serve as first reference for lastGoodPrice
        FetchResponse memory fetchResponse = _getCurrentFetchResponse();
        SecondaryOracleResponse memory secondaryResponse = _getCurrentSecondaryResponse();

        require(_bothOraclesLiveAndUnbrokenAndSimilarPrice(fetchResponse, secondaryResponse), 
            "PriceFeed: Oracles must be working and current");

        _storeFetchPrice(fetchResponse);

        _renounceOwnership();
    }

    //--- Fetch response wrapper functions ---
    function _getCurrentFetchResponse() internal returns (FetchResponse memory fetchResponse) {
        try fetchCaller.getFetchCurrentValue(PLSUSD_FETCH_REQ_ID) returns
        (
            bool ifRetrieve,
            uint256 value,
            uint256 _timestampRetrieved
        )
        {
            //If call to Fetch succeeds, return the response and success = true
            fetchResponse.ifRetrieve = ifRetrieve;
            fetchResponse.value = value;
            fetchResponse.timestamp = _timestampRetrieved;
            fetchResponse.success = true;

            return (fetchResponse);
        } catch {
            //If call to Fetch reverts, return a zero response with success = false
            return (fetchResponse);
        }
    }

    function _getPreviousFetchResponse(uint timestamp) internal returns (FetchResponse memory fetchResponse) {
        try fetchCaller.getFetchPreviousValue(PLSUSD_FETCH_REQ_ID, timestamp) returns
        (
            bool ifRetrieve,
            uint256 value,
            uint256 _timestampRetrieved
        )
        {
            //If call to Fetch succeeds, return the response and success = true
            fetchResponse.ifRetrieve = ifRetrieve;
            fetchResponse.value = value;
            fetchResponse.timestamp = _timestampRetrieved;
            fetchResponse.success = true;

            return (fetchResponse);
        } catch {
            //If call to Fetch reverts, return a zero response with success = false
            return (fetchResponse);
        }
    }

    function _fetchIsFrozen(FetchResponse  memory _fetchResponse) internal view returns (bool) {
        return block.timestamp.sub(_fetchResponse.timestamp) > TIMEOUT;
    }

    function _fetchIsBroken(FetchResponse memory _response) internal view returns (bool) {
        //Check for response call reverted
        if (!_response.success) {return true;}
        //Check for an invalid timeStamp that is 0, or in the future
        if (_response.timestamp == 0 || _response.timestamp > block.timestamp) {return true;}
        //Check for zero price
        if (_response.value == 0) {return true;}

        return false;
    }

    function _storeFetchPrice(FetchResponse memory _fetchResponse) internal returns (uint) {
        _storePrice(_fetchResponse.value);
        return _fetchResponse.value;
    }

    function _changeStatus(Status _status) internal {
        status = _status;
        emit PriceFeedStatusChanged(_status);
    }

    function _storePrice(uint _currentPrice) internal {
        lastGoodPrice = _currentPrice;
        emit LastGoodPriceUpdated(_currentPrice);
    }

    /*
    * fetchPrice():
    * Returns the latest price obtained from the Oracle. Called by LiquidLoans functions that require a current price.
    *
    * Also callable by anyone externally.
    *
    * Non-view function - it stores the last good price seen by LiquidLoans.
    *
    * Uses a main oracle (Fetch) and a fallback oracle (SecondaryOracle) in case Fetch fails. If both fail, 
    * it uses the last good price seen by LiquidLoans.
    *
    */
    function fetchPrice() external override returns (uint) {
        //Get current and previous price data from Fetch and current price data from SecondaryOracle
        FetchResponse memory fetchResponse = _getCurrentFetchResponse();
        FetchResponse memory prevFetchResponse = _getPreviousFetchResponse(fetchResponse.timestamp);
        SecondaryOracleResponse memory secondaryResponse = _getCurrentSecondaryResponse();

        //--- CASE 1: System fetched last price from Fetch  ---
        if (status == Status.fetchWorking) {
            //If Fetch is broken, try SecondaryOracle
            if (_fetchIsBroken(fetchResponse)) {

                //If SecondaryOracle is broken then both oracles are untrusted, so return the last good price
                if (_secondaryIsBroken(secondaryResponse)) {
                    _changeStatus(Status.bothOraclesUntrusted);
                    return lastGoodPrice;
                }
                /*
                * If SecondaryOracle is only frozen but otherwise returning valid data, return the last good price.
                */
                if (_secondaryIsFrozen(secondaryResponse)) {
                    _changeStatus(Status.usingSecondaryFetchUntrusted);
                    return lastGoodPrice;
                }
                    
                //If Fetch is broken and SecondaryOracle is working, switch to SecondaryOracle and return current SecondaryOracle price
                _changeStatus(Status.usingSecondaryFetchUntrusted);
                return _storeSecondaryPrice(secondaryResponse);
            }

            //If Fetch is frozen, try SecondaryOracle
            if (_fetchIsFrozen(fetchResponse)) {         

                //If SecondaryOracle is broken too, remember SecondaryOracle broke, and return last good price
                if (_secondaryIsBroken(secondaryResponse)) {
                    _changeStatus(Status.usingFetchSecondaryUntrusted);
                    return lastGoodPrice;     
                }

                //If SecondaryOracle is frozen or working, remember Fetch froze, and switch to SecondaryOracle
                _changeStatus(Status.usingSecondaryFetchFrozen);
                
                if (_secondaryIsFrozen(secondaryResponse)) {return lastGoodPrice;}

                //If SecondaryOracle is working, use it
                return _storeSecondaryPrice(secondaryResponse);
            }

            //If Fetch price has changed by > 50% between two consecutive rounds, compare it to SecondaryOracle's price
            if (_fetchPriceChangeAboveMax(fetchResponse, prevFetchResponse)) {

                //If SecondaryOracle is broken, both oracles are untrusted, and return last good price
                if (_secondaryIsBroken(secondaryResponse)) {
                    _changeStatus(Status.bothOraclesUntrusted);
                    return lastGoodPrice;
                }

                //If SecondaryOracle is frozen, switch to SecondaryOracle and return last good price 
                if (_secondaryIsFrozen(secondaryResponse)) { 
                    _changeStatus(Status.usingSecondaryFetchUntrusted);
                    return lastGoodPrice;
                }

                /* 
                * If SecondaryOracle is live and both oracles have a similar price, conclude that Fetch's large price deviation between
                * two consecutive rounds was likely a legitmate market price movement, and so continue using Fetch 
                */
                if (_bothOraclesSimilarPrice(fetchResponse, secondaryResponse)) {
                    return _storeFetchPrice(fetchResponse);
                }               

                //If SecondaryOracle is live but the oracles differ too much in price, conclude that Fetch's initial price deviation was
                //an oracle failure. Switch to SecondaryOracle, and use SecondaryOracle price

                _changeStatus(Status.usingSecondaryFetchUntrusted);
                return _storeSecondaryPrice(secondaryResponse);
            }

            //If Fetch is working and SecondaryOracle is broken, remember SecondaryOracle is broken
            if (_secondaryIsBroken(secondaryResponse)) {
                _changeStatus(Status.usingFetchSecondaryUntrusted);    
            }

            //If Fetch is working, return Fetch current price (no status change)
            return _storeFetchPrice(fetchResponse);
        }

        //--- CASE 2: The system fetched last price from SecondaryOracle --- 
        if (status == Status.usingSecondaryFetchUntrusted) { 
            //If both SecondaryOracle and Fetch are live, unbroken, and reporting similar prices, switch back to Fetch
            if (_bothOraclesLiveAndUnbrokenAndSimilarPrice(fetchResponse, secondaryResponse)) {
                _changeStatus(Status.fetchWorking);
                return _storeFetchPrice(fetchResponse);
            }

            if (_secondaryIsBroken(secondaryResponse)) {
                _changeStatus(Status.bothOraclesUntrusted);
                return lastGoodPrice; 
            }

            /*
            * If SecondaryOracle is only frozen but otherwise returning valid data, just return the last good price.
            */
            if (_secondaryIsFrozen(secondaryResponse)) {return lastGoodPrice;}
                
            //Otherwise, use SecondaryOracle price
            return _storeSecondaryPrice(secondaryResponse);
        }

        //--- CASE 3: Both oracles were untrusted at the last price fetch ---
        if (status == Status.bothOraclesUntrusted) {
            /*
            * If both oracles are now live, unbroken and similar price, we assume that they are reporting
            * accurately, and so we switch back to Fetch.
            */
            if (_bothOraclesLiveAndUnbrokenAndSimilarPrice(fetchResponse, secondaryResponse)) {
                _changeStatus(Status.fetchWorking);
                return _storeFetchPrice(fetchResponse);
            } 

            //Otherwise, return the last good price - both oracles are still untrusted (no status change)
            return lastGoodPrice;
        }

        //--- CASE 4: Using SecondaryOracle, and Fetch is frozen ---
        if (status == Status.usingSecondaryFetchFrozen) {
            if (_fetchIsBroken(fetchResponse)) {
                //If both Oracles are broken, return last good price
                if (_secondaryIsBroken(secondaryResponse)) {
                    _changeStatus(Status.bothOraclesUntrusted);
                    return lastGoodPrice;
                }

                //If Fetch is broken, remember it and switch to using SecondaryOracle
                _changeStatus(Status.usingSecondaryFetchUntrusted);

                if (_secondaryIsFrozen(secondaryResponse)) {
                    return lastGoodPrice;
                }

                //If SecondaryOracle is working, return SecondaryOracle current price
                return _storeSecondaryPrice(secondaryResponse);
            }

            if (_fetchIsFrozen(fetchResponse)) {
                //if Fetch is frozen and SecondaryOracle is broken, remember SecondaryOracle broke, and return last good price
                if (_secondaryIsBroken(secondaryResponse)) {
                    _changeStatus(Status.usingFetchSecondaryUntrusted);
                    return lastGoodPrice;
                }

                //If both are frozen, just use lastGoodPrice
                if (_secondaryIsFrozen(secondaryResponse)) {
                    return lastGoodPrice;
                }

                //if Fetch is frozen and SecondaryOracle is working, keep using SecondaryOracle (no status change)
                return _storeSecondaryPrice(secondaryResponse);
            }

            //if Fetch is live and SecondaryOracle is broken, remember SecondaryOracle broke, and return Fetch price
            if (_secondaryIsBroken(secondaryResponse)) {
                _changeStatus(Status.usingFetchSecondaryUntrusted);
                return _storeFetchPrice(fetchResponse);
            }

            //If Fetch is live and SecondaryOracle is frozen, just use last good price (no status change) since we have no basis for comparison
            if (_secondaryIsFrozen(secondaryResponse)) {
                return lastGoodPrice;
            }

            //If Fetch is live and SecondaryOracle is working, compare prices. Switch to Fetch
            //if prices are within 5%, and return Fetch price.
            if (_bothOraclesSimilarPrice(fetchResponse, secondaryResponse)) {
                _changeStatus(Status.fetchWorking);
                return _storeFetchPrice(fetchResponse);
            }

            //Otherwise if Fetch is live but price not within 5% of SecondaryOracle, distrust Fetch, and return SecondaryOracle price
            _changeStatus(Status.usingSecondaryFetchUntrusted);
            return _storeSecondaryPrice(secondaryResponse);
        }

        //--- CASE 5: Using Fetch, SecondaryOracle is untrusted ---
        if (status == Status.usingFetchSecondaryUntrusted) {
            //If Fetch breaks, now both oracles are untrusted
            if (_fetchIsBroken(fetchResponse)) {
                _changeStatus(Status.bothOraclesUntrusted);
                return lastGoodPrice;
            }

            //If Fetch is frozen, return last good price (no status change)
            if (_fetchIsFrozen(fetchResponse)) {
                return lastGoodPrice;
            }

            //If Fetch and SecondaryOracle are both live, unbroken and similar price, switch back to fetchWorking and return Fetch price
            if (_bothOraclesLiveAndUnbrokenAndSimilarPrice(fetchResponse, secondaryResponse)) {
                _changeStatus(Status.fetchWorking);
                return _storeFetchPrice(fetchResponse);
            }

            //If Fetch is live but deviated >50% from it's previous price and SecondaryOracle is still untrusted, switch 
            //to bothOraclesUntrusted and return last good price
            if (_fetchPriceChangeAboveMax(fetchResponse, prevFetchResponse)) {
                _changeStatus(Status.bothOraclesUntrusted);
                return lastGoodPrice;
            }

            //Otherwise if Fetch is live and deviated <50% from it's previous price and SecondaryOracle is still untrusted, 
            //return Fetch price (no status change)
            return _storeFetchPrice(fetchResponse);
        }
    }

    function _bothOraclesLiveAndUnbrokenAndSimilarPrice
    (
        FetchResponse memory _fetchResponse,
        SecondaryOracleResponse memory _secondaryOracleResponse
    )
        internal
        view
        returns (bool)
    {
        //Return false if either oracle is broken or frozen
        if
        (
            _secondaryIsBroken(_secondaryOracleResponse) ||
            _secondaryIsFrozen(_secondaryOracleResponse) ||
            _fetchIsBroken(_fetchResponse) ||
            _fetchIsFrozen(_fetchResponse)
        )
        {
            return false;
        }

        return _bothOraclesSimilarPrice(_fetchResponse, _secondaryOracleResponse);
    }

    //TODO: Check this function as SecondaryOracle digits differs from fetch
    function _bothOraclesSimilarPrice(FetchResponse memory _fetchResponse, SecondaryOracleResponse memory _secondaryOracleResponse) internal pure returns (bool) {

        //Get the relative price difference between the oracles. Use the lower price as the denominator, i.e. the reference for the calculation.
        uint minPrice = LiquidLoansMath._min(_fetchResponse.value, _secondaryOracleResponse.value);
        uint maxPrice = LiquidLoansMath._max(_fetchResponse.value, _secondaryOracleResponse.value);
        uint percentPriceDifference = maxPrice.sub(minPrice).mul(DECIMAL_PRECISION).div(minPrice);

        /*
        * Return true if the relative price difference is <= 3%: if so, we assume both oracles are probably reporting
        * the honest market price, as it is unlikely that both have been broken/hacked and are still in-sync.
        */
        return percentPriceDifference <= MAX_PRICE_DIFFERENCE_BETWEEN_ORACLES;
    }

    function _fetchPriceChangeAboveMax(FetchResponse memory _currentResponse, FetchResponse memory _prevResponse) internal pure returns (bool) {

        uint currentPrice = _currentResponse.value;
        uint prevPrice = _prevResponse.value;

        uint minPrice = LiquidLoansMath._min(currentPrice, prevPrice);
        uint maxPrice = LiquidLoansMath._max(currentPrice, prevPrice);

        /*
        * Use the larger price as the denominator:
        * - If price decreased, the percentage deviation is in relation to the the previous price.
        * - If price increased, the percentage deviation is in relation to the current price.
        */
        uint percentDeviation = maxPrice.sub(minPrice).mul(DECIMAL_PRECISION).div(maxPrice);

        // Return true if price has more than doubled, or more than halved.
        return percentDeviation > MAX_PRICE_DEVIATION_FROM_PREVIOUS_PRICE;
    }

    // --- SecondaryOracle Functions ---
    function _storeSecondaryPrice(SecondaryOracleResponse memory _secondaryOracleResponse) internal returns (uint) {
        _storePrice(_secondaryOracleResponse.value);
        return _secondaryOracleResponse.value;
    }

    function _secondaryIsFrozen(SecondaryOracleResponse  memory _response) internal view returns (bool) {
        return block.timestamp.sub(_response.timestamp) > TIMEOUT;
    }

    function _secondaryIsBroken(SecondaryOracleResponse memory _response) internal view returns (bool) {
        //Check for response call reverted
        if (!_response.success) {return true;}
        //Check for an invalid timeStamp that is 0, or in the future
        if (_response.timestamp == 0 || _response.timestamp > block.timestamp) {return true;}
        //Check for zero price
        if (_response.value == 0) {return true;}

        return false;
    }

    /*
    * Get the response from the secondary oracle
    */
    function _getCurrentSecondaryResponse() internal returns (SecondaryOracleResponse memory _response) {
        (_response.ifRetrieve, _response.value, _response.timestamp, _response.success) = secondaryOracle.getPrice();
    }

}

// 2022 Liquid Loans