{"id":541,"date":"2023-08-07T17:46:29","date_gmt":"2023-08-07T15:46:29","guid":{"rendered":"https:\/\/ackeeblockchain.com\/blog\/?p=541"},"modified":"2025-02-19T14:11:41","modified_gmt":"2025-02-19T12:11:41","slug":"chainlink-data-feeds","status":"publish","type":"post","link":"https:\/\/ackee.xyz\/blog\/chainlink-data-feeds\/","title":{"rendered":"Chainlink Data Feeds, Security Researcher&#8217;s Perspective"},"content":{"rendered":"<p><span style=\"font-weight: 400;\">This article provides a holistic view of the <strong>Chainlink Data Feeds, <\/strong>focusing\u00a0on <strong>security<\/strong>. The article targets mainly <strong>EVM<\/strong> and <strong>Solidity <\/strong>and has<\/span><span style=\"font-weight: 400;\">\u00a0<strong>three main parts<\/strong>:<\/span><\/p>\n<ol>\n<li><span style=\"font-weight: 400;\"> Architecture of the feeds and off-chain aggregation protocol<\/span><\/li>\n<li><span style=\"font-weight: 400;\"> Trust model<\/span><\/li>\n<li><span style=\"font-weight: 400;\"> Common data-feed-related issues<\/span><\/li>\n<\/ol>\n<p><span style=\"font-weight: 400;\">Firstly, we introduce the <strong>problems Chainlink solves<\/strong> and <strong>how<\/strong> they solve them: the core idea of the off-chain aggregation protocol is explained, and the core contracts are described. Then we discuss the <strong>trust model of the feeds<\/strong>. This will help you evaluate how risky it is for you to integrate the feeds into your protocol. Lastly, and most importantly, we discuss the <strong>data-feed-related issues: <\/strong>we provide explanations, code examples, and recommendations on how to avoid the issues.<\/span><\/p>\n<h2><span style=\"font-weight: 400;\">Architecture &amp; Off-chain aggregation<\/span><\/h2>\n<h3><span style=\"font-weight: 400;\">Motivation<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">During the execution of a smart contract, we can&#8217;t call external APIs as we need deterministic execution for all the nodes. As such, it is problematic to access off-chain data from smart contracts. Thus, we need EOAs to post the data to the chain.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">We need three things for posting off-chain data to the chain:<\/span><\/p>\n<ol>\n<li><span style=\"font-weight: 400;\"> independence and quality of the off-chain data feeds<\/span><\/li>\n<li><span style=\"font-weight: 400;\"> decentralization of the entities that post the data to the chain<\/span><\/li>\n<li><span style=\"font-weight: 400;\"> freshness of the data<\/span><\/li>\n<\/ol>\n<p><span style=\"font-weight: 400;\">Chainlink tries to achieve this via its off-chain aggregation protocol (OCR).<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">Off-chain reporting protocol<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">OCR is a protocol built around a <strong>decentralized oracle network<\/strong>, which is a network of (hopefully) <strong>independent<\/strong> nodes. The nodes monitor <strong>multiple off-chain feeds<\/strong> to get information about current prices. As part of OCR, a leader is selected. The <strong>leader aggregates reports<\/strong> from other participants and then sends a transaction to the chain.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">If the leader <strong>misbehaves<\/strong>, <strong>he gets slashed<\/strong>. The report is sent to the Chainlink contracts. There the report is read, signatures are verified, and the median of the prices is taken:<\/span><\/p>\n<pre><code class=\"language-solidity\">int192 median = r.observations[r.observations.length\/2];\nrequire(minAnswer &lt;= median &amp;&amp; median &lt;= maxAnswer, &quot;median is out of min-max range&quot;);\nr.hotVars.latestAggregatorRoundId++;\ns_transmissions[r.hotVars.latestAggregatorRoundId] =\nTransmission(median, uint64(block.timestamp));<\/code><\/pre>\n<h3><span style=\"font-weight: 400;\">Architecture of the Data Feed contracts<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">Each feed (e.g., BTC\/ETH, ETH\/USD,..) has two main corresponding contracts &#8211; a <strong>proxy<\/strong> and an <strong>aggregator<\/strong>. If you copy a data feed address from the Chainlink <a href=\"https:\/\/docs.chain.link\/data-feeds\/price-feeds\/addresses\" target=\"_blank\" rel=\"noopener\">dashboard<\/a>, you get the address of the Proxy. The Proxy then points to the implementation &#8211; the aggregator.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Here is a diagram from the <a href=\"https:\/\/docs.chain.link\/data-feeds\/historical-data\">docs<\/a>:<\/span><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-540 aligncenter\" src=\"https:\/\/abchprod.wpengine.com\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1.png\" alt=\"\" width=\"696\" height=\"392\" srcset=\"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1.png 1200w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1-300x169.png 300w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1-1024x576.png 1024w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1-768x432.png 768w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1-370x208.png 370w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1-1170x658.png 1170w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1-972x546.png 972w, https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/2023-06-30_15-54-39_screenshot-1-760x428.png 760w\" sizes=\"auto, (max-width: 696px) 100vw, 696px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p><span style=\"font-weight: 400;\">The Proxy allows for the protocol to be <strong>upgradeable<\/strong>. The aggregator then handles all the reporting logic.<\/span><\/p>\n<h4><span style=\"font-weight: 400;\">Proxy<\/span><\/h4>\n<p><span style=\"font-weight: 400;\">For the consumer contracts, the Proxy exposes the AggregatorV3Interface. This allows the consumers to <strong>retrieve the prices<\/strong> and <strong>additional metadata<\/strong>.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">It also allows for <strong>upgrading the aggregator contract<\/strong> (the access controls are mentioned in the Trust model). During each upgrade, a phaseId variable is incremented. Using the phases, we can query even historical aggregators and, thus, historical data.<\/span><\/p>\n<h4><span style=\"font-weight: 400;\">Aggregator<\/span><\/h4>\n<p><span style=\"font-weight: 400;\">The <a href=\"https:\/\/github.com\/smartcontractkit\/libocr\/blob\/master\/contract\/AccessControlledOffchainAggregator.sol\" target=\"_blank\" rel=\"noopener\">AccessControlledOffchainAggregator<\/a><\/span><span style=\"font-weight: 400;\">\u00a0is the contract that handles the price updates. The prices are updated in two scenarios:<\/span><\/p>\n<ul>\n<li><span style=\"font-weight: 400;\">heartbeat threshold triggers (the data feeds are updated periodically)<\/span><\/li>\n<li><span style=\"font-weight: 400;\">deviation threshold triggers (off-chain values deviate by at least the threshold value and thus must be updated)<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">One price update defines one round. With each update, we increment <strong>roundId<\/strong> variable. Each update takes <em>block.timestamp<\/em> and sets it as the time of the update of the feed. This timestamp is the value that is returned as startedAt, updatedAt when calling <em>latestRoundData().<\/em><\/span><\/p>\n<h4><span style=\"font-weight: 400;\">AggregatorV3Interface<\/span><\/h4>\n<p><span style=\"font-weight: 400;\">Most protocols will interact with the Data Feeds using the AggregatorV3Interface. The API is well described in the <a href=\"https:\/\/docs.chain.link\/data-feeds\/api-reference\" target=\"_blank\" rel=\"noopener\">docs<\/a>. We will specifically focus only on <em>latestRoundData()<\/em>\u00a0function.<\/span><\/p>\n<pre><code class=\"language-solidity\">function latestRoundData() external view\nreturns (\n  uint80 roundId,\n  int256 answer,\n  uint256 startedAt,\n  uint256 updatedAt,\n  uint80 answeredInRound\n)<\/code><\/pre>\n<p><span style=\"font-weight: 400;\">The <em>roundId<\/em> and <em>answeredInRound<\/em> contain the same value which is the latest round as it was described previously. <em>answer<\/em> is the price and <em>startedAt<\/em> and <em>updatedAt<\/em> again contain the same value and correspond to the round&#8217;s timestamp as described previously.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">It can be surprising that the variables contain the same values, see the <a href=\"https:\/\/github.com\/smartcontractkit\/libocr\/blob\/c5e49b4195ac49bbf9ea3b122f24b3e19dbb8c4a\/contract\/OffchainAggregator.sol#L859-L886\" target=\"_blank\" rel=\"noopener\">code<\/a> for a proof. The same values are returned because the aggregators were upgraded, but Chainlink kept the API (i.e., previous versions behaved differently).<\/span><\/p>\n<p><span style=\"font-weight: 400;\">We even wrote a test to verify this:<\/span><\/p>\n<pre><code class=\"language-python\">#eth mainnet addrs\n\nfeeds = {\n&quot;usdt\/eth&quot; : Address(&quot;0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46&quot;),\n&quot;usdc\/eth&quot; : Address(&quot;0x986b5E1e1755e3C2440e960477f25201B0a8bbD4&quot;),\n&quot;btc\/eth&quot; : Address(&quot;0xdeb288F737066589598e9214E782fa5A8eD689e8&quot;),\n&quot;eth\/btc&quot; : Address(&quot;0xAc559F25B1619171CbC396a50854A3240b6A4e99&quot;),\n&quot;eth\/usd&quot; : Address(&quot;0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419&quot;),\n&quot;ampl\/usd&quot; : Address(&quot;0xe20CA8D7546932360e37E9D72c1a47334af57706&quot;),\n&quot;matic\/usd&quot; : Address(&quot;0x7bAC85A8a13A4BcD8abb3eB7d6b4d632c5a57676&quot;),\n&quot;ftm\/eth&quot; : Address(&quot;0x2DE7E4a9488488e0058B95854CC2f7955B35dC9b&quot;),\n}\n\n@default_chain.connect(fork=&quot; https:\/\/eth-mainnet.g.alchemy.com\/v2\/top_secret&quot;)\ndef test_get_data():\n  for type, addr in feeds.items():\n    roundId, answer, startedAt, updatedAt, answeredInRound = AggregatorV3Interface(addr).latestRoundData()\n    assert roundId == answeredInRound\n    assert startedAt == updatedAt<\/code><\/pre>\n<p><span style=\"font-weight: 400;\">The last important thing to discuss here is the <strong>decimals<\/strong>. Each pair has decimals representing the decimals (as we know them from eg, ERC20) of the price. Some feeds have 8 decimals, and some 18. There is not a clear rule on how many decimals each data feed has; see the following list of decimals of pairs we retrieved from mainnet:<\/span><\/p>\n<pre><span style=\"font-weight: 400;\">\u00a0\u00a0\u00a0\u00a0feed usdt\/eth, decimals: 18<\/span>\r\n\r\n<span style=\"font-weight: 400;\">\u00a0 \u00a0 feed usdc\/eth, decimals: 18<\/span>\r\n\r\n<span style=\"font-weight: 400;\">\u00a0\u00a0\u00a0\u00a0feed btc\/eth, decimals: 18<\/span>\r\n\r\n<span style=\"font-weight: 400;\">\u00a0\u00a0\u00a0\u00a0feed eth\/btc, decimals: 8<\/span>\r\n\r\n<span style=\"font-weight: 400;\">\u00a0\u00a0\u00a0\u00a0feed eth\/usd, decimals: 8<\/span>\r\n\r\n<span style=\"font-weight: 400;\">\u00a0\u00a0\u00a0\u00a0feed ampl\/usd, decimals: 18<\/span>\r\n\r\n<span style=\"font-weight: 400;\">\u00a0\u00a0\u00a0\u00a0feed matic\/usd, decimals: 8<\/span>\r\n\r\n<span style=\"font-weight: 400;\">\u00a0 \u00a0 feed ftm\/eth, decimals: 18<\/span><\/pre>\n<p><span style=\"font-weight: 400;\">The feeds denominated in ETH seem to have 18 decimals and feeds denominated in non-ETH have 8 decimals. <strong>However<\/strong>, at the same time we have the AMPL\/USD feed which has 18 decimals, which breaks the rule.<\/span><\/p>\n<h2><span style=\"font-weight: 400;\">Trust model<\/span><\/h2>\n<p><span style=\"font-weight: 400;\">This section will discuss the<strong> trust assumptions<\/strong> the protocols and security researchers have to consider when interacting with the Data Feeds.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">First and foremost, the Data Feeds are <strong>upgradeable<\/strong>. A Chainlink-operated multisig owns the Proxy contract of each feed. They use a Safe multig, which we found by extracting the owner address from the Proxy and <a href=\"https:\/\/etherscan.io\/address\/0x21f73d42eb58ba49ddb685dc29d3bf5c0f0373ca#code\" target=\"_blank\" rel=\"noopener\">inspecting<\/a> it on Etherscan. The multisig has 9 owners and its threshold is 4, see the <a href=\"https:\/\/evm.storage\/eth\/17598336\/0x21f73d42eb58ba49ddb685dc29d3bf5c0f0373ca\" target=\"_blank\" rel=\"noopener\">contract<\/a> on evm.storage. That means that only 4 signatures are needed to update any of the feeds arbitrarily.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Each upgrade can fail or can incorporate censorship of specific addresses.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Additionally, the nodes of the OCR protocol have to be trusted. They are supposedly decentralized and independent, but their count is much lower than the count of Ethereum nodes, so manipulation could be easier.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Also, even the Data Feeds can provide incorrect values even if the nodes are honest. This happened in the past, see the <a href=\"https:\/\/blog.chain.link\/improving-and-decentralizing-chainlinks-feature-release-and-network-upgrade-process\/?ref=blog.synthetix.io\" target=\"_blank\" rel=\"noopener\">post-mortem<\/a>.<\/span><\/p>\n<h2><span style=\"font-weight: 400;\">Issues<\/span><\/h2>\n<p><span style=\"font-weight: 400;\">In this part, we will describe all the <strong>issues<\/strong> that can arise <strong>when interacting with the Data Feeds<\/strong>. We will mainly focus on the newest <strong>V3<\/strong> version.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">1. Deprecated API<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">Some of the functions that the Data Feed contracts expose are deprecated and shouldn&#8217;t be used anymore. See the full <a href=\"https:\/\/docs.chain.link\/data-feeds\/api-reference\" target=\"_blank\" rel=\"noopener\">list<\/a>. Those functions can be removed in future upgrades and thus aren&#8217;t safe to use.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">These functions are:<\/span><\/p>\n<ul>\n<li><span style=\"font-weight: 400;\">getAnswer,<\/span><\/li>\n<li><span style=\"font-weight: 400;\">getTimestamp,<\/span><\/li>\n<li><span style=\"font-weight: 400;\">importantly <strong>latestAnswer,<\/strong><\/span><\/li>\n<li><span style=\"font-weight: 400;\">latestRound,<\/span><\/li>\n<li><span style=\"font-weight: 400;\">latestTimestamp.<\/span><\/li>\n<\/ul>\n<h3><span style=\"font-weight: 400;\">2. Receiving stale data<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">Each Data Feed can return stale data, e.g., due to a bug in an upgrade, OCR nodes not being able to come to consensus, etc. However, the <em>AggregatorV3Interface<\/em>\u00a0provides enough information to check whether the data is fresh.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">One of the values that the <em>latestRoundData<\/em> function returns is <em>updatedAt<\/em>. Additionally, we have access to <em>block.timestamp<\/em>. We get the time since the last update by subtracting these two values. We can set a maximal threshold that this difference can be. If it is greater, the data is stale, and we revert.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">3. Too wide freshness interval<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">As we explained in the previous issue, we need <strong>staleness checks<\/strong>. However, if we use too wide freshness threshold, then we won&#8217;t be able to detect stale prices in time.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The interval mustn&#8217;t be too wide. Ideally, it should be relative to the heartbeat interval of the given feed.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">4. Too narrow freshness interval<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">Additionally, we can&#8217;t decide to use <strong>a very narrow<\/strong> freshness interval. If it was decided for a freshness interval shorter then the heartbeat period, then some of the queries of the feeds would <strong>incorrectly revert<\/strong>. As a result, the user would be DoSed.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">5. Reading prices inside try\/catch block<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">As we explained, the contracts are upgradeable, and thus the calls can revert due to a <strong>bug<\/strong> or <strong>censorship<\/strong>. In such cases, the protocol can get stuck, and the users DoSed.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">If we read the prices using try\/catch we can recover from the revert and, for example, try a different oracle, e.g. TWAP.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">6. L2 sequencer downtime<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">On some L2s (like Arbitrum or Optimism), we have an entity called a <strong>sequencer<\/strong>. The sequencer is a node that receives users&#8217; transactions and posts them in a batch to the L1. Currently, almost no protocol provides decentralized sequencing, and thus their downtime is relatively possible.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">At the same time, these L2s provide an option to interact with them directly through the L1 contracts without requiring the sequencer intervention.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Chainlink provides a feed to <strong>check the sequencer downtime<\/strong>, which should be updated through the L1; see the <a href=\"https:\/\/docs.chain.link\/data-feeds\/l2-sequencer-feeds#arbitrum\" target=\"_blank\" rel=\"noopener\">docs<\/a>.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Additionally, Chainlink updates the L2 Data Feeds through the sequencer, so if it is down, the prices aren&#8217;t updated.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">We haven&#8217;t found conclusive proof on this, but it is heavily implied by the fact that Chainlink itself recommends in the <a href=\"https:\/\/docs.chain.link\/data-feeds\/l2-sequencer-feeds#example-code\" target=\"_blank\" rel=\"noopener\">docs<\/a> to check the sequencer uptime when querying the data feeds. If the data feeds weren&#8217;t updated through the sequencer, this recommendation wouldn&#8217;t be present.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Suppose that the sequencer is offline, but the users can bypass it and send their transaction directly through L1. Additionally, suppose that a protocol integrates the ETH\/USD feed and that the price of ETH drops heavily. A malicious user sees this and sends a transaction through the L1 contracts. The transaction is processed in the context of prices <\/span><span style=\"font-weight: 400;\">that were not yet updated, and thus, the user is benefiting from the still-high prices.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">See a more detailed <a href=\"https:\/\/medium.com\/@lopotras\/l2-sequencer-and-stale-oracle-prices-bug-54a749417277\" target=\"_blank\" rel=\"noopener\">blog post<\/a> about this issue.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">7. Hardcoded Data Feed addresses<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">Using <strong>hardcoded data feed addresses is dangerous<\/strong> as, nowadays, the contracts are being deployed on multiple chains. On different chains, the feeds can have different addresses.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The protocol can use invalid feed addresses if the source code isn&#8217;t modified for each deployment.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">8. Wrong interpretation of decimals<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">Some feeds have 8 decimals, some 18, and there is no clear rule on how many decimals each feed has. It is necessary to either query the <em>decimals<\/em>\u00a0function of the given feed or retrieve the information before deployment.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">It shouldn&#8217;t be assumed that a given feed has x decimals without first verifying it. Using wrong assumptions about decimals can lead to serious accounting errors.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">9. Backup oracle<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">If a call to the feed reverts (e.g., after a failed upgrade), having a backup oracle (like a TWAP) will allow the protocol to continue operating without DoSing the users.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">As auditors, we should carefully verify the backup oracles, as the corresponding <strong>logic can be undertested<\/strong>, as their usage can be assumed as a low-likelihood scenario.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">10. Low-quality feed<\/span><\/h3>\n<p><span style=\"font-weight: 400;\">Not all feeds are created equal. Some might be lower quality or can have deprecated status. Luckily, Chainlink provides <a href=\"https:\/\/docs.chain.link\/data-feeds\/selecting-data-feeds\" target=\"_blank\" rel=\"noopener\">status<\/a> <\/span><span style=\"font-weight: 400;\">for each of the feeds. It is recommended to verify the status of the feed before using it.<\/span><\/p>\n<h3><span style=\"font-weight: 400;\">11. Basic price validation<\/span><\/h3>\n<p>A large number of protocols use the following <em>require<\/em> for validating the price: <em>require(answer &gt; 0, &#8220;invalid price&#8221;);.\u00a0 <\/em>This way, a basic sanity check of the price can be implemented. Protocols can add additional requirements that the price lies in some <em>sane\u00a0<\/em>&lt;min, max&gt; interval, which could be used to avoid using the protocol during sudden price crashes (e.g. deppegging).<\/p>\n<h2><span style=\"font-weight: 400;\">Conclusion<\/span><\/h2>\n<p><span style=\"font-weight: 400;\">Chainlink oracles are<strong> the most widely used oracles<\/strong>. They have shown great <strong>reliability<\/strong>, but the protocol <strong>isn&#8217;t fully decentralized<\/strong> and has several <strong>trust assumptions<\/strong>.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Protocols that integrate the oracles can encounter various issues, such as using <strong>deprecated API<\/strong> or <strong>insufficient price data validation<\/strong>.<\/span><\/p>\n<p>Read more of our research <a href=\"https:\/\/ackee.xyz\/blog\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article provides a holistic view of the Chainlink Data Feeds, focusing\u00a0on security. The article targets mainly EVM and Solidity and has\u00a0three main parts: Architecture of the feeds and off-chain aggregation protocol Trust model Common data-feed-related issues Firstly, we introduce the problems Chainlink solves and how they solve them: the core idea of the off-chain aggregation protocol is explained, and the core&hellip;<\/p>\n","protected":false},"author":19,"featured_media":682,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61,10,80],"tags":[93,94,96,24,33,68],"class_list":["post-541","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-education","category-ethereum","category-solidity","tag-chainlink","tag-data","tag-educational","tag-ethereum","tag-evm","tag-solidity"],"aioseo_notices":[],"featured_image_src":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/Chainlink-600x400.png","featured_image_src_square":"https:\/\/ackee.xyz\/blog\/wp-content\/uploads\/2023\/08\/Chainlink-600x600.png","author_info":{"display_name":"Miroslav Skrabal","author_link":"https:\/\/ackee.xyz\/blog\/author\/miroslav\/"},"_links":{"self":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/541","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/users\/19"}],"replies":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/comments?post=541"}],"version-history":[{"count":0,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/posts\/541\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media\/682"}],"wp:attachment":[{"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/media?parent=541"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/categories?post=541"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ackee.xyz\/blog\/wp-json\/wp\/v2\/tags?post=541"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}