Skip to content

Commit 02a9fe1

Browse files
Jaiclaude
andcommitted
fix(audit-low): code-correctness sweep — 10 LOW findings in one pass
- #36: OracleRegistry.setOracleBulk gains MAX_BULK_LENGTH = 256 cap + BulkLengthExceeded(length, max) error. - #37 / #38 / #155: OracleUnifiedDeployer + MultiOracleUnifiedDeployer gain symmetric zero-address guards on every LibProdDeploy sub-deployer constant with typed errors (Pyth/Morpho/Passthrough on the single-feed deployer; Morpho/Passthrough extended onto the multi-feed deployer alongside the existing MultiPyth guard). - #40: Deployment(...) event params indexed across all 5 *BeaconSetDeployer + both *UnifiedDeployer. - #41 / #179 / #180: MultiPythOracleAdapter._setFeeds validates all entries before mutating, writes feedCount last, and internalises MAX_FEEDS / non-empty checks so initialize and setFeeds become thin wrappers. - #43: BasePythOracleAdapter.latestRoundData encodes uint80(uint64(publishTime)) into roundId and answeredInRound for Chainlink monotonic-id convention. - #148: BasePythOracleAdapter enforces the "immutable after initialize" invariant on CorporateActionPauseConfig with a one-shot flag + CorporateActionConfigAlreadyInitialized error. - #149: CorporateActionPauseConfigSet event emitted from _setCorporateActionPauseConfig so indexers observe the install. - #173: Deployment event param renamed `sender` → `caller` across all 5 BeaconSetDeployer + both UnifiedDeployer (NatSpec @param describes it as the direct on-chain caller). All behaviour changes have a test that fails against pre-fix and passes after. forge build / fmt / slither / REUSE green. Test count delta: 214 → 231 (+17). Closes #36, #37, #38, #40, #41, #43, #148, #149, #155, #173, #179, #180. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent b023f80 commit 02a9fe1

22 files changed

Lines changed: 720 additions & 49 deletions

src/abstract/BasePythOracleAdapter.sol

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ error VaultSharePriceOverflow(uint256 price8);
4747
/// source (Pyth's own getPriceAtPublishTime, an indexer, etc.).
4848
error HistoricalRoundDataUnsupported(uint80 roundId);
4949

50+
/// @dev Error raised when `_setCorporateActionPauseConfig` is called more than
51+
/// once. Enforces the SPEC §16.2 "immutable after initialize" invariant on the
52+
/// four corporate-action pause storage slots so subclass discipline cannot
53+
/// silently break it.
54+
error CorporateActionConfigAlreadyInitialized();
55+
5056
/// @title CorporateActionPauseConfig
5157
/// @notice Configuration for the corporate-action-aware auto-pause feature.
5258
/// All fields are immutable after initialize — see SPEC § 16.2.
@@ -113,13 +119,35 @@ abstract contract BasePythOracleAdapter is AggregatorV2V3Interface {
113119
/// pausing. Immutable.
114120
uint64 public pauseTimeAfter;
115121

122+
/// @dev True once `_setCorporateActionPauseConfig` has run. The four
123+
/// corporate-action pause slots become immutable thereafter — any second
124+
/// call reverts with `CorporateActionConfigAlreadyInitialized`. Subclass
125+
/// initializers MUST call the helper exactly once. Packed alongside
126+
/// `pauseTimeBefore` and `pauseTimeAfter` in the same storage slot
127+
/// (8 + 8 + 1 bytes ≪ 32), so the new invariant costs no extra slot.
128+
bool internal _corporateActionConfigInitialized;
129+
116130
/// @notice Emitted when the manual pause state changes.
117131
/// @param isPaused The new pause state.
118132
event PauseSet(bool isPaused);
119133
/// @notice Emitted when the admin is changed.
120134
/// @param oldAdmin The previous admin address.
121135
/// @param newAdmin The new admin address.
122136
event AdminSet(address indexed oldAdmin, address indexed newAdmin);
137+
/// @notice Emitted exactly once on initialize to record the corporate-action
138+
/// auto-pause config. Lets off-chain indexers reconstruct an oracle's
139+
/// full governance state from event logs alone, without per-oracle storage
140+
/// reads.
141+
/// @param corporateActionsVault Address implementing `ICorporateActionsV1`,
142+
/// or `address(0)` to disable auto-pause.
143+
/// @param actionTypeMask Bitmap of action types that trigger an auto-pause.
144+
/// @param pauseTimeBefore Seconds before a pending action's `effectiveTime`
145+
/// to start pausing.
146+
/// @param pauseTimeAfter Seconds after a completed action's `effectiveTime`
147+
/// to keep pausing.
148+
event CorporateActionPauseConfigSet(
149+
address corporateActionsVault, uint256 actionTypeMask, uint64 pauseTimeBefore, uint64 pauseTimeAfter
150+
);
123151

124152
modifier onlyAdmin() {
125153
if (msg.sender != admin) revert OnlyAdmin();
@@ -151,6 +179,13 @@ abstract contract BasePythOracleAdapter is AggregatorV2V3Interface {
151179
}
152180

153181
/// @inheritdoc AggregatorV2V3Interface
182+
/// @dev `roundId` and `answeredInRound` are derived from the Pyth
183+
/// `publishTime` (truncated to `uint80`) so they advance monotonically per
184+
/// Chainlink convention without adding storage. Integrators that diff
185+
/// `roundId` between calls to detect a fresh update will see a different
186+
/// value whenever Pyth has produced a new price. The truncation collision
187+
/// is far in the future — `uint80` covers more seconds than any plausible
188+
/// deployment lifetime.
154189
// slither-disable-next-line pyth-unchecked-confidence
155190
function latestRoundData()
156191
external
@@ -163,7 +198,14 @@ abstract contract BasePythOracleAdapter is AggregatorV2V3Interface {
163198
PythStructs.Price memory priceData = _getPriceData();
164199
int256 scaledPrice = _vaultSharePrice(priceData);
165200

166-
return (1, scaledPrice, uint256(uint64(priceData.publishTime)), uint256(uint64(priceData.publishTime)), 1);
201+
uint80 publishRoundId = uint80(uint64(priceData.publishTime));
202+
return (
203+
publishRoundId,
204+
scaledPrice,
205+
uint256(uint64(priceData.publishTime)),
206+
uint256(uint64(priceData.publishTime)),
207+
publishRoundId
208+
);
167209
}
168210

169211
/// @inheritdoc AggregatorV2V3Interface
@@ -220,12 +262,21 @@ abstract contract BasePythOracleAdapter is AggregatorV2V3Interface {
220262
/// `corporateActionsVault == address(0)` disables auto-pause for the
221263
/// life of the proxy and is the right choice when an oracle is
222264
/// redeployed against a vault that hasn't yet been upgraded to expose
223-
/// `ICorporateActionsV1`.
265+
/// `ICorporateActionsV1`. Reverts with
266+
/// `CorporateActionConfigAlreadyInitialized` if called a second time —
267+
/// the four corporate-action pause slots are SPEC §16.2 immutable.
268+
/// Emits `CorporateActionPauseConfigSet` so off-chain indexers can
269+
/// reconstruct the config from logs alone.
224270
function _setCorporateActionPauseConfig(CorporateActionPauseConfig memory config) internal {
271+
if (_corporateActionConfigInitialized) revert CorporateActionConfigAlreadyInitialized();
272+
_corporateActionConfigInitialized = true;
225273
corporateActionsVault = config.corporateActionsVault;
226274
actionTypeMask = config.actionTypeMask;
227275
pauseTimeBefore = config.pauseTimeBefore;
228276
pauseTimeAfter = config.pauseTimeAfter;
277+
emit CorporateActionPauseConfigSet(
278+
config.corporateActionsVault, config.actionTypeMask, config.pauseTimeBefore, config.pauseTimeAfter
279+
);
229280
}
230281

231282
/// @dev Computes conservative price (price - confidence) as a Rain Float.

src/concrete/deploy/MorphoProtocolAdapterBeaconSetDeployer.sol

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,14 @@ struct MorphoProtocolAdapterBeaconSetDeployerConfig {
3737
/// retains no authority over the beacon after construction. Used for Morpho
3838
/// Blue protocol integration.
3939
contract MorphoProtocolAdapterBeaconSetDeployer {
40-
/// Emitted when a new MorphoProtocolAdapter is deployed.
41-
event Deployment(address sender, address morphoProtocolAdapter);
40+
/// @notice Emitted when a new MorphoProtocolAdapter is deployed.
41+
/// @param caller The direct on-chain caller of `newMorphoProtocolAdapter`.
42+
/// For adapters created via `OracleUnifiedDeployer` /
43+
/// `MultiOracleUnifiedDeployer` this is the unified-deployer contract, not
44+
/// the originating EOA. Indexed so monitoring can filter by deployer.
45+
/// @param morphoProtocolAdapter The address of the new proxy. Indexed so
46+
/// monitoring can filter by adapter.
47+
event Deployment(address indexed caller, address indexed morphoProtocolAdapter);
4248

4349
/// The beacon for the MorphoProtocolAdapter implementation contracts.
4450
IBeacon public immutable I_MORPHO_PROTOCOL_ADAPTER_BEACON;

src/concrete/deploy/MultiOracleUnifiedDeployer.sol

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ import {LibProdDeploy} from "src/lib/LibProdDeploy.sol";
2222
/// set in LibProdDeploy.
2323
error MultiPythBeaconSetDeployerNotSet();
2424

25+
/// @dev Error raised when the `MorphoProtocolAdapterBeaconSetDeployer` address
26+
/// in `LibProdDeploy` is unset (zero) on the current chain.
27+
error MorphoBeaconSetDeployerNotSet();
28+
29+
/// @dev Error raised when the `PassthroughProtocolAdapterBeaconSetDeployer`
30+
/// address in `LibProdDeploy` is unset (zero) on the current chain.
31+
error PassthroughBeaconSetDeployerNotSet();
32+
2533
/// @title MultiOracleUnifiedDeployer
2634
/// @notice Atomically deploys a MultiPythOracleAdapter and all protocol
2735
/// adapters (Morpho, Passthrough for Aave/Compound) for a new vault. Mirrors
@@ -38,14 +46,18 @@ error MultiPythBeaconSetDeployerNotSet();
3846
/// replaced. There is no on-chain pointer to chase. Tracked at #209.
3947
contract MultiOracleUnifiedDeployer {
4048
/// @notice Emitted when a new multi-feed oracle and protocol adapter set is deployed.
41-
/// @param sender The caller that triggered the deployment.
49+
/// @param caller The direct on-chain caller of
50+
/// `newMultiOracleAndProtocolAdapters` — typically the originating EOA,
51+
/// but if this contract is itself wrapped behind another deployer it will
52+
/// be that intermediate contract, not the EOA. Indexed so monitoring can
53+
/// filter by deployer.
4254
/// @param multiPythOracleAdapter The address of the new MultiPythOracleAdapter proxy.
4355
/// @param morphoProtocolAdapter The address of the new MorphoProtocolAdapter proxy.
4456
/// @param passthroughProtocolAdapter The address of the new PassthroughProtocolAdapter proxy.
4557
event Deployment(
46-
address sender,
47-
address multiPythOracleAdapter,
48-
address morphoProtocolAdapter,
58+
address indexed caller,
59+
address indexed multiPythOracleAdapter,
60+
address indexed morphoProtocolAdapter,
4961
address passthroughProtocolAdapter
5062
);
5163

@@ -68,6 +80,12 @@ contract MultiOracleUnifiedDeployer {
6880
if (LibProdDeploy.MULTI_PYTH_ORACLE_ADAPTER_BEACON_SET_DEPLOYER == address(0)) {
6981
revert MultiPythBeaconSetDeployerNotSet();
7082
}
83+
if (LibProdDeploy.MORPHO_PROTOCOL_ADAPTER_BEACON_SET_DEPLOYER == address(0)) {
84+
revert MorphoBeaconSetDeployerNotSet();
85+
}
86+
if (LibProdDeploy.PASSTHROUGH_PROTOCOL_ADAPTER_BEACON_SET_DEPLOYER == address(0)) {
87+
revert PassthroughBeaconSetDeployerNotSet();
88+
}
7189

7290
// 1. Deploy multi-feed oracle adapter
7391
MultiPythOracleAdapter oracleAdapter = MultiPythOracleAdapterBeaconSetDeployer(

src/concrete/deploy/MultiPythOracleAdapterBeaconSetDeployer.sol

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,14 @@ struct MultiPythOracleAdapterBeaconSetDeployerConfig {
3636
/// contract retains no authority over the beacon after construction.
3737
/// Follows the st0x.deploy BeaconSetDeployer pattern.
3838
contract MultiPythOracleAdapterBeaconSetDeployer {
39-
/// Emitted when a new MultiPythOracleAdapter is deployed.
40-
event Deployment(address sender, address multiPythOracleAdapter);
39+
/// @notice Emitted when a new MultiPythOracleAdapter is deployed.
40+
/// @param caller The direct on-chain caller of `newMultiPythOracleAdapter`.
41+
/// For adapters created via `MultiOracleUnifiedDeployer` this is the
42+
/// unified-deployer contract, not the originating EOA. Indexed so
43+
/// monitoring can filter by deployer.
44+
/// @param multiPythOracleAdapter The address of the new proxy. Indexed so
45+
/// monitoring can filter by adapter.
46+
event Deployment(address indexed caller, address indexed multiPythOracleAdapter);
4147

4248
/// The beacon for the MultiPythOracleAdapter implementation contracts.
4349
IBeacon public immutable I_MULTI_PYTH_ORACLE_ADAPTER_BEACON;

src/concrete/deploy/OracleRegistryBeaconSetDeployer.sol

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,13 @@ struct OracleRegistryBeaconSetDeployerConfig {
3636
/// post-construction authority over the beacon. Follows the st0x.deploy
3737
/// BeaconSetDeployer pattern.
3838
contract OracleRegistryBeaconSetDeployer {
39-
/// Emitted when a new OracleRegistry is deployed.
40-
event Deployment(address sender, address oracleRegistry);
39+
/// @notice Emitted when a new OracleRegistry is deployed.
40+
/// @param caller The direct on-chain caller of `newOracleRegistry`. Also
41+
/// becomes the registry admin per SPEC §13. Indexed so monitoring can
42+
/// filter by deployer.
43+
/// @param oracleRegistry The address of the new proxy. Indexed so
44+
/// monitoring can filter by registry.
45+
event Deployment(address indexed caller, address indexed oracleRegistry);
4146

4247
/// The beacon for the OracleRegistry implementation contracts.
4348
IBeacon public immutable I_ORACLE_REGISTRY_BEACON;

src/concrete/deploy/OracleUnifiedDeployer.sol

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ import {MorphoProtocolAdapter} from "src/concrete/protocol/MorphoProtocolAdapter
1414
import {OracleRegistry} from "src/concrete/registry/OracleRegistry.sol";
1515
import {LibProdDeploy} from "src/lib/LibProdDeploy.sol";
1616

17+
/// @dev Error raised when the `PythOracleAdapterBeaconSetDeployer` address in
18+
/// `LibProdDeploy` is unset (zero) on the current chain.
19+
error PythBeaconSetDeployerNotSet();
20+
21+
/// @dev Error raised when the `MorphoProtocolAdapterBeaconSetDeployer` address
22+
/// in `LibProdDeploy` is unset (zero) on the current chain.
23+
error MorphoBeaconSetDeployerNotSet();
24+
25+
/// @dev Error raised when the `PassthroughProtocolAdapterBeaconSetDeployer`
26+
/// address in `LibProdDeploy` is unset (zero) on the current chain.
27+
error PassthroughBeaconSetDeployerNotSet();
28+
1729
/// @title OracleUnifiedDeployer
1830
/// @notice Atomically deploys a PythOracleAdapter and all protocol adapters
1931
/// (Morpho, Passthrough for Aave/Compound) for a new vault. The beacon set
@@ -28,12 +40,18 @@ import {LibProdDeploy} from "src/lib/LibProdDeploy.sol";
2840
/// replaced. There is no on-chain pointer to chase. Tracked at #209.
2941
contract OracleUnifiedDeployer {
3042
/// @notice Emitted when a new oracle and protocol adapter set is deployed.
31-
/// @param sender The caller that triggered the deployment.
43+
/// @param caller The direct on-chain caller of `newOracleAndProtocolAdapters`
44+
/// — typically the originating EOA, but if this contract is itself wrapped
45+
/// behind another deployer it will be that intermediate contract, not the
46+
/// EOA. Indexed so monitoring can filter by deployer.
3247
/// @param pythOracleAdapter The address of the new PythOracleAdapter proxy.
3348
/// @param morphoProtocolAdapter The address of the new MorphoProtocolAdapter proxy.
3449
/// @param passthroughProtocolAdapter The address of the new PassthroughProtocolAdapter proxy.
3550
event Deployment(
36-
address sender, address pythOracleAdapter, address morphoProtocolAdapter, address passthroughProtocolAdapter
51+
address indexed caller,
52+
address indexed pythOracleAdapter,
53+
address indexed morphoProtocolAdapter,
54+
address passthroughProtocolAdapter
3755
);
3856

3957
/// @notice Deploy oracle + all protocol adapters for a new vault.
@@ -54,6 +72,20 @@ contract OracleUnifiedDeployer {
5472
OracleRegistry registry,
5573
CorporateActionPauseConfig calldata pauseConfig
5674
) external {
75+
// Pre-flight: every LibProdDeploy sub-deployer address must be set on
76+
// the current chain. Surface a typed error so a partial deployment or
77+
// wrong-chain invocation fails loudly rather than reverting deep
78+
// inside an ABI decode of an empty extcall return.
79+
if (LibProdDeploy.PYTH_ORACLE_ADAPTER_BEACON_SET_DEPLOYER == address(0)) {
80+
revert PythBeaconSetDeployerNotSet();
81+
}
82+
if (LibProdDeploy.MORPHO_PROTOCOL_ADAPTER_BEACON_SET_DEPLOYER == address(0)) {
83+
revert MorphoBeaconSetDeployerNotSet();
84+
}
85+
if (LibProdDeploy.PASSTHROUGH_PROTOCOL_ADAPTER_BEACON_SET_DEPLOYER == address(0)) {
86+
revert PassthroughBeaconSetDeployerNotSet();
87+
}
88+
5789
// 1. Deploy oracle adapter
5890
PythOracleAdapter oracleAdapter = PythOracleAdapterBeaconSetDeployer(
5991
LibProdDeploy.PYTH_ORACLE_ADAPTER_BEACON_SET_DEPLOYER

src/concrete/deploy/PassthroughProtocolAdapterBeaconSetDeployer.sol

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,15 @@ struct PassthroughProtocolAdapterBeaconSetDeployerConfig {
4141
/// contract retains no authority over the beacon after construction. Used
4242
/// for Aave V3, Compound V3, and any future Chainlink-compatible protocol.
4343
contract PassthroughProtocolAdapterBeaconSetDeployer {
44-
/// Emitted when a new PassthroughProtocolAdapter is deployed.
45-
event Deployment(address sender, address passthroughProtocolAdapter);
44+
/// @notice Emitted when a new PassthroughProtocolAdapter is deployed.
45+
/// @param caller The direct on-chain caller of
46+
/// `newPassthroughProtocolAdapter`. For adapters created via
47+
/// `OracleUnifiedDeployer` / `MultiOracleUnifiedDeployer` this is the
48+
/// unified-deployer contract, not the originating EOA. Indexed so
49+
/// monitoring can filter by deployer.
50+
/// @param passthroughProtocolAdapter The address of the new proxy. Indexed
51+
/// so monitoring can filter by adapter.
52+
event Deployment(address indexed caller, address indexed passthroughProtocolAdapter);
4653

4754
/// The beacon for the PassthroughProtocolAdapter implementation contracts.
4855
IBeacon public immutable I_PASSTHROUGH_PROTOCOL_ADAPTER_BEACON;

src/concrete/deploy/PythOracleAdapterBeaconSetDeployer.sol

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,14 @@ struct PythOracleAdapterBeaconSetDeployerConfig {
3636
/// retains no authority over the beacon after construction. Follows the
3737
/// st0x.deploy BeaconSetDeployer pattern.
3838
contract PythOracleAdapterBeaconSetDeployer {
39-
/// Emitted when a new PythOracleAdapter is deployed.
40-
event Deployment(address sender, address pythOracleAdapter);
39+
/// @notice Emitted when a new PythOracleAdapter is deployed.
40+
/// @param caller The direct on-chain caller of `newPythOracleAdapter`. For
41+
/// adapters created via `OracleUnifiedDeployer` /
42+
/// `MultiOracleUnifiedDeployer` this is the unified-deployer contract, not
43+
/// the originating EOA. Indexed so monitoring can filter by deployer.
44+
/// @param pythOracleAdapter The address of the new proxy. Indexed so
45+
/// monitoring can filter by adapter.
46+
event Deployment(address indexed caller, address indexed pythOracleAdapter);
4147

4248
/// The beacon for the PythOracleAdapter implementation contracts.
4349
IBeacon public immutable I_PYTH_ORACLE_ADAPTER_BEACON;

src/concrete/oracle/MultiPythOracleAdapter.sol

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@ contract MultiPythOracleAdapter is BasePythOracleAdapter, ICloneableV2, Initiali
119119

120120
if (config.vault == address(0)) revert ZeroVault();
121121
if (config.admin == address(0)) revert ZeroAdmin();
122-
if (config.feeds.length == 0) revert ZeroFeeds();
123-
if (config.feeds.length > MAX_FEEDS) revert TooManyFeeds();
124122

125123
vault = config.vault;
126124
admin = config.admin;
@@ -136,8 +134,6 @@ contract MultiPythOracleAdapter is BasePythOracleAdapter, ICloneableV2, Initiali
136134
/// @notice Update the entire feed list. Admin only.
137135
/// @param feeds The new ordered feed configurations.
138136
function setFeeds(FeedConfig[] calldata feeds) external onlyAdmin {
139-
if (feeds.length == 0) revert ZeroFeeds();
140-
if (feeds.length > MAX_FEEDS) revert TooManyFeeds();
141137
_setFeeds(feeds);
142138
emit FeedsSet(feeds);
143139
}
@@ -225,18 +221,37 @@ contract MultiPythOracleAdapter is BasePythOracleAdapter, ICloneableV2, Initiali
225221
}
226222
}
227223

228-
/// @dev Internal feed setter. Validates and stores feeds.
224+
/// @dev Internal feed setter. Validates every entry and the length bounds
225+
/// up front, then mutates storage. The validate-then-mutate ordering means
226+
/// a bad input never partially clobbers existing feeds — important even
227+
/// though Solidity reverts roll back state today, because it keeps the
228+
/// helper composable into future delegatecall / multi-call contexts where
229+
/// partial reverts would otherwise leak. Length checks are folded in so
230+
/// `initialize` and `setFeeds` become thin wrappers and any future internal
231+
/// caller is also bound by `MAX_FEEDS` / non-empty.
229232
function _setFeeds(FeedConfig[] memory feeds) internal {
233+
// Validate length bounds first.
234+
if (feeds.length == 0) revert ZeroFeeds();
235+
if (feeds.length > MAX_FEEDS) revert TooManyFeeds();
236+
237+
// Validate each entry before any storage write.
238+
for (uint256 i = 0; i < feeds.length; i++) {
239+
if (feeds[i].priceId == bytes32(0)) revert ZeroPriceId(i);
240+
if (feeds[i].maxAge == 0) revert ZeroMaxAge(i);
241+
}
242+
243+
// Mutate: clear tail of old entries that no longer fit, then write new
244+
// entries, then update `feedCount` last so an external reader can
245+
// never observe a length without its slot also populated.
230246
uint256 oldCount = feedCount;
231247
for (uint256 i = feeds.length; i < oldCount; i++) {
232248
delete _feeds[i];
233249
}
234250

235-
feedCount = feeds.length;
236251
for (uint256 i = 0; i < feeds.length; i++) {
237-
if (feeds[i].priceId == bytes32(0)) revert ZeroPriceId(i);
238-
if (feeds[i].maxAge == 0) revert ZeroMaxAge(i);
239252
_feeds[i] = feeds[i];
240253
}
254+
255+
feedCount = feeds.length;
241256
}
242257
}

0 commit comments

Comments
 (0)