Skip to content

Commit eaeaa60

Browse files
committed
add additional basic protective validations on oracle price
1 parent f67d823 commit eaeaa60

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

src/FixedStakingRewards.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ error NotEnoughRewards(uint256 available, uint256 required);
1717
error RewardsNotAvailableYet(uint256 currentTime, uint256 availableTime);
1818
error CannotWithdrawZero();
1919
error CannotWithdrawStakingToken(address attemptedToken);
20+
error InvalidPriceFeed(uint256 updateTime, int256 currentRewardTokenRate);
2021

2122
contract FixedStakingRewards is IStakingRewards, ERC20, ReentrancyGuard, Ownable {
2223
using SafeERC20 for IERC20;
@@ -156,7 +157,10 @@ contract FixedStakingRewards is IStakingRewards, ERC20, ReentrancyGuard, Ownable
156157

157158
/* ========== INTERNAL FUNCTIONS ========== */
158159
function _rebalance() internal {
159-
(, int256 currentRewardTokenRate, , , ) = rewardsTokenRateAggregator.latestRoundData();
160+
(, int256 currentRewardTokenRate, , uint256 updateTime, ) = rewardsTokenRateAggregator.latestRoundData();
161+
if (currentRewardTokenRate == 0 || updateTime < block.timestamp - 1 hours) {
162+
revert InvalidPriceFeed(updateTime, currentRewardTokenRate);
163+
}
160164
rewardRate = targetRewardApy * 1e18 / (uint256(currentRewardTokenRate) * 10 ** (18 - rewardsTokenRateDecimals)) / 365 days;
161165
}
162166

test/FixedStakingRewards.t.sol

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
NotEnoughRewards,
1414
RewardsNotAvailableYet,
1515
CannotWithdrawZero,
16-
CannotWithdrawStakingToken
16+
CannotWithdrawStakingToken,
17+
InvalidPriceFeed
1718
} from "../src/FixedStakingRewards.sol";
1819

1920

@@ -172,6 +173,8 @@ contract FixedStakingRewardsTest is Test {
172173
event Recovered(address token, uint256 amount);
173174

174175
function setUp() public {
176+
vm.warp(1000000000);
177+
175178
owner = address(this);
176179
user1 = makeAddr("user1");
177180
user2 = makeAddr("user2");
@@ -597,6 +600,7 @@ contract FixedStakingRewardsTest is Test {
597600
assertEq(earnedAfterRateChange, totalExpectedRewards);
598601

599602
// stake again to make sure rewards are preserved
603+
mockAggregator.setLatestAnswer(1e8 / 2, block.timestamp);
600604
vm.startPrank(user1);
601605
stakingToken.approve(address(stakingRewards), 200e18);
602606
stakingRewards.stake(200e18);
@@ -678,6 +682,7 @@ contract FixedStakingRewardsTest is Test {
678682

679683
vm.warp(block.timestamp + 1 days);
680684

685+
mockAggregator.setLatestAnswer(1e8, block.timestamp);
681686
stakingRewards.reclaim();
682687

683688
assertEq(rewardsToken.balanceOf(owner), initialBalance + amount);
@@ -762,6 +767,44 @@ contract FixedStakingRewardsTest is Test {
762767
assertEq(stakingRewards.rewardRate(), 0);
763768
}
764769

770+
function test_Rebalance_RevertWhen_PriceFeedReturnsZero() public {
771+
// Set target APY
772+
stakingRewards.setRewardYieldForYear(1e18);
773+
774+
// Set aggregator to return zero rate
775+
mockAggregator.setLatestAnswer(0, block.timestamp);
776+
777+
// Rebalance should revert with InvalidPriceFeed
778+
vm.expectRevert(abi.encodeWithSelector(InvalidPriceFeed.selector, block.timestamp, int256(0)));
779+
stakingRewards.rebalance();
780+
}
781+
782+
function test_Rebalance_RevertWhen_PriceFeedIsStale() public {
783+
// Set target APY
784+
stakingRewards.setRewardYieldForYear(1e18);
785+
786+
// Set aggregator with stale data (2 hours old)
787+
uint256 staleTimestamp = block.timestamp - 2 hours;
788+
mockAggregator.setLatestAnswer(1e8 / 2, staleTimestamp);
789+
790+
// Rebalance should revert with InvalidPriceFeed
791+
vm.expectRevert(abi.encodeWithSelector(InvalidPriceFeed.selector, staleTimestamp, int256(1e8 / 2)));
792+
stakingRewards.rebalance();
793+
}
794+
795+
function test_Rebalance_RevertWhen_PriceFeedIsStaleExactly1Hour() public {
796+
// Set target APY
797+
stakingRewards.setRewardYieldForYear(1e18);
798+
799+
// Set aggregator with data exactly 1 hour old (should still revert)
800+
uint256 staleTimestamp = block.timestamp - 1 hours - 1;
801+
mockAggregator.setLatestAnswer(1e8 / 2, staleTimestamp);
802+
803+
// Rebalance should revert with InvalidPriceFeed
804+
vm.expectRevert(abi.encodeWithSelector(InvalidPriceFeed.selector, staleTimestamp, int256(1e8 / 2)));
805+
stakingRewards.rebalance();
806+
}
807+
765808
/*//////////////////////////////////////////////////////////////
766809
MODIFIER TESTS
767810
//////////////////////////////////////////////////////////////*/

0 commit comments

Comments
 (0)