Skip to content

Commit 40ada8b

Browse files
committed
chore(lazer) Add sessions to evm contracts
1 parent 15443a4 commit 40ada8b

File tree

4 files changed

+178
-11
lines changed

4 files changed

+178
-11
lines changed

lazer/contracts/evm/src/PythLazerLib.sol

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ library PythLazerLib {
109109
returns (PythLazerStructs.PriceFeedProperty property, uint16 new_pos)
110110
{
111111
uint8 propertyId = uint8(update[pos]);
112-
require(propertyId <= 8, "Unknown property");
112+
require(propertyId <= 9, "Unknown property");
113113
property = PythLazerStructs.PriceFeedProperty(propertyId);
114114
pos += 1;
115115
new_pos = pos;
@@ -201,16 +201,17 @@ library PythLazerLib {
201201
// Price Property
202202
if (property == PythLazerStructs.PriceFeedProperty.Price) {
203203
(feed._price, pos) = parseFeedValueInt64(payload, pos);
204-
if (feed._price != 0)
204+
if (feed._price != 0) {
205205
_setPresent(
206206
feed,
207207
uint8(PythLazerStructs.PriceFeedProperty.Price)
208208
);
209-
else
209+
} else {
210210
_setApplicableButMissing(
211211
feed,
212212
uint8(PythLazerStructs.PriceFeedProperty.Price)
213213
);
214+
}
214215

215216
// Best Bid Price Property
216217
} else if (
@@ -307,16 +308,17 @@ library PythLazerLib {
307308
payload,
308309
pos
309310
);
310-
if (feed._confidence != 0)
311+
if (feed._confidence != 0) {
311312
_setPresent(
312313
feed,
313314
uint8(PythLazerStructs.PriceFeedProperty.Confidence)
314315
);
315-
else
316+
} else {
316317
_setApplicableButMissing(
317318
feed,
318319
uint8(PythLazerStructs.PriceFeedProperty.Confidence)
319320
);
321+
}
320322

321323
// Funding Rate Property
322324
} else if (
@@ -405,6 +407,19 @@ library PythLazerLib {
405407
)
406408
);
407409
}
410+
411+
// Market Session Property
412+
} else if (
413+
property == PythLazerStructs.PriceFeedProperty.MarketSession
414+
) {
415+
(feed._marketSession, pos) = parseFeedValueInt16(
416+
payload,
417+
pos
418+
);
419+
_setPresent(
420+
feed,
421+
uint8(PythLazerStructs.PriceFeedProperty.MarketSession)
422+
);
408423
} else {
409424
// This should never happen due to validation in parseFeedProperty
410425
revert("Unexpected property");
@@ -513,6 +528,17 @@ library PythLazerLib {
513528
);
514529
}
515530

531+
/// @notice Check if market session exists
532+
function hasMarketSession(
533+
PythLazerStructs.Feed memory feed
534+
) public pure returns (bool) {
535+
return
536+
_hasValue(
537+
feed,
538+
uint8(PythLazerStructs.PriceFeedProperty.MarketSession)
539+
);
540+
}
541+
516542
// Requested helpers — property included in this update
517543
function isPriceRequested(
518544
PythLazerStructs.Feed memory feed
@@ -601,6 +627,16 @@ library PythLazerLib {
601627
);
602628
}
603629

630+
function isMarketSessionRequested(
631+
PythLazerStructs.Feed memory feed
632+
) public pure returns (bool) {
633+
return
634+
_isRequested(
635+
feed,
636+
uint8(PythLazerStructs.PriceFeedProperty.MarketSession)
637+
);
638+
}
639+
604640
// Safe getter functions (revert if property doesn't exist)
605641

606642
/// @notice Get price (reverts if not exists)
@@ -731,4 +767,19 @@ library PythLazerLib {
731767
);
732768
return feed._fundingRateInterval;
733769
}
770+
771+
/// @notice Get market session (reverts if not exists)
772+
function getMarketSession(
773+
PythLazerStructs.Feed memory feed
774+
) public pure returns (int16) {
775+
require(
776+
isMarketSessionRequested(feed),
777+
"Market session is not requested for the timestamp"
778+
);
779+
require(
780+
hasMarketSession(feed),
781+
"Market session is not present for the timestamp"
782+
);
783+
return feed._marketSession;
784+
}
734785
}

lazer/contracts/evm/src/PythLazerStructs.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ library PythLazerStructs {
1919
Confidence,
2020
FundingRate,
2121
FundingTimestamp,
22-
FundingRateInterval
22+
FundingRateInterval,
23+
MarketSession
2324
}
2425

2526
// Tri-state for a property's availability within a feed at a given timestamp
@@ -52,6 +53,8 @@ library PythLazerStructs {
5253
int64 _fundingRate;
5354
uint64 _fundingTimestamp;
5455
uint64 _fundingRateInterval;
56+
// Slot 4: 2 bytes
57+
int16 _marketSession;
5558
}
5659

5760
struct Update {

lazer/contracts/evm/test/PythLazer.t.sol

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ contract PythLazerTest is Test {
129129
}
130130

131131
/// @notice Build a property with given ID and encoded value bytes
132-
/// @param propertyId The property ID (0-8)
132+
/// @param propertyId The property ID (0-9)
133133
/// @param valueBytes The encoded value (int64/uint64 = 8 bytes, uint16/int16 = 2 bytes)
134134
function buildProperty(
135135
uint8 propertyId,
@@ -139,6 +139,7 @@ contract PythLazerTest is Test {
139139
if (propertyId >= 6 && propertyId <= 8) {
140140
return abi.encodePacked(propertyId, uint8(1), valueBytes);
141141
} else {
142+
// MarketSession (9) and other properties don't need the exists flag
142143
return abi.encodePacked(propertyId, valueBytes);
143144
}
144145
}
@@ -171,9 +172,9 @@ contract PythLazerTest is Test {
171172
return abi.encodePacked(value);
172173
}
173174

174-
/// @notice Test parsing single feed with all 9 properties
175+
/// @notice Test parsing single feed with all 10 properties
175176
function test_parseUpdate_singleFeed_allProperties() public pure {
176-
bytes[] memory properties = new bytes[](9);
177+
bytes[] memory properties = new bytes[](10);
177178
properties[0] = buildProperty(0, encodeInt64(100000000)); // price
178179
properties[1] = buildProperty(1, encodeInt64(99000000)); // bestBid
179180
properties[2] = buildProperty(2, encodeInt64(101000000)); // bestAsk
@@ -183,6 +184,7 @@ contract PythLazerTest is Test {
183184
properties[6] = buildProperty(6, encodeInt64(123456)); // fundingRate
184185
properties[7] = buildProperty(7, encodeUint64(1234567890)); // fundingTimestamp
185186
properties[8] = buildProperty(8, encodeUint64(3600)); // fundingRateInterval
187+
properties[9] = buildProperty(9, encodeInt16(0)); // marketSession (0 = REGULAR)
186188

187189
bytes[] memory feeds = new bytes[](1);
188190
feeds[0] = buildFeedDataMulti(1, properties); // feedId = 1
@@ -216,6 +218,7 @@ contract PythLazerTest is Test {
216218
assertEq(feed._fundingRate, 123456);
217219
assertEq(feed._fundingTimestamp, 1234567890);
218220
assertEq(feed._fundingRateInterval, 3600);
221+
assertEq(feed._marketSession, 0);
219222

220223
// Verify exists flags (all should be set)
221224
assertTrue(PythLazerLib.hasPrice(feed));
@@ -227,6 +230,7 @@ contract PythLazerTest is Test {
227230
assertTrue(PythLazerLib.hasFundingRate(feed));
228231
assertTrue(PythLazerLib.hasFundingTimestamp(feed));
229232
assertTrue(PythLazerLib.hasFundingRateInterval(feed));
233+
assertTrue(PythLazerLib.hasMarketSession(feed));
230234
}
231235

232236
/// @notice Test parsing single feed with minimal properties
@@ -270,6 +274,7 @@ contract PythLazerTest is Test {
270274
assertFalse(PythLazerLib.isFundingRateRequested(feed));
271275
assertFalse(PythLazerLib.isFundingTimestampRequested(feed));
272276
assertFalse(PythLazerLib.isFundingRateIntervalRequested(feed));
277+
assertFalse(PythLazerLib.isMarketSessionRequested(feed));
273278
}
274279

275280
/// @notice Test parsing multiple feeds
@@ -325,6 +330,7 @@ contract PythLazerTest is Test {
325330
assertFalse(
326331
PythLazerLib.isFundingRateIntervalRequested(update.feeds[0])
327332
);
333+
assertFalse(PythLazerLib.isMarketSessionRequested(update.feeds[0]));
328334

329335
// Verify Feed 2
330336
assertEq(update.feeds[1].feedId, 2);
@@ -342,6 +348,7 @@ contract PythLazerTest is Test {
342348
assertFalse(
343349
PythLazerLib.isFundingRateIntervalRequested(update.feeds[1])
344350
);
351+
assertFalse(PythLazerLib.isMarketSessionRequested(update.feeds[1]));
345352

346353
// Verify Feed 3
347354
assertEq(update.feeds[2].feedId, 3);
@@ -359,6 +366,80 @@ contract PythLazerTest is Test {
359366
assertFalse(
360367
PythLazerLib.isFundingRateIntervalRequested(update.feeds[2])
361368
);
369+
assertFalse(PythLazerLib.isMarketSessionRequested(update.feeds[2]));
370+
}
371+
372+
/// @notice Test parsing MarketSession property
373+
function test_parseUpdate_marketSession() public pure {
374+
bytes[] memory properties = new bytes[](3);
375+
properties[0] = buildProperty(0, encodeInt64(100000000)); // price
376+
properties[1] = buildProperty(4, encodeInt16(-8)); // exponent
377+
properties[2] = buildProperty(9, encodeInt16(1)); // marketSession (1 = PRE_MARKET)
378+
379+
bytes[] memory feeds = new bytes[](1);
380+
feeds[0] = buildFeedDataMulti(1, properties);
381+
382+
bytes memory payload = buildPayload(
383+
1700000000,
384+
PythLazerStructs.Channel.RealTime,
385+
feeds
386+
);
387+
388+
PythLazerStructs.Update memory update = PythLazerLib
389+
.parseUpdateFromPayload(payload);
390+
391+
PythLazerStructs.Feed memory feed = update.feeds[0];
392+
393+
assertEq(feed._marketSession, 1);
394+
assertTrue(PythLazerLib.hasMarketSession(feed));
395+
assertTrue(PythLazerLib.isMarketSessionRequested(feed));
396+
assertEq(PythLazerLib.getMarketSession(feed), 1);
397+
398+
// Test different market session values
399+
bytes[] memory properties2 = new bytes[](3);
400+
properties2[0] = buildProperty(0, encodeInt64(200000000));
401+
properties2[1] = buildProperty(4, encodeInt16(-8));
402+
properties2[2] = buildProperty(9, encodeInt16(2)); // POST_MARKET
403+
404+
bytes[] memory feeds2 = new bytes[](1);
405+
feeds2[0] = buildFeedDataMulti(2, properties2);
406+
407+
bytes memory payload2 = buildPayload(
408+
1700000001,
409+
PythLazerStructs.Channel.RealTime,
410+
feeds2
411+
);
412+
413+
PythLazerStructs.Update memory update2 = PythLazerLib
414+
.parseUpdateFromPayload(payload2);
415+
416+
PythLazerStructs.Feed memory feed2 = update2.feeds[0];
417+
assertEq(feed2._marketSession, 2);
418+
assertEq(PythLazerLib.getMarketSession(feed2), 2);
419+
}
420+
421+
/// @notice Test MarketSession getter when not requested
422+
function test_parseUpdate_marketSessionNotRequested() public pure {
423+
bytes[] memory properties = new bytes[](2);
424+
properties[0] = buildProperty(0, encodeInt64(100000000));
425+
properties[1] = buildProperty(4, encodeInt16(-8));
426+
427+
bytes[] memory feeds = new bytes[](1);
428+
feeds[0] = buildFeedDataMulti(1, properties);
429+
430+
bytes memory payload = buildPayload(
431+
1700000000,
432+
PythLazerStructs.Channel.RealTime,
433+
feeds
434+
);
435+
436+
PythLazerStructs.Update memory update = PythLazerLib
437+
.parseUpdateFromPayload(payload);
438+
439+
PythLazerStructs.Feed memory feed = update.feeds[0];
440+
441+
assertFalse(PythLazerLib.isMarketSessionRequested(feed));
442+
assertFalse(PythLazerLib.hasMarketSession(feed));
362443
}
363444

364445
/// @notice Test when optional properties are zero
@@ -471,8 +552,8 @@ contract PythLazerTest is Test {
471552

472553
/// @notice Test unknown property ID
473554
function test_parseUpdate_unknownProperty() public {
474-
// Build payload with invalid property ID (99)
475-
bytes memory invalidProperty = buildProperty(99, encodeInt64(100));
555+
// Build payload with invalid property ID (10, which is > 9)
556+
bytes memory invalidProperty = buildProperty(10, encodeInt64(100));
476557

477558
bytes[] memory properties = new bytes[](1);
478559
properties[0] = invalidProperty;

lazer/contracts/evm/test/PythLazerApi.t.sol

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,18 @@ contract PythLazerApiTest is Test {
144144
PythLazerLib.isFundingRateIntervalRequested(feed3),
145145
"Feed 3: funding rate interval should NOT be requested"
146146
);
147+
// MarketSession may or may not be requested depending on API response
148+
// If present, verify it can be accessed correctly
149+
if (PythLazerLib.isMarketSessionRequested(feed3)) {
150+
assertTrue(
151+
PythLazerLib.hasMarketSession(feed3),
152+
"Feed 3: if market session is requested, it should be present"
153+
);
154+
int16 marketSession = PythLazerLib.getMarketSession(feed3);
155+
// MarketSession should be a valid value (0-4 based on enum)
156+
assertGe(marketSession, 0, "Feed 3: market session should be >= 0");
157+
assertLe(marketSession, 4, "Feed 3: market session should be <= 4");
158+
}
147159

148160
// Verify parsed values match API reference values exactly
149161
assertEq(
@@ -289,6 +301,26 @@ contract PythLazerApiTest is Test {
289301
PythLazerLib.isConfidenceRequested(feed112),
290302
"Feed 112: confidence should NOT be requested"
291303
);
304+
// MarketSession may or may not be requested depending on API response
305+
// If present, verify it can be accessed correctly
306+
if (PythLazerLib.isMarketSessionRequested(feed112)) {
307+
assertTrue(
308+
PythLazerLib.hasMarketSession(feed112),
309+
"Feed 112: if market session is requested, it should be present"
310+
);
311+
int16 marketSession = PythLazerLib.getMarketSession(feed112);
312+
// MarketSession should be a valid value (0-4 based on enum)
313+
assertGe(
314+
marketSession,
315+
0,
316+
"Feed 112: market session should be >= 0"
317+
);
318+
assertLe(
319+
marketSession,
320+
4,
321+
"Feed 112: market session should be <= 4"
322+
);
323+
}
292324

293325
// Verify parsed values match API reference values exactly
294326
assertEq(

0 commit comments

Comments
 (0)