Skip to content

Commit 9875126

Browse files
authored
Merge pull request #20 from Gearbox-protocol/price-feed-store-tests
fix: price-feed-store tests
2 parents 2c1063c + bf83095 commit 9875126

File tree

4 files changed

+285
-8
lines changed

4 files changed

+285
-8
lines changed

contracts/global/PriceFeedStore.sol

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ contract PriceFeedStore is ImmutableOwnableTrait, SanityCheckTrait, PriceFeedVal
4040
mapping(address => EnumerableSet.AddressSet) internal _allowedPriceFeeds;
4141

4242
/// @notice Mapping from price feed address to its data
43-
mapping(address => PriceFeedInfo) public priceFeedInfo;
43+
mapping(address => PriceFeedInfo) internal _priceFeedInfo;
4444

4545
constructor(address _addressProvider)
4646
ImmutableOwnableTrait(
@@ -60,13 +60,17 @@ contract PriceFeedStore is ImmutableOwnableTrait, SanityCheckTrait, PriceFeedVal
6060

6161
/// @notice Returns the staleness period for a price feed
6262
function getStalenessPeriod(address priceFeed) external view returns (uint32) {
63-
return priceFeedInfo[priceFeed].stalenessPeriod;
63+
return _priceFeedInfo[priceFeed].stalenessPeriod;
6464
}
6565

6666
function getKnownTokens() external view returns (address[] memory) {
6767
return _knownTokens.values();
6868
}
6969

70+
function getKnownPriceFeeds() external view returns (address[] memory) {
71+
return _knownPriceFeeds.values();
72+
}
73+
7074
/**
7175
* @notice Adds a new price feed
7276
* @param priceFeed The address of the new price feed
@@ -90,10 +94,10 @@ contract PriceFeedStore is ImmutableOwnableTrait, SanityCheckTrait, PriceFeedVal
9094
}
9195

9296
_knownPriceFeeds.add(priceFeed);
93-
priceFeedInfo[priceFeed].author = msg.sender;
94-
priceFeedInfo[priceFeed].priceFeedType = priceFeedType;
95-
priceFeedInfo[priceFeed].stalenessPeriod = stalenessPeriod;
96-
priceFeedInfo[priceFeed].version = priceFeedVersion;
97+
_priceFeedInfo[priceFeed].author = msg.sender;
98+
_priceFeedInfo[priceFeed].priceFeedType = priceFeedType;
99+
_priceFeedInfo[priceFeed].stalenessPeriod = stalenessPeriod;
100+
_priceFeedInfo[priceFeed].version = priceFeedVersion;
97101

98102
emit AddPriceFeed(priceFeed, stalenessPeriod);
99103
}
@@ -110,11 +114,11 @@ contract PriceFeedStore is ImmutableOwnableTrait, SanityCheckTrait, PriceFeedVal
110114
nonZeroAddress(priceFeed)
111115
{
112116
if (!_knownPriceFeeds.contains(priceFeed)) revert PriceFeedNotKnownException(priceFeed);
113-
uint32 oldStalenessPeriod = priceFeedInfo[priceFeed].stalenessPeriod;
117+
uint32 oldStalenessPeriod = _priceFeedInfo[priceFeed].stalenessPeriod;
114118

115119
if (stalenessPeriod != oldStalenessPeriod) {
116120
_validatePriceFeed(priceFeed, stalenessPeriod);
117-
priceFeedInfo[priceFeed].stalenessPeriod = stalenessPeriod;
121+
_priceFeedInfo[priceFeed].stalenessPeriod = stalenessPeriod;
118122
emit SetStalenessPeriod(priceFeed, stalenessPeriod);
119123
}
120124
}
@@ -129,6 +133,7 @@ contract PriceFeedStore is ImmutableOwnableTrait, SanityCheckTrait, PriceFeedVal
129133
if (!_knownPriceFeeds.contains(priceFeed)) revert PriceFeedNotKnownException(priceFeed);
130134

131135
_allowedPriceFeeds[token].add(priceFeed);
136+
_knownTokens.add(token);
132137

133138
emit AllowPriceFeed(token, priceFeed);
134139
}
@@ -147,4 +152,8 @@ contract PriceFeedStore is ImmutableOwnableTrait, SanityCheckTrait, PriceFeedVal
147152

148153
emit ForbidPriceFeed(token, priceFeed);
149154
}
155+
156+
function priceFeedInfo(address priceFeed) external view returns (PriceFeedInfo memory) {
157+
return _priceFeedInfo[priceFeed];
158+
}
150159
}
File renamed without changes.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.23;
3+
4+
import {PriceFeedInfo} from "../../interfaces/Types.sol";
5+
6+
import {Test} from "forge-std/Test.sol";
7+
import {PriceFeedStore} from "../../global/PriceFeedStore.sol";
8+
import {IPriceFeedStore} from "../../interfaces/IPriceFeedStore.sol";
9+
import {IAddressProvider} from "../../interfaces/IAddressProvider.sol";
10+
import {MockPriceFeed} from "../mocks/MockPriceFeed.sol";
11+
import {AP_INSTANCE_MANAGER_PROXY, NO_VERSION_CONTROL} from "../../libraries/ContractLiterals.sol";
12+
import {ImmutableOwnableTrait} from "../../traits/ImmutableOwnableTrait.sol";
13+
import {
14+
ZeroAddressException,
15+
StalePriceException,
16+
IncorrectPriceException,
17+
IncorrectPriceFeedException
18+
} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol";
19+
20+
contract PriceFeedStoreTest is Test {
21+
PriceFeedStore public store;
22+
address public owner;
23+
address public token;
24+
MockPriceFeed public priceFeed;
25+
IAddressProvider public addressProvider;
26+
27+
function setUp() public {
28+
owner = makeAddr("owner");
29+
token = makeAddr("token");
30+
priceFeed = new MockPriceFeed();
31+
32+
vm.mockCall(
33+
address(addressProvider),
34+
abi.encodeWithSignature(
35+
"getAddressOrRevert(bytes32,uint256)", AP_INSTANCE_MANAGER_PROXY, NO_VERSION_CONTROL
36+
),
37+
abi.encode(owner)
38+
);
39+
40+
store = new PriceFeedStore(address(addressProvider));
41+
}
42+
43+
/// @notice Test basic price feed addition flow
44+
function test_PFS_01_addPriceFeed_works() public {
45+
uint32 stalenessPeriod = 3600;
46+
47+
vm.prank(owner);
48+
store.addPriceFeed(address(priceFeed), stalenessPeriod);
49+
50+
// Verify price feed was added correctly
51+
assertEq(store.getStalenessPeriod(address(priceFeed)), stalenessPeriod);
52+
53+
// Get price feed info
54+
PriceFeedInfo memory priceFeedInfo = store.priceFeedInfo(address(priceFeed));
55+
store.priceFeedInfo(address(priceFeed));
56+
57+
// Verify all parameters were set correctly
58+
assertEq(priceFeedInfo.author, owner);
59+
assertEq(priceFeedInfo.priceFeedType, "MOCK_PRICE_FEED");
60+
assertEq(priceFeedInfo.stalenessPeriod, stalenessPeriod);
61+
assertEq(priceFeedInfo.version, 1);
62+
63+
// Verify price feed is in known list
64+
address[] memory knownPriceFeeds = store.getKnownPriceFeeds();
65+
assertEq(knownPriceFeeds.length, 1);
66+
assertEq(knownPriceFeeds[0], address(priceFeed));
67+
}
68+
69+
/// @notice Test that only owner can add price feeds
70+
function test_PFS_02_addPriceFeed_reverts_if_not_owner() public {
71+
address notOwner = makeAddr("notOwner");
72+
vm.prank(notOwner);
73+
vm.expectRevert(ImmutableOwnableTrait.NotOwnerException.selector);
74+
store.addPriceFeed(address(priceFeed), 3600);
75+
}
76+
77+
/// @notice Test that zero address price feed cannot be added
78+
function test_PFS_03_addPriceFeed_reverts_on_zero_address() public {
79+
vm.prank(owner);
80+
vm.expectRevert(ZeroAddressException.selector);
81+
store.addPriceFeed(address(0), 3600);
82+
}
83+
84+
/// @notice Test duplicate price feed addition is prevented
85+
function test_PFS_04_addPriceFeed_reverts_on_duplicate() public {
86+
vm.startPrank(owner);
87+
store.addPriceFeed(address(priceFeed), 3600);
88+
89+
vm.expectRevert(
90+
abi.encodeWithSelector(IPriceFeedStore.PriceFeedAlreadyAddedException.selector, address(priceFeed))
91+
);
92+
store.addPriceFeed(address(priceFeed), 3600);
93+
vm.stopPrank();
94+
}
95+
96+
/// @notice Test staleness period validation
97+
function test_PFS_05_addPriceFeed_validates_staleness() public {
98+
MockPriceFeed stalePriceFeed = new MockPriceFeed();
99+
stalePriceFeed.setLastUpdateTime(block.timestamp);
100+
101+
vm.warp(block.timestamp + 7200);
102+
103+
vm.prank(owner);
104+
vm.expectRevert(StalePriceException.selector);
105+
store.addPriceFeed(address(stalePriceFeed), 3600);
106+
}
107+
108+
/// @notice Test price feed allowance for tokens
109+
function test_PFS_06_allowPriceFeed_works() public {
110+
vm.startPrank(owner);
111+
store.addPriceFeed(address(priceFeed), 3600);
112+
store.allowPriceFeed(token, address(priceFeed));
113+
vm.stopPrank();
114+
115+
assertTrue(store.isAllowedPriceFeed(token, address(priceFeed)));
116+
}
117+
118+
/// @notice Test only owner can allow price feeds
119+
function test_PFS_07_allowPriceFeed_reverts_if_not_owner() public {
120+
vm.prank(owner);
121+
store.addPriceFeed(address(priceFeed), 3600);
122+
123+
address notOwner = makeAddr("notOwner");
124+
vm.prank(notOwner);
125+
vm.expectRevert(ImmutableOwnableTrait.NotOwnerException.selector);
126+
store.allowPriceFeed(token, address(priceFeed));
127+
}
128+
129+
/// @notice Test unknown price feeds cannot be allowed
130+
function test_PFS_08_allowPriceFeed_reverts_on_unknown_feed() public {
131+
vm.prank(owner);
132+
vm.expectRevert(abi.encodeWithSelector(IPriceFeedStore.PriceFeedNotKnownException.selector, address(priceFeed)));
133+
store.allowPriceFeed(token, address(priceFeed));
134+
}
135+
136+
/// @notice Test price feed forbidding
137+
function test_PFS_09_forbidPriceFeed_works() public {
138+
vm.startPrank(owner);
139+
store.addPriceFeed(address(priceFeed), 3600);
140+
store.allowPriceFeed(token, address(priceFeed));
141+
store.forbidPriceFeed(token, address(priceFeed));
142+
vm.stopPrank();
143+
144+
assertFalse(store.isAllowedPriceFeed(token, address(priceFeed)));
145+
}
146+
147+
/// @notice Test only owner can forbid price feeds
148+
function test_PFS_10_forbidPriceFeed_reverts_if_not_owner() public {
149+
vm.startPrank(owner);
150+
store.addPriceFeed(address(priceFeed), 3600);
151+
store.allowPriceFeed(token, address(priceFeed));
152+
vm.stopPrank();
153+
154+
address notOwner = makeAddr("notOwner");
155+
vm.prank(notOwner);
156+
vm.expectRevert(ImmutableOwnableTrait.NotOwnerException.selector);
157+
store.forbidPriceFeed(token, address(priceFeed));
158+
}
159+
160+
/// @notice Test staleness period updates
161+
function test_PFS_11_setStalenessPeriod_works() public {
162+
vm.startPrank(owner);
163+
store.addPriceFeed(address(priceFeed), 3600);
164+
store.setStalenessPeriod(address(priceFeed), 7200);
165+
vm.stopPrank();
166+
167+
assertEq(store.getStalenessPeriod(address(priceFeed)), 7200);
168+
}
169+
170+
/// @notice Test staleness period validation on update
171+
function test_PFS_12_setStalenessPeriod_validates_staleness() public {
172+
vm.startPrank(owner);
173+
store.addPriceFeed(address(priceFeed), 3600);
174+
175+
priceFeed.setLastUpdateTime(block.timestamp);
176+
vm.warp(block.timestamp + 7200);
177+
178+
vm.expectRevert(StalePriceException.selector);
179+
store.setStalenessPeriod(address(priceFeed), 3601);
180+
vm.stopPrank();
181+
}
182+
183+
/// @notice Test token list management
184+
function test_PFS_13_maintains_token_list() public {
185+
vm.startPrank(owner);
186+
store.addPriceFeed(address(priceFeed), 3600);
187+
store.allowPriceFeed(token, address(priceFeed));
188+
vm.stopPrank();
189+
190+
address[] memory knownTokens = store.getKnownTokens();
191+
assertEq(knownTokens.length, 1);
192+
assertEq(knownTokens[0], token);
193+
}
194+
195+
/// @notice Test multiple price feeds per token
196+
function test_PFS_14_allows_multiple_feeds_per_token() public {
197+
MockPriceFeed priceFeed2 = new MockPriceFeed();
198+
199+
vm.startPrank(owner);
200+
store.addPriceFeed(address(priceFeed), 3600);
201+
store.addPriceFeed(address(priceFeed2), 3600);
202+
store.allowPriceFeed(token, address(priceFeed));
203+
store.allowPriceFeed(token, address(priceFeed2));
204+
vm.stopPrank();
205+
206+
address[] memory feeds = store.getPriceFeeds(token);
207+
assertEq(feeds.length, 2);
208+
assertTrue(store.isAllowedPriceFeed(token, address(priceFeed)));
209+
assertTrue(store.isAllowedPriceFeed(token, address(priceFeed2)));
210+
}
211+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.23;
3+
4+
import {IPriceFeed} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IPriceFeed.sol";
5+
6+
contract MockPriceFeed is IPriceFeed {
7+
uint256 private _lastUpdateTime;
8+
int256 private _price;
9+
bytes32 private constant _CONTRACT_TYPE = "MOCK_PRICE_FEED";
10+
uint256 private constant _VERSION = 1;
11+
12+
constructor() {
13+
_lastUpdateTime = block.timestamp;
14+
_price = 1e18; // Default price of 1
15+
}
16+
17+
function lastUpdateTime() external view returns (uint256) {
18+
return _lastUpdateTime;
19+
}
20+
21+
function version() external pure returns (uint256) {
22+
return _VERSION;
23+
}
24+
25+
function contractType() external pure returns (bytes32) {
26+
return _CONTRACT_TYPE;
27+
}
28+
29+
function latestRoundData()
30+
external
31+
view
32+
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
33+
{
34+
return (0, _price, _lastUpdateTime, _lastUpdateTime, 0);
35+
}
36+
37+
// Test helper functions
38+
function setLastUpdateTime(uint256 timestamp) external {
39+
_lastUpdateTime = timestamp;
40+
}
41+
42+
function setPrice(int256 newPrice) external {
43+
_price = newPrice;
44+
}
45+
46+
function decimals() external pure returns (uint8) {
47+
return 8; // Standard 8 decimals for USD oracles
48+
}
49+
50+
function description() external pure returns (string memory) {
51+
return "Mock Price Feed";
52+
}
53+
54+
function skipPriceCheck() external pure returns (bool) {
55+
return false;
56+
}
57+
}

0 commit comments

Comments
 (0)