Skip to content

Commit 8c18375

Browse files
feat(lazer/evm): update example to use new PythLazerLib struct-based parsing
- Update ExampleReceiver.sol to use parseUpdateFromPayload() for high-level struct-based parsing - Add helper function pattern for memory-to-calldata conversion - Use safe getter functions (hasPrice, getPrice, etc.) for property extraction - Add utility functions: getCurrentPrice(), getSpread(), isPriceFresh(), setTargetFeedId() - Update tests to use TransparentUpgradeableProxy for PythLazer initialization - Add comprehensive test cases for struct-based parsing, fee validation, and helper functions - Update README with detailed documentation on contract architecture, tri-state property system, and integration guide - Update pyth-crosschain submodule to include PythLazerStructs.sol Co-Authored-By: [email protected] <[email protected]>
1 parent b48bc74 commit 8c18375

File tree

4 files changed

+378
-144
lines changed

4 files changed

+378
-144
lines changed

lazer/evm/README.md

Lines changed: 136 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ This directory contains Solidity smart contract examples demonstrating how to in
44

55
## What is Pyth Lazer?
66

7-
Pyth Lazer is a high-performance, low-latency price feed service that provides real-time financial market data to blockchain applications. It supports multiple blockchain networks and offers both JSON and binary message formats for optimal performance.
7+
Pyth Lazer is a high-performance, low-latency price feed protocol that provides real-time financial market data to blockchain applications. Unlike traditional pull-based oracles, Pyth Lazer uses ECDSA signatures for fast verification and delivers sub-second price updates via WebSocket streams.
8+
9+
Key features of Pyth Lazer include support for multiple blockchain networks, a tri-state property system that distinguishes between present values, applicable but missing values, and not applicable properties, and support for various price feed properties including price, confidence, bid/ask prices, funding rates, and market session information.
810

911
## Prerequisites
1012

@@ -30,131 +32,195 @@ Before running these examples, make sure you have the following installed:
3032
forge build
3133
```
3234

35+
## Contract Architecture
36+
37+
The example uses three main components from the Pyth Lazer SDK:
38+
39+
**PythLazer.sol** is the main contract that verifies ECDSA signatures from trusted signers. It manages trusted signer keys with expiration times and collects verification fees for each update.
40+
41+
**PythLazerLib.sol** is a library that provides parsing functions for Lazer payloads. It includes both low-level parsing functions like `parsePayloadHeader()` and `parseFeedHeader()`, as well as a high-level `parseUpdateFromPayload()` function that returns a structured `Update` object.
42+
43+
**PythLazerStructs.sol** defines the data structures used by the library, including the `Update` struct containing timestamp, channel, and feeds array, the `Feed` struct with all price properties and a tri-state map, and enums for `Channel`, `PriceFeedProperty`, `PropertyState`, and `MarketSession`.
44+
3345
## Examples
3446

35-
### 1. ExampleReceiver Contract (`src/ExampleReceiver.sol`)
36-
Demonstrates how to receive and process Pyth Lazer price updates in a smart contract.
47+
### ExampleReceiver Contract (`src/ExampleReceiver.sol`)
48+
49+
This contract demonstrates the recommended approach for receiving and processing Pyth Lazer price updates using the high-level struct-based parsing.
3750

38-
**What it does:**
39-
- Verifies Pyth Lazer signatures on-chain
40-
- Parses price feed payloads to extract price data
51+
**Key Features:**
52+
- Verifies Pyth Lazer signatures on-chain via the PythLazer contract
53+
- Uses `parseUpdateFromPayload()` for clean, structured parsing
54+
- Extracts all available price feed properties using safe getter functions
4155
- Handles verification fees and refunds excess payments
42-
- Extracts multiple price feed properties (price, timestamps, exponents, etc.)
43-
- Filters updates by feed ID and timestamp
56+
- Filters updates by target feed ID and timestamp freshness
57+
- Emits events for price updates
4458

45-
**Key Functions:**
59+
**Main Function:**
4660
```solidity
4761
function updatePrice(bytes calldata update) public payable
4862
```
4963

50-
**How to run the test:**
51-
```bash
52-
forge test -v
53-
```
64+
This function performs the following steps:
65+
1. Pays the verification fee to PythLazer and verifies the signature
66+
2. Parses the payload into a structured `Update` object
67+
3. Iterates through feeds to find the target feed
68+
4. Extracts available properties using safe getter functions like `hasPrice()` and `getPrice()`
69+
5. Updates contract state and emits a `PriceUpdated` event
5470

55-
### 2. Test Suite (`test/ExampleReceiver.t.sol`)
56-
Comprehensive test demonstrating the contract functionality with real price data.
71+
**Helper Functions:**
72+
- `getCurrentPrice()` - Returns the current price and exponent
73+
- `getSpread()` - Returns the bid-ask spread
74+
- `isPriceFresh(maxAge)` - Checks if the price is within the specified age
75+
- `setTargetFeedId(feedId)` - Updates the target feed ID
5776

58-
**What it does:**
59-
- Sets up a PythLazer contract with trusted signer
60-
- Creates and funds test accounts
61-
- Submits a price update with verification fee
62-
- Validates parsed price data and fee handling
77+
### Test Suite (`test/ExampleReceiver.t.sol`)
78+
79+
Comprehensive tests demonstrating the contract functionality with real signed price data.
80+
81+
**Test Cases:**
82+
- `test_updatePrice_structBased()` - Tests the main price update flow
83+
- `test_revert_insufficientFee()` - Verifies fee requirement
84+
- `test_nonTargetFeed_noUpdate()` - Ensures non-target feeds don't update state
85+
- `test_setTargetFeedId()` - Tests feed ID configuration
86+
- `test_helperFunctions()` - Tests utility functions
6387

6488
**How to run:**
6589
```bash
66-
forge test -v
90+
forge test -vv
6791
```
6892

69-
**Expected output:**
93+
**Expected output for the struct-based test:**
7094
- **Timestamp**: `1738270008001000` (microseconds since Unix epoch)
7195
- **Price**: `100000000` (raw price value)
7296
- **Exponent**: `-8` (price = 100000000 × 10^-8 = $1.00)
7397
- **Publisher Count**: `1`
7498

7599
## Understanding Price Data
76100

77-
Pyth Lazer prices use a fixed-point representation:
78-
```
79-
actual_price = price × 10^exponent
80-
```
101+
Pyth Lazer prices use a fixed-point representation where the actual price equals the raw price multiplied by 10 raised to the power of the exponent.
81102

82103
**Example from the test:**
83104
- Raw price: `100000000`
84105
- Exponent: `-8`
85106
- Actual price: `100000000 × 10^-8 = $1.00`
86107

87-
### Feed Properties
108+
### Available Feed Properties
109+
110+
The `Feed` struct can contain the following properties, each with a tri-state indicating whether it's present, applicable but missing, or not applicable:
88111

89-
The contract can extract multiple price feed properties:
112+
| Property | Type | Description |
113+
|----------|------|-------------|
114+
| Price | int64 | Main price value |
115+
| BestBidPrice | int64 | Highest bid price in the market |
116+
| BestAskPrice | int64 | Lowest ask price in the market |
117+
| Exponent | int16 | Decimal exponent for price normalization |
118+
| PublisherCount | uint16 | Number of publishers contributing to this price |
119+
| Confidence | uint64 | Confidence interval (1 standard deviation) |
120+
| FundingRate | int64 | Perpetual funding rate (optional) |
121+
| FundingTimestamp | uint64 | Timestamp of funding rate (optional) |
122+
| FundingRateInterval | uint64 | Funding rate interval in seconds (optional) |
123+
| MarketSession | enum | Market session status (Regular, PreMarket, PostMarket, OverNight, Closed) |
90124

91-
- **Price**: Main price value
92-
- **BestBidPrice**: Highest bid price in the market
93-
- **BestAskPrice**: Lowest ask price in the market
94-
- **Exponent**: Decimal exponent for price normalization
95-
- **PublisherCount**: Number of publishers contributing to this price
125+
### Tri-State Property System
126+
127+
Each property in a feed has a tri-state that indicates its availability:
128+
129+
- **Present**: The property has a valid value for this timestamp
130+
- **ApplicableButMissing**: The property was requested but no value is available
131+
- **NotApplicable**: The property was not included in this update
132+
133+
Use the `has*()` functions (e.g., `hasPrice()`, `hasExponent()`) to check if a property is present before accessing it with the `get*()` functions.
96134

97135
## Integration Guide
98136

99137
To integrate Pyth Lazer into your own contract:
100138

101-
1. **Import the required libraries:**
102-
```solidity
103-
import {PythLazer} from "pyth-lazer/PythLazer.sol";
104-
import {PythLazerLib} from "pyth-lazer/PythLazerLib.sol";
105-
```
139+
### Step 1: Import the required contracts
106140

107-
2. **Set up the PythLazer contract:**
108-
```solidity
109-
PythLazer pythLazer;
110-
constructor(address pythLazerAddress) {
111-
pythLazer = PythLazer(pythLazerAddress);
112-
}
113-
```
141+
```solidity
142+
import {PythLazer} from "pyth-lazer/PythLazer.sol";
143+
import {PythLazerLib} from "pyth-lazer/PythLazerLib.sol";
144+
import {PythLazerStructs} from "pyth-lazer/PythLazerStructs.sol";
145+
```
114146

115-
3. **Handle verification fees:**
116-
```solidity
117-
uint256 verification_fee = pythLazer.verification_fee();
118-
require(msg.value >= verification_fee, "Insufficient fee");
119-
```
147+
### Step 2: Store the PythLazer contract reference
120148

121-
4. **Verify and parse updates:**
122-
```solidity
123-
(bytes memory payload,) = pythLazer.verifyUpdate{value: verification_fee}(update);
124-
// Parse payload using PythLazerLib functions
125-
```
149+
```solidity
150+
PythLazer public pythLazer;
126151
127-
## Configuration
152+
constructor(address pythLazerAddress) {
153+
pythLazer = PythLazer(pythLazerAddress);
154+
}
155+
```
128156

129-
### Feed IDs
157+
### Step 3: Verify updates and parse the payload
130158

131-
The example filters for feed ID `6`. To use different feeds:
159+
```solidity
160+
function updatePrice(bytes calldata update) public payable {
161+
// Pay fee and verify signature
162+
uint256 fee = pythLazer.verification_fee();
163+
require(msg.value >= fee, "Insufficient fee");
164+
(bytes memory payload, ) = pythLazer.verifyUpdate{value: fee}(update);
165+
166+
// Parse using helper (converts memory to calldata)
167+
PythLazerStructs.Update memory parsedUpdate = this.parsePayloadExternal(payload);
168+
169+
// Process feeds...
170+
}
171+
172+
// Helper to convert memory bytes to calldata for the library
173+
function parsePayloadExternal(bytes calldata payload)
174+
external view returns (PythLazerStructs.Update memory) {
175+
return PythLazerLib.parseUpdateFromPayload(payload);
176+
}
177+
```
132178

133-
1. Update the feed ID check in `updatePrice()`:
134-
```solidity
135-
if (feedId == YOUR_FEED_ID && _timestamp > timestamp) {
136-
// Update logic
137-
}
138-
```
179+
### Step 4: Extract price data using safe getters
180+
181+
```solidity
182+
for (uint256 i = 0; i < parsedUpdate.feeds.length; i++) {
183+
PythLazerStructs.Feed memory feed = parsedUpdate.feeds[i];
184+
uint32 feedId = PythLazerLib.getFeedId(feed);
185+
186+
if (feedId == targetFeedId) {
187+
if (PythLazerLib.hasPrice(feed)) {
188+
int64 price = PythLazerLib.getPrice(feed);
189+
}
190+
if (PythLazerLib.hasExponent(feed)) {
191+
int16 exponent = PythLazerLib.getExponent(feed);
192+
}
193+
// ... extract other properties as needed
194+
}
195+
}
196+
```
139197

140-
2. Obtain feed IDs from the Pyth Lazer documentation or API
198+
## Deployed Contract Addresses
199+
200+
For production deployments, use the official PythLazer contract addresses. You can find the latest addresses in the [Pyth Network documentation](https://docs.pyth.network/price-feeds/contract-addresses).
141201

142202
## Troubleshooting
143203

144204
### Common Issues
145205

146-
1. **Build Errors**: Make sure all dependencies are installed with `forge install`
206+
**Build Errors**: Make sure all dependencies are installed with `forge install`. If you see missing file errors, try updating the pyth-crosschain submodule:
207+
```bash
208+
cd lib/pyth-crosschain && git fetch origin && git checkout origin/main
209+
```
210+
211+
**InvalidInitialization Error in Tests**: The PythLazer contract uses OpenZeppelin's upgradeable pattern. Deploy it via a TransparentUpgradeableProxy as shown in the test file.
147212

148-
2. **Test Failures**: Ensure you're using a compatible Foundry version and all submodules are properly initialized
213+
**Memory to Calldata Conversion**: The `parseUpdateFromPayload()` function expects calldata bytes, but `verifyUpdate()` returns memory bytes. Use the external helper pattern shown in the example to convert between them.
149214

150-
3. **Gas Issues**: The contract includes gas optimization for parsing multiple feed properties
215+
**Gas Optimization**: For gas-sensitive applications, consider using the low-level parsing functions (`parsePayloadHeader`, `parseFeedHeader`, `parseFeedProperty`) to parse only the properties you need.
151216

152217
## Resources
153218

154219
- [Pyth Network Documentation](https://docs.pyth.network/)
220+
- [Pyth Lazer Documentation](https://docs.pyth.network/lazer)
155221
- [Foundry Book](https://book.getfoundry.sh/)
156-
- [Pyth Lazer SDK](https://github.com/pyth-network/pyth-crosschain)
222+
- [Pyth Crosschain Repository](https://github.com/pyth-network/pyth-crosschain)
157223

158224
## License
159225

160-
This project is licensed under the Apache-2.0 license.
226+
This project is licensed under the Apache-2.0 license.

lazer/evm/lib/pyth-crosschain

Submodule pyth-crosschain updated 2728 files

0 commit comments

Comments
 (0)