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.