Pre-SEP: Oracle Consumer Interface

209 views
Skip to first unread message

Alex Mootz

unread,
Mar 29, 2023, 2:56:12 PM3/29/23
to Stellar Developers
Hello all,

The teams at Stellar Expert and Script3 have put together a pre-SEP to define a uniform oracle consumer interface for price feed data. We are hoping to get some early feedback from the Stellar community regarding the direction of this SEP.

Outstanding Work
- Standardized errors

===================================================
## Preamble

```
SEP: TBD
Title: Oracle Consumer Interface
Authors: Alex Mootz <@mootz12>, OrbitLens <@orbitlens>, Markus Paulson-Luna <@markus_0_>
Track: Standard
Status: Draft
Created: 2023-02-17
Updated: 2023-03-29
Version: 0.0.0
Discussion: TBD
```

## Simple Summary

A standard interface for oracles transmitting price information.

## Motivation

Oracles, specifically price feed oracles, are used extensively in DeFi across blockchain ecosystems. Smart contracts consume prices from
oracles to assist in valuing on-chain tokens. A standard price feed interface allows smart contracts to fetch prices from any oracle
provider. Without this, smart contracts are forced to use adapter contracts that attempt to normalize the price consumption mechanism to
avoid being locked into whatever oracle implementation they are consuming.

In Soroban, cross-contract calls are relatively expensive, so an extensive adapter contract network is both inefficient for the blockchain
and expensive for the end user.

This SEP proposes a standard price oracle interface, such that smart contracts can implement a uniform consumption mechanism.

Smart contracts that follow standard interface described in this SEP also get the flexibility of switching between standard oracle price
feeds without internal logic changes. This increases the overall safety and flexibility of the Soroban smart contract ecosystem.

## Abstract

Oracles report a wide variety of data to the blockchain, enabling on-chain contracts to access the reported data. Oracles reporting price
data have become one of the most important use cases of oracles to-date. Implementing a standard consumption model for smart contracts
looking to access price data improves smart contract safety, flexibility, and reliability.

This SEP does not define how providers report price information, only how consumer smart contracts can access it via a standard interface.
The proposal is focused mainly on price feed for on-chain and off-chain assets. Other oracle types are outside the scope of this SEP.

## Specification

Oracle feed providers report all price feeds denominated in the base asset to a single price feed aggregator contract, regardless of
the number of asset price feeds being reported.

### Interface

The price feed contract should follow the `PriceFeedTrait` interface defined here:

```rust
/// Price data for an asset at a specific timestamp
pub struct PriceData {
    price: i128,
    timestamp: u64
}

/// Oracle feed interface description
pub trait PriceFeedTrait {
    /// Return the base asset the price is reported in
    fn base(
        env: soroban_sdk::Env,
    ) -> Address;

    /// Return all assets quoted by the price feed
    fn assets(
        env: soroban_sdk::Env,
    ) -> Vec<Address>;

    /// Return the number of decimals for all assets quoted by the oracle
    fn decimals(
        env: soroban_sdk::Env
    ) -> u32;

    /// Return default tick period timeframe (in seconds)
    fn resolution(
        env: soroban_sdk::Env
    ) -> u32;

    /// Get price in base asset at specific timestamp
    fn price(
        env: soroban_sdk::Env,
        asset: Address,
        timestamp: u64
    ) -> Option<PriceData>;

    /// Get last N price records
    fn prices(
        env: soroban_sdk::Env,
        asset: Address,
        records: u32
    ) -> Option<Vec<PriceData>>;

    /// Get the most recent price for an asset
    fn lastprice(
        env: soroban_sdk::Env,
        asset: Address,
    ) -> Option<PriceData>;
}
```

### Contract Address

The price feed aggregator contract should have a stable contract address. That is, it must be upgradeable without changing the contract
address. Smart contract consumers should only be expected to change their price feed aggregator address if they would like to change oracle
providers.

## Design Rationale

`PriceData` represents a datapoint describing a price of an asset at a specific timestamp. Price is encoded as an `i128` number where last N
digits designate the fractional part of the price stored with precision defined by `decimals()` of the given oracle feed. So the actual
price can be calculated as `price/10^decimals`. Note that using this conversion directly in the Soroban environment will result in the loss
of a fractional part since only integer division is supported, so only safe integer arithmetics should be used when consuming oracle prices.

Parameter `timestamp` in `PriceData` refers to Unix Timestamp calculated as `floor(unix_now()/resolution)*resolution `to provide uniform
price feed timeframes trimmed to oracle resolution. It is important for consumer contracts to check the timestamp field of the returned
values against the current ledger timestamp to make sure that the reported price value is not stale.

Method `last_price(env, asset)` returns the last known price ("current price") for a given asset provided by the oracle. Often developers
need to fetch last N values to apply some averaging logic to the feed values. For instance, time-weighted average price (TWAP) formula,
which is the measure of an asset's average price over a predetermined period of time. Correspondingly, `prices(env, asset, records)` method
can be used to retrieve the desired number of records.

For other logic developers may utilize `prices(env, asset, timestamp)` method which returns price information for a given point in time,
under condition that the historical data for a requested timestamp is available, or throws an error otherwise.

### Timeframe Resolution and Price Feed Precision

Particular values of data timeframe resolution and decimals precision is out of scope of this SEP. Once the price feed aggregation contract
is deployed, these values should never change. It is impossible to guarantee that consumer contracts can react to any updates in these
values.

### Timestamps Instead of Rounds

Some other well-known oracle implementations utilize generic round id instead of timestamps to identify price feed data points and allow
non-uniform sampling rates. However, this brings additional complexity on the consumer side. Moreover, downstream contracts need a timestamp
attached to each price update round either way, because it is often used for averaging and price staleness checks.

Primary motivation of using uniform timestamps as price feed entry identifiers is to get rid of excessive generic identifiers and ensure
that consumers can apply averaging algorithms (such as widely-utilized TWAP) directly to the data received from the oracle feed, without the
need to preprocess and normalize timestamps.

This approach also implies that an oracle should update its data feed regularly, or it should have the ability to extrapolate fragmented
reported data in a way to present a normalized monotonic output in response to price history calls following the described uniform sampling
rate approach.

### Data Reporting

The exact mechanism of data reporting mechanism utilized by an oracle is out of scope of this SEP. Nevertheless, it is recommended to avoid
relaying on centralized data sources or centralized reporting entity. Ideally, this process should be controlled by smart contract quoting
the data directly or a set of independent off-chain nodes which can come to the agreement on the feed updates by utilizing some kind of
on-chain or off-chain consensus.

### Data Retention

History retention period is individually configured by an oracle provider. This SEP does not provide specific guidelines in this regard.

## Security Concerns

This SEP does not create any security concerns for the Stellar protocol.

### Oracle Price Manipulations

Oracles can be exposed to various manipulations depending on the mechanism of price reporting.

On-chain price providers are vulnerable to manipulations via spot price spoofing. For example, an attacker can can take out a flash loan to
drain the liquidity from one side of the orderbook or liquidity pool. As a result, the protocol's internal price is directly controlled by
the attacker, which can inflate the price, subsequently execute arbitrage trade or take an undercollateralized loan in a smart contract
relying solely on the manipulated protocol, and then repay a loan, getting away with profit.

Off-chain oracles can suffer from a temporary outage, misconfiguration, or errors of external data API. The nodes software itself is
potentially vulnerable to attacks on access control, cryptographic implementation, transport, and database security.

Operators of centralized oracles may to report malicious data and abuse the trust of consuming contracts if the incentive is high enough.
On the other hand, nodes in the decentralized oracle network may misbehave by reporting invalid, malformed, or not properly validated data,
as well as simply coping the feed from other nodes without verification (in this case the resilience of the corresponding decentralized
oracle network may degrade significantly up to the point where a single entity controls the network).

### Best Practices for Consumer Smart Contract Developers

- Always check retrieved price data for staleness by comparing the quoted timestamp with current date.
- If available, utilize several oracle providers to mitigate the risks of service denial. Do not rely on a single oracle in mission-critical
  use-cases.
- Apply TWAP whenever possible. Consuming data without averaging may expose a contract to risks of high assets volatility or malicious
  artificial oracle price spoofing.
- Consumer protocols can set up additional safeguards for noticeable anomalies in the consumed oracles feed values (e.g. sudden deviations
  of stablecoin prices).
- Avoid using oracles with illiquid assets and markets as they are more prone to manipulations.
- If it's not critical to the functionality of the protocol, set up artificial delays to prevent flash-loan transactions attacks.
- To assess possible risk of trusting to the particular oracle, check the contact ownership, authorization required to update the feed,
  declaration about underlying data sources and security guarantees.
- In case of templated consumer contracts deployment or if a consumer contract is not upgradeable by design, it is advisable to use a proxy
  oracle contract with the same interface described above. Such a proxy contract can act as an intermediary between a consumer and a price
  feed contract, or may contain some advanced logic – price aggregation from several oracle providers, custom precision/resolution
  transforms, cross-price calculation, etc. It can be updated to point to other oracle feed (or to change internal aggregation logic)
  transparently to data consumers.

Dmytro Kozhevin

unread,
Mar 29, 2023, 3:35:27 PM3/29/23
to Alex Mootz, Stellar Developers
Given that the asset price is unit-less I assume that the users will always need to fetch the prices of at least two assets. Would it make sense to fetch multiple prices at once in order to optimize the oracle usage patterns?

--
You received this message because you are subscribed to the Google Groups "Stellar Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to stellar-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/stellar-dev/6a2aebd4-52dc-4f2b-a5f1-2ff1b0a59843n%40googlegroups.com.

Alex Mootz

unread,
Mar 31, 2023, 11:54:40 AM3/31/23
to Stellar Developers
> Given that the asset price is unit-less I assume that the users will always need to fetch the prices of at least two assets. Would it make sense to fetch multiple prices at once in order to optimize the oracle usage patterns?

Each price feed contract will quote all prices with a common base asset that can be fetched with `base`. So, a single price feed oracle with a base asset of USDC would be able to quote, for example, XLM/USDC, AQUA/USDC, and EURC/USDC, so consumers will only need to fetch a single price to get a quote. We considered including an endpoint to fetch `XLM/AQUA` prices by using the `XLM/USDC` and `AQUA/USDC` prices, but that felt slightly out of scope for the price feed to implement (and can be dangerous in some scenarios).

However, I do expect it to be pretty common for consumers to fetch more than one price, which was the motivation to store all prices of a single base asset into one contract. If the current Ethereum approach was taken where each asset pair is its own contract, consumers would have to load a new contract for each asset they want to price, which would be expensive in the current Soroban implementation.

Orbit Lens

unread,
Mar 31, 2023, 11:55:07 AM3/31/23
to Dmytro Kozhevin, Alex Mootz, Stellar Developers
No, an oracle always has base asset, so all prices are denominated in this base currency. For example, if the base asset for a particular price feed is set to USDC, then all quoted token prices are denominated in USDC. Base asset is arbitrary, oracle service providers can choose any asset they see fit, whether it's a fiat-pegged asset, any cryptoasset, or XLM. The only requirement -- it should exist on the ledger. 

I assume that the users will always need to fetch the prices of at least two assets. 

The most common use case aside from fetching a single asset price is a cross-price calculation. In our contract implementation, in addition to the interface methods described in this SEP, we also introduced a set of methods that perform cross-price calculations with semantics similar to price()lastprice() and prices() methods. So, for example, if the base asset of the oracle is USDC, developers can directly fetch BTC/XLM rate (calculated automatically inside the method) without the need to execute the conversion in the client contract. 

After the discussion with colleagues from Script3, we all agreed to exclude these cross-price methods from the SEP to keep it to the bare minimum. But of course, we are open to any thoughts from the community in this regard. If developers find it useful, we can definitely extend the proposed interface.

Yuri Escalianti

unread,
Jun 1, 2023, 7:27:00 PM6/1/23
to Stellar Developers
Hi all, we at BP Ventures have some ideas for improvements on this contract structure. We created a PR for discussion: https://github.com/stellar/stellar-protocol/pull/1357
Reply all
Reply to author
Forward
0 new messages