This article provides a holistic perspective: Chainlink data feed, focus on security. The main target of the article is EVM and firmness and three main parts:
- Architecture of feed and off-chain aggregation protocols
- trust model
- Common data feed issues
First, we Problems Chainlink Solve and how They solve this. The core ideas of the off-chain aggregation protocol are explained and the core contracts are explained. Then we Trust model for feeds. This will help you evaluate how risky it is to integrate the feed into your protocol. Last but not least: Issues with data feeds: Provides explanations, code examples, and recommendations on how to avoid problems.
Architecture and off-chain aggregation
Motivation
While executing a smart contract, external APIs cannot be called as it requires deterministic execution on all nodes. Therefore, accessing off-chain data from smart contracts is problematic. Therefore, an EOA is required to post data to the chain.
To publish off-chain data to the chain, you need three things:
- Independence and quality of off-chain data feeds
- Decentralization of entities publishing data on chain
- Freshness of data
Chainlink seeks to achieve this through the Off-Chain Aggregation Protocol (OCR).
Off-chain reporting protocol
OCR is a protocol built on: Decentralized Oracle NetworkThis is (hopefully) a network. independent node. Nodes monitor Multiple off-chain feeds To get information about current prices. As part of OCR, a leader is selected. that much The leader compiles the report. Send transactions onto the chain from other participants.
If you are a leader behave badly, he gets cut. Reports are sent to Chainlink contracts. From there, we read the report, verify the signature, and find the median price.
Architecture of data feed contract
Each feed (e.g. BTC/ETH, ETH/USD,..) has two main corresponding contracts. deputy and collector. You will get the proxy address by copying the data feed address from your Chainlink dashboard. The proxy then points to the implementation, i.e. the aggregator.
Here is a diagram from the document:
The proxy allows the protocol to be: Upgradable. The aggregator then handles all reporting logic.
deputy
For consumer contracts, the proxy exposes an AggregatorV3Interface. Through this, consumers search price and Additional metadata.
It also allows: Aggregator Agreement Upgrade (Access control is mentioned in the trust model). During each upgrade, the PhaseId variable is incremented. Steps allow you to query historical aggregators and even historical data.
aggregator
AccessControlledOffchainAggregator A contract that handles price updates. Prices are updated in two scenarios:
- Heartbeat threshold trigger (data feed updated periodically)
- Deviation threshold trigger (off-chain values deviate by at least a threshold and must be updated)
One price update defines one round. Every time we update we round id variable. Time taken for each update block.timestamp Set to feed update time. This timestamp is the value returned by startAt and updateAt when called. Latest round data().
AggregatorV3Interface
Most protocols use AggregatorV3Interface to interact with data feeds. The API is well explained in the documentation. We will focus specifically on the following: Latest round data() function.
that much round id and Answered in round Contains the same latest round values as previously described. answer It’s the price start time and update time Again it contains the same value and corresponds to the timestamp of the round as described previously.
It may be surprising that variables contain identical values. See proof code. Because the aggregator has been upgraded, the same values are returned, but Chainlink has maintained the API (i.e. older versions behave differently).
We also wrote a test to check this.
One last important thing to discuss here is decimal. Each pair has a decimal point indicating the decimal point of the price (e.g. as seen in ERC20). Some feeds have 8 decimal places, some have 18. There are no clear rules for the number of decimal places for each data feed. Search on the mainnet and see the following list of decimal pairs:
feed usdt/eth, decimals: 18 feed usdc/eth, decimals: 18 feed btc/eth, decimals: 18 feed eth/btc, decimals: 8 feed eth/usd, decimals: 8 feed ampl/usd, decimals: 18 feed matic/usd, decimals: 8 feed ftm/eth, decimals: 18
Feeds marked as ETH appear to have 18 decimal places, while feeds marked as non-ETH appear to have 8 decimal places. butAt the same time, there is an AMPL/USD feed that has 18 decimal places, which violates the rules.
trust model
This section discusses: trust assumption Protocols and security researchers need to consider when interacting with data feeds.
Among other things, the data feed is: Upgradable. The multisig operated by Chainlink owns the proxy contract for each feed. They use Safe multig, which they find by extracting the owner address from the proxy and checking it on Etherscan. Multisignature has 9 owners and a threshold of 4. See the contract in evm.storage. This means that you only need 4 signatures to randomly update a random feed.
Each upgrade may fail or incorporate censorship for specific addresses.
Additionally, the nodes in the OCR protocol must be trustworthy. They are supposedly decentralized and independent, but their number is much smaller than the number of Ethereum nodes, so they may be easier to manipulate.
Additionally, even data feeds can provide incorrect values even if the nodes are honest. This has happened in the past. See post-mortem.
problem
This part explains it all. problem what could happen When interacting with a data feed. We will mainly focus on the latest V3 version.
1. Deprecated API
Some features provided by data feed contracts are deprecated and should no longer be used. Check out the full list. These features may be removed in a future upgrade and are therefore not safe to use.
These features include:
- Get answers,
- getTimestamp,
- An important thing is Latest answer,
- Latest round,
- Latest timestamp.
2. Receiving old data
Each data feed may return outdated data due to upgrade bugs, OCR nodes failing to reach consensus, etc. AggregatorV3Interface Provides enough information to ensure that your data is up to date.
One of the values he pursues is Latest round data The function return is update time. Additionally, we have access to: block.timestamp. Subtract these two values to get the time since the last update. You can set a maximum threshold above which this difference can occur. If this value is larger, the data will become stale and will be reverted.
3. The freshness interval is too wide.
As explained in the previous issue, what we need is poor inspection. However, if you use a freshness threshold that is too wide, you may not be able to detect stale prices in a timely manner.
The gap should not be too wide. Ideally, this should be based on the heartbeat interval of that feed.
4. The freshness interval is too narrow.
Also we cannot decide to use very narrow Freshness interval. If determined to be up to date with an interval shorter than the heartbeat period, some queries in the feed will wrong revert. As a result, the user becomes a DoS target.
5. Reading price inside try/catch block
As explained, contracts are upgradeable, so calls may be cancelled. bug or censorship. In such cases, the protocol may break and users may suffer a DoS.
If you read the price using try/catch you can recover from revert and try another oracle like TWAP for example.
6. L2 sequencer downtime
Some L2s (e.g. Arbitrum or Optimism) have sequencer. A sequencer is a node that receives user transactions and posts them to L1 in batches. Few protocols currently offer distributed sequencing, so downtime is relatively possible.
At the same time, these L2s provide the option to interact directly through L1 contracts without sequencer intervention.
Chainlink provides feeds for: Check sequencer downtime, which must be updated via L1. Please refer to the documentation.
Additionally, Chainlink updates its L2 data feed through a sequencer, so if it goes down, prices will not update.
I haven’t found any conclusive evidence for this, but it’s heavily implied by the fact that Chainlink itself recommends checking sequencer uptime when querying data feeds in their documentation. If your data feed is not updated through the sequencer, you will not see this recommendation.
Assume that the sequencer is offline, but the user can bypass it and send transactions directly over L1. We also assume that the protocol integrates the ETH/USD feed and the ETH price drops significantly. The malicious user sees this and sends the transaction through the L1 contract. Transactions are processed based on price. It hasn’t been updated yet, so users are still benefiting from the higher prices.
For more information on this issue, see the blog post.
7. Hardcoded data feed address
use Hardcoded data feed addresses are dangerous Nowadays, contracts are distributed across multiple chains. In other chains, the feed’s address may be different.
If the source code is not modified for each deployment, the protocol may use the wrong feed address.
8. Misinterpretation of prime numbers
Some feeds have 8 decimal places, some have 18, and there are no clear rules for how many decimal places each feed has. You must query one of the following: decimal Performs the functions of a given feed or retrieves information prior to distribution.
You shouldn’t assume that a particular feed has x decimal places without checking first. Using incorrect assumptions about decimals can lead to serious accounting errors.
9. Backup Oracle
If calls to the feed revert (e.g. after a failed upgrade), using a backup oracle (such as TWAP) allows the protocol to continue to function without DoSing users.
As auditors, we need to check backup oracles carefully. Logic can be underestimatedBecause its use can be assumed to be an unlikely scenario.
10. Low-quality feed
Not all feeds are created equal. Some may be of low quality or obsolete. Fortunately, Chainlink provides state. For each feed. We recommend checking the feed status before use.
11. Basic price verification
A large number of protocols use: need To check prices: require(answer > 0, “invalid price”);. This way you can implement basic sanity checking for prices. Protocols may add additional requirements that the price be part of the price. sane
conclusion
Chainlink Oracle Most widely used Oracle. They did a great job trustworthyBut the protocol Not fully decentralized And there are several trust assumption.
Protocols incorporating oracles can face a variety of challenges, including: Deprecated API or Insufficient price data verification.