QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: low
Invalid

ChainlinkOracle.sol Price Feed Lacks Deviation Checks Allowing Price Manipulation

Summary

ChainlinkOracle lacks price deviation checks, allowing manipulated price data to be accepted without validation against historical or expected values.

Vulnerability Details

The getData() function accepts any non-zero price without validating price movements:

function _getData() internal view override returns (int216, uint40) {
(, /*uint80 roundID*/ int data, , /*uint startedAt*/ uint timestamp, ) = /*uint80 answeredInRound*/
priceFeed.latestRoundData();
require(data > 0, "INVLDDATA");
data = data * int(10 ** normalizationFactor);
return (int216(data), uint40(timestamp)); // Overflow of data is extremely improbable and uint40 is large enough for timestamps for a very long time
}
}

prices can be arbitrarily manipulated:

Proof of concept

function testPriceManipulation() public {
int256 initialPrice = 1000e8;
int256 manipulatedPrice = 500e8;
console.log("Initial price:", uint256(int256(initialPrice)));
(int216 price1,) = oracle.getData();
assertEq(price1, 1000e18, "Initial price should be 1000");
mockFeed.setPrice(manipulatedPrice);
console.log("Manipulated price:", uint256(int256(manipulatedPrice)));
(int216 price2,) = oracle.getData();
assertEq(price2, 500e18, "Should accept manipulated price");
}
}

results:

[PASS] testPriceManipulation() (gas: 35864)
Logs:
Initial price: 100000000000
Manipulated price: 50000000000
Traces:
[35864] ChainlinkOracleTest::testPriceManipulation()
├─ [0] console::log("Initial price:", 100000000000 [1e11]) [staticcall]
│ └─ ← [Stop]
├─ [14656] ChainlinkOracle::getData() [staticcall]
│ ├─ [10855] MockChainlinkFeed::latestRoundData() [staticcall]
│ │ └─ ← [Return] 1, 100000000000 [1e11], 0, 1, 1
│ └─ ← [Return] 1000000000000000000000 [1e21], 1
├─ [0] VM::assertEq(1000000000000000000000 [1e21], 1000000000000000000000 [1e21], "Initial price should be 1000") [staticcall]
│ └─ ← [Return]
├─ [3154] MockChainlinkFeed::setPrice(50000000000 [5e10])
│ └─ ← [Stop]
├─ [0] console::log("Manipulated price:", 50000000000 [5e10]) [staticcall]
│ └─ ← [Stop]
├─ [2156] ChainlinkOracle::getData() [staticcall]
│ ├─ [855] MockChainlinkFeed::latestRoundData() [staticcall]
│ │ └─ ← [Return] 1, 50000000000 [5e10], 0, 1, 1
│ └─ ← [Return] 500000000000000000000 [5e20], 1
├─ [0] VM::assertEq(500000000000000000000 [5e20], 500000000000000000000 [5e20], "Should accept manipulated price") [staticcall]
│ └─ ← [Return]
└─ ← [Stop]

Impact

Unchecked price deviations enable:

Flash loan attacks via price manipulation

Malicious liquidations through artificial price movements

Protocol insolvency from trades at manipulated prices

Tools Used

Foundry testing framework

Manual code review

Recommendations

Add deviation threshold checks:

function getData() public view returns (int216, uint40) {
(,int data, , uint timestamp,) = priceFeed.latestRoundData();
require(data > 0, "INVLDDATA");
int256 prevPrice = getPreviousRoundPrice();
int256 maxDeviation = (prevPrice * 10) / 100; // 10% max deviation
require(abs(data - prevPrice) <= maxDeviation, "INVALID_DEVIATION");
data = data * int(10 ** normalizationFactor);
return (int216(data), uint40(timestamp));
}
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

invalid_chainlink_min_max_no_check

LightChaser: ## [Low-25] Chainlink answer is not compared against min/max values

Appeal created

nomadic_bear Submitter
7 months ago
n0kto Lead Judge
6 months ago
n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

invalid_chainlink_min_max_no_check

LightChaser: ## [Low-25] Chainlink answer is not compared against min/max values

Support

FAQs

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