@@ -24,36 +24,13 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
24
24
setPyth2WormholeEmitter (pyth2WormholeEmitter);
25
25
}
26
26
27
- function updatePriceBatchFromVm (bytes calldata encodedVm ) private returns (PythInternalStructs.BatchPriceAttestation memory bpa ) {
27
+ function updatePriceBatchFromVm (bytes calldata encodedVm ) private {
28
28
(IWormhole.VM memory vm , bool valid , string memory reason ) = wormhole ().parseAndVerifyVM (encodedVm);
29
29
30
30
require (valid, reason);
31
31
require (verifyPythVM (vm), "invalid data source chain/emitter ID " );
32
32
33
- PythInternalStructs.BatchPriceAttestation memory batch = parseBatchPriceAttestation (vm.payload);
34
-
35
- uint freshPrices = 0 ;
36
-
37
- for (uint i = 0 ; i < batch.attestations.length ; i++ ) {
38
- PythInternalStructs.PriceAttestation memory attestation = batch.attestations[i];
39
-
40
- PythInternalStructs.PriceInfo memory newPriceInfo = createNewPriceInfo (attestation);
41
- PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo (attestation.priceId);
42
-
43
- bool fresh = false ;
44
- if (newPriceInfo.price.publishTime > latestPrice.price.publishTime) {
45
- freshPrices += 1 ;
46
- fresh = true ;
47
- setLatestPriceInfo (attestation.priceId, newPriceInfo);
48
- }
49
-
50
- emit PriceFeedUpdate (attestation.priceId, fresh, vm.emitterChainId, vm.sequence, latestPrice.price.publishTime,
51
- newPriceInfo.price.publishTime, newPriceInfo.price.price, newPriceInfo.price.conf);
52
- }
53
-
54
- emit BatchPriceFeedUpdate (vm.emitterChainId, vm.sequence, batch.attestations.length , freshPrices);
55
-
56
- return batch;
33
+ parseAndProcessBatchPriceAttestation (vm);
57
34
}
58
35
59
36
function updatePriceFeeds (bytes [] calldata updateData ) public override payable {
@@ -76,146 +53,163 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
76
53
return singleUpdateFeeInWei () * updateData.length ;
77
54
}
78
55
79
- function createNewPriceInfo (PythInternalStructs.PriceAttestation memory pa ) private pure returns (PythInternalStructs.PriceInfo memory info ) {
80
- PythInternalStructs.PriceAttestationStatus status = PythInternalStructs.PriceAttestationStatus (pa.status);
81
- if (status == PythInternalStructs.PriceAttestationStatus.TRADING) {
82
- info.price.price = pa.price;
83
- info.price.conf = pa.conf;
84
- info.price.publishTime = pa.publishTime;
85
- info.emaPrice.publishTime = pa.publishTime;
86
- } else {
87
- info.price.price = pa.prevPrice;
88
- info.price.conf = pa.prevConf;
89
- info.price.publishTime = pa.prevPublishTime;
90
-
91
- // The EMA is last updated when the aggregate had trading status,
92
- // so, we use prev_publish_time (the time when the aggregate last had trading status).
93
- info.emaPrice.publishTime = pa.prevPublishTime;
94
- }
95
-
96
- info.price.expo = pa.expo;
97
- info.emaPrice.price = pa.emaPrice;
98
- info.emaPrice.conf = pa.emaConf;
99
- info.emaPrice.expo = pa.expo;
100
-
101
- return info;
102
- }
103
-
104
56
function verifyPythVM (IWormhole.VM memory vm ) private view returns (bool valid ) {
105
57
return isValidDataSource (vm.emitterChainId, vm.emitterAddress);
106
58
}
107
59
108
-
109
- function parseBatchPriceAttestation ( bytes memory encoded ) public pure returns (PythInternalStructs.BatchPriceAttestation memory bpa ) {
60
+ function parseAndProcessBatchPriceAttestation (IWormhole.VM memory vm ) internal {
61
+ bytes memory encoded = vm.payload;
110
62
uint index = 0 ;
111
63
112
64
// Check header
113
- bpa.header.magic = encoded.toUint32 (index);
114
- index += 4 ;
115
- require (bpa.header.magic == 0x50325748 , "invalid magic value " );
116
-
117
- bpa.header.versionMajor = encoded.toUint16 (index);
118
- index += 2 ;
119
- require (bpa.header.versionMajor == 3 , "invalid version major, expected 3 " );
120
-
121
- bpa.header.versionMinor = encoded.toUint16 (index);
122
- index += 2 ;
123
- require (bpa.header.versionMinor >= 0 , "invalid version minor, expected 0 or more " );
124
-
125
- bpa.header.hdrSize = encoded.toUint16 (index);
126
- index += 2 ;
127
-
128
- // NOTE(2022-04-19): Currently, only payloadId comes after
129
- // hdrSize. Future extra header fields must be read using a
130
- // separate offset to respect hdrSize, i.e.:
131
- //
132
- // uint hdrIndex = 0;
133
- // bpa.header.payloadId = encoded.toUint8(index + hdrIndex);
134
- // hdrIndex += 1;
135
- //
136
- // bpa.header.someNewField = encoded.toUint32(index + hdrIndex);
137
- // hdrIndex += 4;
138
- //
139
- // // Skip remaining unknown header bytes
140
- // index += bpa.header.hdrSize;
141
-
142
- bpa.header.payloadId = encoded.toUint8 (index);
143
-
144
- // Skip remaining unknown header bytes
145
- index += bpa.header.hdrSize;
146
-
147
- // Payload ID of 2 required for batch headerBa
148
- require (bpa.header.payloadId == 2 , "invalid payload ID, expected 2 for BatchPriceAttestation " );
65
+ {
66
+ uint32 magic = encoded.toUint32 (index);
67
+ index += 4 ;
68
+ require (magic == 0x50325748 , "invalid magic value " );
69
+
70
+ uint16 versionMajor = encoded.toUint16 (index);
71
+ index += 2 ;
72
+ require (versionMajor == 3 , "invalid version major, expected 3 " );
73
+
74
+ uint16 versionMinor = encoded.toUint16 (index);
75
+ index += 2 ;
76
+ require (versionMinor >= 0 , "invalid version minor, expected 0 or more " );
77
+
78
+ uint16 hdrSize = encoded.toUint16 (index);
79
+ index += 2 ;
80
+
81
+ // NOTE(2022-04-19): Currently, only payloadId comes after
82
+ // hdrSize. Future extra header fields must be read using a
83
+ // separate offset to respect hdrSize, i.e.:
84
+ //
85
+ // uint hdrIndex = 0;
86
+ // bpa.header.payloadId = encoded.toUint8(index + hdrIndex);
87
+ // hdrIndex += 1;
88
+ //
89
+ // bpa.header.someNewField = encoded.toUint32(index + hdrIndex);
90
+ // hdrIndex += 4;
91
+ //
92
+ // // Skip remaining unknown header bytes
93
+ // index += bpa.header.hdrSize;
94
+
95
+ uint8 payloadId = encoded.toUint8 (index);
96
+
97
+ // Skip remaining unknown header bytes
98
+ index += hdrSize;
99
+
100
+ // Payload ID of 2 required for batch headerBa
101
+ require (payloadId == 2 , "invalid payload ID, expected 2 for BatchPriceAttestation " );
102
+ }
149
103
150
104
// Parse the number of attestations
151
- bpa. nAttestations = encoded.toUint16 (index);
105
+ uint16 nAttestations = encoded.toUint16 (index);
152
106
index += 2 ;
153
107
154
108
// Parse the attestation size
155
- bpa. attestationSize = encoded.toUint16 (index);
109
+ uint16 attestationSize = encoded.toUint16 (index);
156
110
index += 2 ;
157
- require (encoded.length == (index + (bpa.attestationSize * bpa.nAttestations)), "invalid BatchPriceAttestation size " );
158
-
159
- bpa.attestations = new PythInternalStructs.PriceAttestation [](bpa.nAttestations);
111
+ require (encoded.length == (index + (attestationSize * nAttestations)), "invalid BatchPriceAttestation size " );
160
112
113
+ PythInternalStructs.PriceInfo memory info;
114
+ bytes32 priceId;
115
+ uint freshPrices = 0 ;
116
+
161
117
// Deserialize each attestation
162
- for (uint j= 0 ; j < bpa. nAttestations; j++ ) {
118
+ for (uint j= 0 ; j < nAttestations; j++ ) {
163
119
// NOTE: We don't advance the global index immediately.
164
120
// attestationIndex is an attestation-local offset used
165
121
// for readability and easier debugging.
166
122
uint attestationIndex = 0 ;
167
123
168
- // Attestation
169
- bpa.attestations[j].productId = encoded.toBytes32 (index + attestationIndex);
124
+ // Unused bytes32 product id
170
125
attestationIndex += 32 ;
171
126
172
- bpa.attestations[j]. priceId = encoded.toBytes32 (index + attestationIndex);
127
+ priceId = encoded.toBytes32 (index + attestationIndex);
173
128
attestationIndex += 32 ;
174
129
175
- bpa.attestations[j] .price = int64 (encoded.toUint64 (index + attestationIndex));
130
+ info.price .price = int64 (encoded.toUint64 (index + attestationIndex));
176
131
attestationIndex += 8 ;
177
132
178
- bpa.attestations[j] .conf = encoded.toUint64 (index + attestationIndex);
133
+ info.price .conf = encoded.toUint64 (index + attestationIndex);
179
134
attestationIndex += 8 ;
180
135
181
- bpa.attestations[j].expo = int32 (encoded.toUint32 (index + attestationIndex));
136
+ info.price.expo = int32 (encoded.toUint32 (index + attestationIndex));
137
+ info.emaPrice.expo = info.price.expo;
182
138
attestationIndex += 4 ;
183
139
184
- bpa.attestations[j]. emaPrice = int64 (encoded.toUint64 (index + attestationIndex));
140
+ info. emaPrice.price = int64 (encoded.toUint64 (index + attestationIndex));
185
141
attestationIndex += 8 ;
186
142
187
- bpa.attestations[j].emaConf = encoded.toUint64 (index + attestationIndex);
143
+ info.emaPrice.conf = encoded.toUint64 (index + attestationIndex);
188
144
attestationIndex += 8 ;
189
145
190
- bpa.attestations[j].status = encoded.toUint8 (index + attestationIndex);
191
- attestationIndex += 1 ;
192
-
193
- bpa.attestations[j].numPublishers = encoded.toUint32 (index + attestationIndex);
194
- attestationIndex += 4 ;
195
-
196
- bpa.attestations[j].maxNumPublishers = encoded.toUint32 (index + attestationIndex);
197
- attestationIndex += 4 ;
146
+ {
147
+ // Status is an enum (encoded as uint8) with the following values:
148
+ // 0 = UNKNOWN: The price feed is not currently updating for an unknown reason.
149
+ // 1 = TRADING: The price feed is updating as expected.
150
+ // 2 = HALTED: The price feed is not currently updating because trading in the product has been halted.
151
+ // 3 = AUCTION: The price feed is not currently updating because an auction is setting the price.
152
+ uint8 status = encoded.toUint8 (index + attestationIndex);
153
+ attestationIndex += 1 ;
154
+
155
+ // Unused uint32 numPublishers
156
+ attestationIndex += 4 ;
157
+
158
+ // Unused uint32 numPublishers
159
+ attestationIndex += 4 ;
160
+
161
+ // Unused uint64 attestationTime
162
+ attestationIndex += 8 ;
163
+
164
+ info.price.publishTime = encoded.toUint64 (index + attestationIndex);
165
+ info.emaPrice.publishTime = info.price.publishTime;
166
+ attestationIndex += 8 ;
167
+
168
+ if (status == 1 ) { // status == TRADING
169
+ attestationIndex += 24 ;
170
+ } else {
171
+ // If status is not trading then the latest available price is
172
+ // the previous price info that are passed here.
173
+
174
+ // Previous publish time
175
+ info.price.publishTime = encoded.toUint64 (index + attestationIndex);
176
+ attestationIndex += 8 ;
177
+
178
+ // Previous price
179
+ info.price.price = int64 (encoded.toUint64 (index + attestationIndex));
180
+ attestationIndex += 8 ;
181
+
182
+ // Previous confidence
183
+ info.price.conf = encoded.toUint64 (index + attestationIndex);
184
+ attestationIndex += 8 ;
185
+
186
+ // The EMA is last updated when the aggregate had trading status,
187
+ // so, we use previous publish time here too.
188
+ info.emaPrice.publishTime = info.price.publishTime;
189
+ }
190
+ }
198
191
199
- bpa.attestations[j].attestationTime = encoded.toUint64 (index + attestationIndex);
200
- attestationIndex += 8 ;
192
+ require (attestationIndex <= attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes " );
201
193
202
- bpa.attestations[j].publishTime = encoded. toUint64 (index + attestationIndex);
203
- attestationIndex += 8 ;
194
+ // Respect specified attestation size for forward-compat
195
+ index += attestationSize ;
204
196
205
- bpa.attestations[j].prevPublishTime = encoded. toUint64 (index + attestationIndex);
206
- attestationIndex += 8 ;
197
+ // Store the attestation
198
+ PythInternalStructs.PriceInfo memory latestPrice = latestPriceInfo (priceId) ;
207
199
208
- bpa.attestations[j].prevPrice = int64 (encoded.toUint64 (index + attestationIndex));
209
- attestationIndex += 8 ;
200
+ bool fresh = false ;
201
+ if (info.price.publishTime > latestPrice.price.publishTime) {
202
+ freshPrices += 1 ;
203
+ fresh = true ;
204
+ setLatestPriceInfo (priceId, info);
205
+ }
210
206
211
- bpa.attestations[j].prevConf = encoded.toUint64 (index + attestationIndex);
212
- attestationIndex += 8 ;
207
+ emit PriceFeedUpdate (priceId, fresh, vm.emitterChainId, vm.sequence, latestPrice.price.publishTime,
208
+ info.price.publishTime, info.price.price, info.price.conf);
209
+ }
213
210
214
- require (attestationIndex <= bpa.attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes " );
215
211
216
- // Respect specified attestation size for forward-compat
217
- index += bpa.attestationSize;
218
- }
212
+ emit BatchPriceFeedUpdate (vm.emitterChainId, vm.sequence, nAttestations, freshPrices);
219
213
}
220
214
221
215
function queryPriceFeed (bytes32 id ) public view override returns (PythStructs.PriceFeed memory priceFeed ){
0 commit comments