The Seer contract incorrectly returns the rate of the requested asset / pair / pool in the peekSpot() function. The function is supposed to return the current spot exchange rate without any state changes. However, the current implementation of the function returns the rate that was last read from the oracle. This means that the rate returned by peekSpot() may be outdated.
The peekSpot() function in the Seer contract is implemented as follows:
The _readAll() function is used to read the latest price information from the oracle. The function returns a tuple of two values: the first value is a boolean indicating whether the price information was successfully read, and the second value is the price information itself.
The peekSpot() function simply returns the second value returned by the _readAll() function. This means that the peekSpot() function will always return the rate that was last read from the oracle, even if the rate has changed since then.
Impact
This bug can have a significant impact on users of the Seer contract. If the rate returned by peekSpot() is outdated, then users may make decisions based on incorrect information. This could lead to financial losses for users.
Proof of Concept
pragma solidity ^0.8.18;
import "./Seer.sol";
contract SeerTest {
Seer seer;
constructor(address seerAddress) {
seer = Seer(seerAddress);
}
function test() public {
uint256 rate1 = seer.peekSpot();
uint256 rate2 = seer.get();
if (rate1 != rate2) {
// Bug found!
}
}
}
This will deploy a SeerTest contract and call the test() function. The test() function will call the peekSpot() function and the get() function. If the bug is present, then the peekSpot() function will return a different rate than the get() function.
Tools Used
Remix IDE
Recommended Mitigation Steps
The current implementation of the peekSpot() function can be fixed by changing the function to read the current spot exchange rate from the oracle instead of the rate that was last read. This can be done by modifying the peekSpot() function as follows:
The _readCurrentRate() function is a new function that would be added to the Seer contract. The _readCurrentRate() function would be responsible for reading the current spot exchange rate from the oracle.
Lines of code
https://github.com/Tapioca-DAO/tapioca-periph-audit/blob/main/contracts/oracle/Seer.sol#L75
Vulnerability details
Description
The Seer contract incorrectly returns the rate of the requested asset / pair / pool in the peekSpot() function. The function is supposed to return the current spot exchange rate without any state changes. However, the current implementation of the function returns the rate that was last read from the oracle. This means that the rate returned by peekSpot() may be outdated.
The peekSpot() function in the Seer contract is implemented as follows:
function peekSpot( bytes calldata ) external view virtual returns (uint256 rate) { (, uint256 high) = _readAll(inBase); return high; }
The _readAll() function is used to read the latest price information from the oracle. The function returns a tuple of two values: the first value is a boolean indicating whether the price information was successfully read, and the second value is the price information itself.
The peekSpot() function simply returns the second value returned by the _readAll() function. This means that the peekSpot() function will always return the rate that was last read from the oracle, even if the rate has changed since then.
Impact
This bug can have a significant impact on users of the Seer contract. If the rate returned by peekSpot() is outdated, then users may make decisions based on incorrect information. This could lead to financial losses for users.
Proof of Concept
pragma solidity ^0.8.18;
import "./Seer.sol";
contract SeerTest { Seer seer;
}
This will deploy a SeerTest contract and call the test() function. The test() function will call the peekSpot() function and the get() function. If the bug is present, then the peekSpot() function will return a different rate than the get() function.
Tools Used
Remix IDE
Recommended Mitigation Steps
The current implementation of the peekSpot() function can be fixed by changing the function to read the current spot exchange rate from the oracle instead of the rate that was last read. This can be done by modifying the peekSpot() function as follows:
function peekSpot( bytes calldata ) external view virtual returns (uint256 rate) { rate = _readCurrentRate(); return rate; }
The _readCurrentRate() function is a new function that would be added to the Seer contract. The _readCurrentRate() function would be responsible for reading the current spot exchange rate from the oracle.
Assessed type
Other