@@ -11,6 +11,19 @@ contract MockPyth is AbstractPyth {
1111 uint singleUpdateFeeInWei;
1212 uint validTimePeriod;
1313
14+ // Mock structure for TWAP price information
15+ struct MockTwapPriceInfo {
16+ int32 expo;
17+ int64 price;
18+ uint64 conf;
19+ uint64 publishTime;
20+ uint64 prevPublishTime;
21+ uint64 publishSlot;
22+ int128 cumulativePrice;
23+ uint128 cumulativeConf;
24+ uint64 numDownSlots;
25+ }
26+
1427 constructor (uint _validTimePeriod , uint _singleUpdateFeeInWei ) {
1528 singleUpdateFeeInWei = _singleUpdateFeeInWei;
1629 validTimePeriod = _validTimePeriod;
@@ -160,6 +173,186 @@ contract MockPyth is AbstractPyth {
160173 );
161174 }
162175
176+ function parseTwapPriceFeedUpdates (
177+ bytes [][] calldata updateData ,
178+ bytes32 [] calldata priceIds
179+ )
180+ external
181+ payable
182+ returns (PythStructs.TwapPriceFeed[] memory twapPriceFeeds )
183+ {
184+ // Validate inputs and fee
185+ if (updateData.length != 2 ) revert PythErrors.InvalidUpdateData ();
186+
187+ uint requiredFee = getUpdateFee (updateData[0 ]);
188+ if (requiredFee != getUpdateFee (updateData[1 ]))
189+ revert PythErrors.InvalidUpdateData ();
190+ if (msg .value < requiredFee) revert PythErrors.InsufficientFee ();
191+
192+ twapPriceFeeds = new PythStructs.TwapPriceFeed [](priceIds.length );
193+
194+ // Process each price ID
195+ for (uint i = 0 ; i < priceIds.length ; i++ ) {
196+ processTwapPriceFeed (updateData, priceIds[i], i, twapPriceFeeds);
197+ }
198+ }
199+
200+ function processTwapPriceFeed (
201+ bytes [][] calldata updateData ,
202+ bytes32 priceId ,
203+ uint index ,
204+ PythStructs.TwapPriceFeed[] memory twapPriceFeeds
205+ ) private {
206+ // Find start price feed
207+ PythStructs.PriceFeed memory startFeed;
208+ uint64 startPrevPublishTime;
209+ bool foundStart = false ;
210+
211+ for (uint j = 0 ; j < updateData[0 ].length ; j++ ) {
212+ (startFeed, startPrevPublishTime) = abi.decode (
213+ updateData[0 ][j],
214+ (PythStructs.PriceFeed, uint64 )
215+ );
216+
217+ if (startFeed.id == priceId) {
218+ foundStart = true ;
219+ break ;
220+ }
221+ }
222+
223+ if (! foundStart) revert PythErrors.PriceFeedNotFoundWithinRange ();
224+
225+ // Find end price feed
226+ PythStructs.PriceFeed memory endFeed;
227+ uint64 endPrevPublishTime;
228+ bool foundEnd = false ;
229+
230+ for (uint j = 0 ; j < updateData[1 ].length ; j++ ) {
231+ (endFeed, endPrevPublishTime) = abi.decode (
232+ updateData[1 ][j],
233+ (PythStructs.PriceFeed, uint64 )
234+ );
235+
236+ if (endFeed.id == priceId) {
237+ foundEnd = true ;
238+ break ;
239+ }
240+ }
241+
242+ if (! foundEnd) revert PythErrors.PriceFeedNotFoundWithinRange ();
243+
244+ // Validate time ordering
245+ if (startFeed.price.publishTime >= endFeed.price.publishTime) {
246+ revert PythErrors.InvalidTwapUpdateDataSet ();
247+ }
248+
249+ // Convert to MockTwapPriceInfo
250+ MockTwapPriceInfo memory startInfo = createMockTwapInfo (
251+ startFeed,
252+ startPrevPublishTime
253+ );
254+ MockTwapPriceInfo memory endInfo = createMockTwapInfo (
255+ endFeed,
256+ endPrevPublishTime
257+ );
258+
259+ if (startInfo.publishSlot >= endInfo.publishSlot) {
260+ revert PythErrors.InvalidTwapUpdateDataSet ();
261+ }
262+
263+ // Calculate and store TWAP
264+ twapPriceFeeds[index] = calculateTwap (priceId, startInfo, endInfo);
265+
266+ // Emit event in a separate function to reduce stack depth
267+ emitTwapUpdate (
268+ priceId,
269+ startInfo.publishTime,
270+ endInfo.publishTime,
271+ twapPriceFeeds[index]
272+ );
273+ }
274+
275+ function emitTwapUpdate (
276+ bytes32 priceId ,
277+ uint64 startTime ,
278+ uint64 endTime ,
279+ PythStructs.TwapPriceFeed memory twapFeed
280+ ) private {
281+ emit TwapPriceFeedUpdate (
282+ priceId,
283+ startTime,
284+ endTime,
285+ twapFeed.twap.price,
286+ twapFeed.twap.conf,
287+ twapFeed.downSlotRatio
288+ );
289+ }
290+
291+ function createMockTwapInfo (
292+ PythStructs.PriceFeed memory feed ,
293+ uint64 prevPublishTime
294+ ) internal pure returns (MockTwapPriceInfo memory mockInfo ) {
295+ mockInfo.expo = feed.price.expo;
296+ mockInfo.price = feed.price.price;
297+ mockInfo.conf = feed.price.conf;
298+ mockInfo.publishTime = uint64 (feed.price.publishTime);
299+ mockInfo.prevPublishTime = prevPublishTime;
300+
301+ // Use publishTime as publishSlot in mock implementation
302+ mockInfo.publishSlot = uint64 (feed.price.publishTime);
303+
304+ // Create mock cumulative values for demonstration
305+ // In a real implementation, these would accumulate over time
306+ mockInfo.cumulativePrice =
307+ int128 (feed.price.price) *
308+ int128 (uint128 (mockInfo.publishSlot));
309+ mockInfo.cumulativeConf =
310+ uint128 (feed.price.conf) *
311+ uint128 (mockInfo.publishSlot);
312+
313+ // Default to 0 down slots for mock
314+ mockInfo.numDownSlots = 0 ;
315+
316+ return mockInfo;
317+ }
318+
319+ function calculateTwap (
320+ bytes32 priceId ,
321+ MockTwapPriceInfo memory twapPriceInfoStart ,
322+ MockTwapPriceInfo memory twapPriceInfoEnd
323+ ) internal pure returns (PythStructs.TwapPriceFeed memory twapPriceFeed ) {
324+ twapPriceFeed.id = priceId;
325+ twapPriceFeed.startTime = twapPriceInfoStart.publishTime;
326+ twapPriceFeed.endTime = twapPriceInfoEnd.publishTime;
327+
328+ // Calculate differences between start and end points for slots and cumulative values
329+ uint64 slotDiff = twapPriceInfoEnd.publishSlot -
330+ twapPriceInfoStart.publishSlot;
331+ int128 priceDiff = twapPriceInfoEnd.cumulativePrice -
332+ twapPriceInfoStart.cumulativePrice;
333+ uint128 confDiff = twapPriceInfoEnd.cumulativeConf -
334+ twapPriceInfoStart.cumulativeConf;
335+
336+ // Calculate time-weighted average price (TWAP) and confidence
337+ int128 twapPrice = priceDiff / int128 (uint128 (slotDiff));
338+ uint128 twapConf = confDiff / uint128 (slotDiff);
339+
340+ twapPriceFeed.twap.price = int64 (twapPrice);
341+ twapPriceFeed.twap.conf = uint64 (twapConf);
342+ twapPriceFeed.twap.expo = twapPriceInfoStart.expo;
343+ twapPriceFeed.twap.publishTime = twapPriceInfoEnd.publishTime;
344+
345+ // Calculate downSlotRatio as a value between 0 and 1,000,000
346+ uint64 totalDownSlots = twapPriceInfoEnd.numDownSlots -
347+ twapPriceInfoStart.numDownSlots;
348+ uint64 downSlotsRatio = (totalDownSlots * 1_000_000 ) / slotDiff;
349+
350+ // Safely downcast to uint32 (sufficient for value range 0-1,000,000)
351+ twapPriceFeed.downSlotRatio = uint32 (downSlotsRatio);
352+
353+ return twapPriceFeed;
354+ }
355+
163356 function createPriceFeedUpdateData (
164357 bytes32 id ,
165358 int64 price ,
0 commit comments