function test_POC_StaleManipulatedPricesCauseDirectUnwindRevert() public {
vm.warp(30 days);
vm.mockCall(USDC, abi.encodeWithSignature("decimals()"), abi.encode(uint8(6)));
vm.mockCall(WETH, abi.encodeWithSignature("decimals()"), abi.encode(uint8(18)));
uint256 debtAmount = 10 ether;
uint256 liqThreshold = 8000;
vm.mockCall(
AAVE_PROTOCOL_DATA_PROVIDER,
abi.encodeWithSignature("getReserveConfigurationData(address)", USDC),
abi.encode(uint256(6), uint256(8000), liqThreshold, uint256(10500), uint256(1000), true, true, false, true, false)
);
uint256 staleUpdatedAt = block.timestamp - 20 days;
MockConfigurablePriceFeed staleWethFeed =
new MockConfigurablePriceFeed(100, int256(4000e8), staleUpdatedAt, 100);
MockConfigurablePriceFeed staleUsdcFeed =
new MockConfigurablePriceFeed(200, int256(5e7), staleUpdatedAt, 200);
strataxOracle.setPriceFeed(WETH, address(staleWethFeed));
strataxOracle.setPriceFeed(USDC, address(staleUsdcFeed));
uint256 expectedWithdraw = (debtAmount * 4000e8 * (10 ** 6) * 1e4) / (5e7 * (10 ** 18) * liqThreshold);
vm.mockCall(WETH, abi.encodeWithSignature("approve(address,uint256)", AAVE_POOL, debtAmount), abi.encode(true));
vm.mockCall(
AAVE_POOL,
abi.encodeWithSignature("repay(address,uint256,uint256,address)", WETH, debtAmount, 2, address(stratax)),
abi.encode(debtAmount)
);
vm.mockCallRevert(
AAVE_POOL,
abi.encodeWithSignature("withdraw(address,uint256,address)", USDC, expectedWithdraw, address(stratax)),
abi.encodeWithSignature("Error(string)", "withdraw exceeds available collateral")
);
Stratax.UnwindParams memory unwindParams = Stratax.UnwindParams({
collateralToken: USDC,
collateralToWithdraw: 0,
debtToken: WETH,
debtAmount: debtAmount,
oneInchSwapData: bytes(""),
minReturnAmount: 0
});
bytes memory encodedParams = abi.encode(Stratax.OperationType.UNWIND, ownerTrader, unwindParams);
vm.prank(AAVE_POOL);
vm.expectRevert(bytes("withdraw exceeds available collateral"));
stratax.executeOperation(WETH, debtAmount, 0, address(stratax), encodedParams);
}
// StrataxOracle.sol
+ uint256 public immutable MAX_STALE_TIME;
constructor(uint256 _maxStaleTime){
+ require(_maxStaleTime > 0, "Invalid stale time");
//...
+ MAX_STALE_TIME = _maxStaleTime;
}
function getPrice(address _token) public view returns (uint256 price) {
address priceFeedAddress = priceFeeds[_token];
require(priceFeedAddress != address(0), "Price feed not set for token");
AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeedAddress);
- (int256 answer,,,) = priceFeed.latestRoundData();
+ (uint80 roundId, int256 answer,, uint256 updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData();
require(answer > 0, "Invalid price from oracle");
// @> should check price freshness
+ require(updatedAt != 0 && block.timestamp - updatedAt <= MAX_STALE_TIME, "Price is stale");
+ require(answeredInRound >= roundId, "Price is stale");
price = uint256(answer);
}