15,000 USDC
View results
Submission Details
Severity: medium
Valid

Chainlink Oracle Centralization

Summary

Using Chainlink as a single external price oracle creates a single point of failure.

Impact

This can lead to potential manipulation if the oracle is compromised or becomes inaccurate. A more robust architecture should consider multiple price oracles and aggregating their prices to provide a better price estimate. This would reduce the dependency on Chainlink and the potential impact if Chainlink were to fail or be manipulated. While incorporating multiple oracles does increase the complexity of the system, the added redundancy would greatly enhance the system's resilience and, in my opinion, user interaction.

Recommendations

Use an aggregate of oracles, both centralized and decentralized such as Band Protocol, UMA's Optimistic Oracle, and API3. Using multiple sources would minimize the risk associated with any single oracle failure and/or oracle manipulation attacks. Or consider a fallback oracle in case Chainlink goes down; and/or implementing time-weighted average prices (TWAPs). Manipulation will always be possible, even with TWAPs but a system with checks and balances is better than a system with a single point of failure.

Vulnerability Details

While critical to the overall protocol, I believe this is an obvious known issue among the developers; however, I point this out, because as a consumer wanting to trade or interact with the protocol, knowing it relies solely on a single oracle may not promote or encourage user involvement. Both Dai and Aave for example (which this protocol is loosely based upon) use governance tokens and thus users feel they can participate and there are multiple checks. For example, it can be seen how the price feeds (multiple) are implemented in MakerDao.

Sample Implementation

This is meant to be a sample implementation.

  1. Add a secondary oracle to the mapping:

77-80: mapping(address => address) private s_secondaryPriceFeeds;
  1. Include addresses for secondary price feeds in the constructor.

112: constructor(address[] memory tokenAddresses, address[] memory priceFeedAddresses, address[] memory secondaryPriceFeedAddresses, address dscAddress) {
// ...
  1. Create a function, or add an if check in a setter function, to switch to the secondary price feed; possibly adding an authorization check using the onlyOwner modifier to restrict contract functionality.

function switchToSecondaryPriceFeed(address token) external [onlyOwner option] {
// Add checks/effects/interactions
require(s_secondaryPriceFeeds[token] != AggregatorV3Interface(address(0)), DSCEngine__custom_error);
s_priceFeeds[token] = s_secondaryPriceFeeds[token];
}
  1. Update the getPrice and getTokenAmountFromUsd functions to reflect the secondary price feed.

Unintended Consequences

Relying singularly on Chainlink, may not promote a broader usage of the protocol. Moreover, as the stablecoin's collateral grows (as it's intended) so too does the likeliness and willingness of an attack. Furthermore, regarding scale, large institutions may be skeptical, as providing liquidity to this protocol, could be seen as an obfuscated way of investing in Chainlink itself.

Tools Used

Manual review, referring to: Band Protocol, UMA's Optimistic Oracle, API3, MakerDao's price feed implementation.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.