Skip to content

Commit 9565eed

Browse files
committed
feat: add more test utils for twap
1 parent 93195fa commit 9565eed

File tree

9 files changed

+161
-30
lines changed

9 files changed

+161
-30
lines changed

target_chains/ethereum/contracts/contracts/pyth/Pyth.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -601,14 +601,14 @@ abstract contract Pyth is
601601
twapPriceFeed.startTime = twapPriceInfoStart.publishTime;
602602
twapPriceFeed.endTime = twapPriceInfoEnd.publishTime;
603603

604-
// Calculate downSlotRatio as a value between 0 and 1,000,000
604+
// Calculate downSlotsRatio as a value between 0 and 1,000,000
605605
// 0 means no slots were missed, 1,000,000 means all slots were missed
606606
uint64 totalDownSlots = twapPriceInfoEnd.numDownSlots -
607607
twapPriceInfoStart.numDownSlots;
608608
uint64 downSlotsRatio = (totalDownSlots * 1_000_000) / slotDiff;
609609

610610
// Safely downcast to uint32 (sufficient for value range 0-1,000,000)
611-
twapPriceFeed.downSlotRatio = uint32(downSlotsRatio);
611+
twapPriceFeed.downSlotsRatio = uint32(downSlotsRatio);
612612

613613
return twapPriceFeed;
614614
}

target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol

Lines changed: 145 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,20 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
7373
uint64 emaConf;
7474
}
7575

76+
struct TwapPriceFeedMessage {
77+
bytes32 priceId;
78+
uint64 startTime;
79+
uint64 endTime;
80+
int64 price;
81+
uint64 conf;
82+
int32 expo;
83+
uint64 publishTime;
84+
uint64 prevPublishTime;
85+
int64 emaPrice;
86+
uint64 emaConf;
87+
uint64 downSlotsRatio;
88+
}
89+
7690
struct MerkleUpdateConfig {
7791
uint8 depth;
7892
uint8 numSigners;
@@ -101,16 +115,37 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
101115
}
102116
}
103117

104-
function generateWhMerkleUpdateWithSource(
105-
PriceFeedMessage[] memory priceFeedMessages,
106-
MerkleUpdateConfig memory config
107-
) internal returns (bytes memory whMerkleUpdateData) {
108-
bytes[] memory encodedPriceFeedMessages = encodePriceFeedMessages(
109-
priceFeedMessages
118+
function encodeTwapPriceFeedMessages(
119+
TwapPriceFeedMessage[] memory twapPriceFeedMessages
120+
) internal pure returns (bytes[] memory encodedTwapPriceFeedMessages) {
121+
encodedTwapPriceFeedMessages = new bytes[](
122+
twapPriceFeedMessages.length
110123
);
111124

125+
for (uint i = 0; i < twapPriceFeedMessages.length; i++) {
126+
encodedTwapPriceFeedMessages[i] = abi.encodePacked(
127+
uint8(PythAccumulator.MessageType.TwapPriceFeed),
128+
twapPriceFeedMessages[i].priceId,
129+
twapPriceFeedMessages[i].startTime,
130+
twapPriceFeedMessages[i].endTime,
131+
twapPriceFeedMessages[i].price,
132+
twapPriceFeedMessages[i].conf,
133+
twapPriceFeedMessages[i].expo,
134+
twapPriceFeedMessages[i].publishTime,
135+
twapPriceFeedMessages[i].prevPublishTime,
136+
twapPriceFeedMessages[i].emaPrice,
137+
twapPriceFeedMessages[i].emaConf,
138+
twapPriceFeedMessages[i].downSlotsRatio
139+
);
140+
}
141+
}
142+
143+
function generateMerkleUpdateWithEncodedMessages(
144+
bytes[] memory encodedMessages,
145+
MerkleUpdateConfig memory config
146+
) internal returns (bytes memory whMerkleUpdateData) {
112147
(bytes20 rootDigest, bytes[] memory proofs) = MerkleTree
113-
.constructProofs(encodedPriceFeedMessages, config.depth);
148+
.constructProofs(encodedMessages, config.depth);
114149

115150
bytes memory wormholePayload = abi.encodePacked(
116151
uint32(0x41555756), // PythAccumulator.ACCUMULATOR_WORMHOLE_MAGIC
@@ -146,14 +181,14 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
146181
uint8(PythAccumulator.UpdateType.WormholeMerkle),
147182
uint16(wormholeMerkleVaa.length),
148183
wormholeMerkleVaa,
149-
uint8(priceFeedMessages.length)
184+
uint8(encodedMessages.length)
150185
);
151186

152-
for (uint i = 0; i < priceFeedMessages.length; i++) {
187+
for (uint i = 0; i < encodedMessages.length; i++) {
153188
whMerkleUpdateData = abi.encodePacked(
154189
whMerkleUpdateData,
155-
uint16(encodedPriceFeedMessages[i].length),
156-
encodedPriceFeedMessages[i],
190+
uint16(encodedMessages[i].length),
191+
encodedMessages[i],
157192
proofs[i]
158193
);
159194
}
@@ -164,8 +199,32 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
164199
uint8 depth,
165200
uint8 numSigners
166201
) internal returns (bytes memory whMerkleUpdateData) {
167-
whMerkleUpdateData = generateWhMerkleUpdateWithSource(
168-
priceFeedMessages,
202+
bytes[] memory encodedPriceFeedMessages = encodePriceFeedMessages(
203+
priceFeedMessages
204+
);
205+
whMerkleUpdateData = generateMerkleUpdateWithEncodedMessages(
206+
encodedPriceFeedMessages,
207+
MerkleUpdateConfig(
208+
depth,
209+
numSigners,
210+
SOURCE_EMITTER_CHAIN_ID,
211+
SOURCE_EMITTER_ADDRESS,
212+
false
213+
)
214+
);
215+
}
216+
217+
function generateTwapWhMerkleUpdate(
218+
TwapPriceFeedMessage[] memory twapPriceFeedMessages,
219+
uint8 depth,
220+
uint8 numSigners
221+
) internal returns (bytes memory whMerkleUpdateData) {
222+
bytes[]
223+
memory encodedTwapPriceFeedMessages = encodeTwapPriceFeedMessages(
224+
twapPriceFeedMessages
225+
);
226+
whMerkleUpdateData = generateMerkleUpdateWithEncodedMessages(
227+
encodedTwapPriceFeedMessages,
169228
MerkleUpdateConfig(
170229
depth,
171230
numSigners,
@@ -186,7 +245,6 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
186245
bytes[] memory encodedPriceFeedMessages = encodePriceFeedMessages(
187246
priceFeedMessages
188247
);
189-
190248
(bytes20 rootDigest, bytes[] memory proofs) = MerkleTree
191249
.constructProofs(encodedPriceFeedMessages, depth);
192250
// refactoring some of these generateWormhole functions was necessary
@@ -219,6 +277,46 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
219277
}
220278
}
221279

280+
function generateForwardCompatibleTwapWhMerkleUpdate(
281+
TwapPriceFeedMessage[] memory twapPriceFeedMessages,
282+
uint8 depth,
283+
uint8 numSigners,
284+
uint8 minorVersion,
285+
bytes memory trailingHeaderData
286+
) internal view returns (bytes memory whMerkleUpdateData) {
287+
bytes[]
288+
memory encodedTwapPriceFeedMessages = encodeTwapPriceFeedMessages(
289+
twapPriceFeedMessages
290+
);
291+
(bytes20 rootDigest, bytes[] memory proofs) = MerkleTree
292+
.constructProofs(encodedTwapPriceFeedMessages, depth);
293+
bytes
294+
memory wormholeMerkleVaa = generateForwardCompatibleWormholeMerkleVaa(
295+
rootDigest,
296+
trailingHeaderData,
297+
numSigners
298+
);
299+
300+
whMerkleUpdateData = abi.encodePacked(
301+
generateForwardCompatibleWormholeMerkleUpdateHeader(
302+
minorVersion,
303+
trailingHeaderData
304+
),
305+
uint16(wormholeMerkleVaa.length),
306+
wormholeMerkleVaa,
307+
uint8(twapPriceFeedMessages.length)
308+
);
309+
310+
for (uint i = 0; i < twapPriceFeedMessages.length; i++) {
311+
whMerkleUpdateData = abi.encodePacked(
312+
whMerkleUpdateData,
313+
uint16(encodedTwapPriceFeedMessages[i].length),
314+
encodedTwapPriceFeedMessages[i],
315+
proofs[i]
316+
);
317+
}
318+
}
319+
222320
function generateForwardCompatibleWormholeMerkleUpdateHeader(
223321
uint8 minorVersion,
224322
bytes memory trailingHeaderData
@@ -275,6 +373,39 @@ abstract contract PythTestUtils is Test, WormholeTestUtils, RandTestUtils {
275373
priceFeedMessages[i].emaConf = prices[i].conf;
276374
}
277375
}
376+
377+
function pricesToTwapPriceFeedMessages(
378+
bytes32[] memory priceIds,
379+
PythStructs.Price[] memory prices,
380+
uint64[] memory startTimes,
381+
uint64[] memory endTimes,
382+
uint64[] memory downSlotsRatios
383+
) public returns (TwapPriceFeedMessage[] memory twapPriceFeedMessages) {
384+
assertGe(priceIds.length, prices.length);
385+
assertEq(prices.length, startTimes.length);
386+
assertEq(prices.length, endTimes.length);
387+
assertEq(prices.length, downSlotsRatios.length);
388+
389+
twapPriceFeedMessages = new TwapPriceFeedMessage[](prices.length);
390+
391+
for (uint i = 0; i < prices.length; ++i) {
392+
twapPriceFeedMessages[i].priceId = priceIds[i];
393+
twapPriceFeedMessages[i].startTime = startTimes[i];
394+
twapPriceFeedMessages[i].endTime = endTimes[i];
395+
twapPriceFeedMessages[i].price = prices[i].price;
396+
twapPriceFeedMessages[i].conf = prices[i].conf;
397+
twapPriceFeedMessages[i].expo = prices[i].expo;
398+
twapPriceFeedMessages[i].publishTime = uint64(
399+
prices[i].publishTime
400+
);
401+
twapPriceFeedMessages[i].prevPublishTime =
402+
uint64(prices[i].publishTime) -
403+
1;
404+
twapPriceFeedMessages[i].emaPrice = prices[i].price;
405+
twapPriceFeedMessages[i].emaConf = prices[i].conf;
406+
twapPriceFeedMessages[i].downSlotsRatio = downSlotsRatios[i];
407+
}
408+
}
278409
}
279410

280411
contract PythUtilsTest is Test, WormholeTestUtils, PythTestUtils, IPythEvents {

target_chains/ethereum/sdk/solidity/IPythEvents.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ interface IPythEvents {
2222
/// @param endTime End time of the TWAP.
2323
/// @param twapPrice Price of the TWAP.
2424
/// @param twapConf Confidence interval of the TWAP.
25-
/// @param downSlotRatio Down slot ratio of the TWAP.
25+
/// @param downSlotsRatio Down slot ratio of the TWAP.
2626
event TwapPriceFeedUpdate(
2727
bytes32 indexed id,
2828
uint64 startTime,
2929
uint64 endTime,
3030
int64 twapPrice,
3131
uint64 twapConf,
32-
uint32 downSlotRatio
32+
uint32 downSlotsRatio
3333
);
3434
}

target_chains/ethereum/sdk/solidity/MockPyth.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ contract MockPyth is AbstractPyth {
284284
endTime,
285285
twapFeed.twap.price,
286286
twapFeed.twap.conf,
287-
twapFeed.downSlotRatio
287+
twapFeed.downSlotsRatio
288288
);
289289
}
290290

@@ -342,13 +342,13 @@ contract MockPyth is AbstractPyth {
342342
twapPriceFeed.twap.expo = twapPriceInfoStart.expo;
343343
twapPriceFeed.twap.publishTime = twapPriceInfoEnd.publishTime;
344344

345-
// Calculate downSlotRatio as a value between 0 and 1,000,000
345+
// Calculate downSlotsRatio as a value between 0 and 1,000,000
346346
uint64 totalDownSlots = twapPriceInfoEnd.numDownSlots -
347347
twapPriceInfoStart.numDownSlots;
348348
uint64 downSlotsRatio = (totalDownSlots * 1_000_000) / slotDiff;
349349

350350
// Safely downcast to uint32 (sufficient for value range 0-1,000,000)
351-
twapPriceFeed.downSlotRatio = uint32(downSlotsRatio);
351+
twapPriceFeed.downSlotsRatio = uint32(downSlotsRatio);
352352

353353
return twapPriceFeed;
354354
}

target_chains/ethereum/sdk/solidity/PythStructs.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ contract PythStructs {
4040
uint endTime;
4141
// TWAP price
4242
Price twap;
43-
// Down slot ratio
44-
uint32 downSlotRatio;
43+
// Down slots ratio
44+
uint32 downSlotsRatio;
4545
}
4646
}

target_chains/ethereum/sdk/solidity/abis/AbstractPyth.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
{
8282
"indexed": false,
8383
"internalType": "uint32",
84-
"name": "downSlotRatio",
84+
"name": "downSlotsRatio",
8585
"type": "uint32"
8686
}
8787
],
@@ -627,7 +627,7 @@
627627
},
628628
{
629629
"internalType": "uint32",
630-
"name": "downSlotRatio",
630+
"name": "downSlotsRatio",
631631
"type": "uint32"
632632
}
633633
],

target_chains/ethereum/sdk/solidity/abis/IPyth.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
{
6767
"indexed": false,
6868
"internalType": "uint32",
69-
"name": "downSlotRatio",
69+
"name": "downSlotsRatio",
7070
"type": "uint32"
7171
}
7272
],
@@ -517,7 +517,7 @@
517517
},
518518
{
519519
"internalType": "uint32",
520-
"name": "downSlotRatio",
520+
"name": "downSlotsRatio",
521521
"type": "uint32"
522522
}
523523
],

target_chains/ethereum/sdk/solidity/abis/IPythEvents.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
{
6767
"indexed": false,
6868
"internalType": "uint32",
69-
"name": "downSlotRatio",
69+
"name": "downSlotsRatio",
7070
"type": "uint32"
7171
}
7272
],

target_chains/ethereum/sdk/solidity/abis/MockPyth.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
{
123123
"indexed": false,
124124
"internalType": "uint32",
125-
"name": "downSlotRatio",
125+
"name": "downSlotsRatio",
126126
"type": "uint32"
127127
}
128128
],
@@ -722,7 +722,7 @@
722722
},
723723
{
724724
"internalType": "uint32",
725-
"name": "downSlotRatio",
725+
"name": "downSlotsRatio",
726726
"type": "uint32"
727727
}
728728
],

0 commit comments

Comments
 (0)