@@ -37,8 +37,10 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
37
37
uint requiredFee = getUpdateFee (updateData);
38
38
require (msg .value >= requiredFee, "insufficient paid fee amount " );
39
39
40
- for (uint i = 0 ; i < updateData.length ; i ++ ) {
40
+ for (uint i = 0 ; i < updateData.length ; ) {
41
41
updatePriceBatchFromVm (updateData[i]);
42
+
43
+ unchecked { i++ ; }
42
44
}
43
45
44
46
emit UpdatePriceFeeds (msg .sender , updateData.length , requiredFee);
@@ -58,158 +60,168 @@ abstract contract Pyth is PythGetters, PythSetters, AbstractPyth {
58
60
}
59
61
60
62
function parseAndProcessBatchPriceAttestation (IWormhole.VM memory vm ) internal {
61
- bytes memory encoded = vm.payload;
62
- uint index = 0 ;
63
-
64
- // Check header
65
- {
66
- uint32 magic = encoded.toUint32 (index);
67
- index += 4 ;
68
- require (magic == 0x50325748 , "invalid magic value " );
63
+ // Most of the math operations below are simple additions.
64
+ // In the places that there is more complex operation there is
65
+ // a comment explaining why it is safe. Also, byteslib
66
+ // operations have proper require.
67
+ unchecked {
68
+ bytes memory encoded = vm.payload;
69
+ uint index = 0 ;
70
+
71
+ // Check header
72
+ {
73
+ uint32 magic = encoded.toUint32 (index);
74
+ index += 4 ;
75
+ require (magic == 0x50325748 , "invalid magic value " );
76
+
77
+ uint16 versionMajor = encoded.toUint16 (index);
78
+ index += 2 ;
79
+ require (versionMajor == 3 , "invalid version major, expected 3 " );
80
+
81
+ uint16 versionMinor = encoded.toUint16 (index);
82
+ index += 2 ;
83
+ require (versionMinor >= 0 , "invalid version minor, expected 0 or more " );
84
+
85
+ uint16 hdrSize = encoded.toUint16 (index);
86
+ index += 2 ;
87
+
88
+ // NOTE(2022-04-19): Currently, only payloadId comes after
89
+ // hdrSize. Future extra header fields must be read using a
90
+ // separate offset to respect hdrSize, i.e.:
91
+ //
92
+ // uint hdrIndex = 0;
93
+ // bpa.header.payloadId = encoded.toUint8(index + hdrIndex);
94
+ // hdrIndex += 1;
95
+ //
96
+ // bpa.header.someNewField = encoded.toUint32(index + hdrIndex);
97
+ // hdrIndex += 4;
98
+ //
99
+ // // Skip remaining unknown header bytes
100
+ // index += bpa.header.hdrSize;
101
+
102
+ uint8 payloadId = encoded.toUint8 (index);
103
+
104
+ // Skip remaining unknown header bytes
105
+ index += hdrSize;
106
+
107
+ // Payload ID of 2 required for batch headerBa
108
+ require (payloadId == 2 , "invalid payload ID, expected 2 for BatchPriceAttestation " );
109
+ }
69
110
70
- uint16 versionMajor = encoded.toUint16 (index);
111
+ // Parse the number of attestations
112
+ uint16 nAttestations = encoded.toUint16 (index);
71
113
index += 2 ;
72
- require (versionMajor == 3 , "invalid version major, expected 3 " );
73
114
74
- uint16 versionMinor = encoded.toUint16 (index);
115
+ // Parse the attestation size
116
+ uint16 attestationSize = encoded.toUint16 (index);
75
117
index += 2 ;
76
- require (versionMinor >= 0 , "invalid version minor, expected 0 or more " );
77
118
78
- uint16 hdrSize = encoded.toUint16 (index);
79
- index += 2 ;
119
+ // Given the message is valid the arithmetic below should not overflow, and
120
+ // even if it overflows then the require would fail.
121
+ require (encoded.length == (index + (attestationSize * nAttestations)), "invalid BatchPriceAttestation size " );
80
122
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
- }
123
+ PythInternalStructs.PriceInfo memory info;
124
+ bytes32 priceId;
125
+ uint freshPrices = 0 ;
126
+
127
+ // Deserialize each attestation
128
+ for (uint j= 0 ; j < nAttestations; j++ ) {
129
+ // NOTE: We don't advance the global index immediately.
130
+ // attestationIndex is an attestation-local offset used
131
+ // for readability and easier debugging.
132
+ uint attestationIndex = 0 ;
103
133
104
- // Parse the number of attestations
105
- uint16 nAttestations = encoded.toUint16 (index);
106
- index += 2 ;
134
+ // Unused bytes32 product id
135
+ attestationIndex += 32 ;
107
136
108
- // Parse the attestation size
109
- uint16 attestationSize = encoded.toUint16 (index);
110
- index += 2 ;
111
- require (encoded.length == (index + (attestationSize * nAttestations)), "invalid BatchPriceAttestation size " );
137
+ priceId = encoded.toBytes32 (index + attestationIndex);
138
+ attestationIndex += 32 ;
112
139
113
- PythInternalStructs.PriceInfo memory info;
114
- bytes32 priceId;
115
- uint freshPrices = 0 ;
116
-
117
- // Deserialize each attestation
118
- for (uint j= 0 ; j < nAttestations; j++ ) {
119
- // NOTE: We don't advance the global index immediately.
120
- // attestationIndex is an attestation-local offset used
121
- // for readability and easier debugging.
122
- uint attestationIndex = 0 ;
140
+ info.price.price = int64 (encoded.toUint64 (index + attestationIndex));
141
+ attestationIndex += 8 ;
123
142
124
- // Unused bytes32 product id
125
- attestationIndex += 32 ;
143
+ info.price.conf = encoded. toUint64 (index + attestationIndex);
144
+ attestationIndex += 8 ;
126
145
127
- priceId = encoded.toBytes32 (index + attestationIndex);
128
- attestationIndex += 32 ;
146
+ info.price.expo = int32 (encoded.toUint32 (index + attestationIndex));
147
+ info.emaPrice.expo = info.price.expo;
148
+ attestationIndex += 4 ;
129
149
130
- info.price .price = int64 (encoded.toUint64 (index + attestationIndex));
131
- attestationIndex += 8 ;
150
+ info.emaPrice .price = int64 (encoded.toUint64 (index + attestationIndex));
151
+ attestationIndex += 8 ;
132
152
133
- info.price .conf = encoded.toUint64 (index + attestationIndex);
134
- attestationIndex += 8 ;
153
+ info.emaPrice .conf = encoded.toUint64 (index + attestationIndex);
154
+ attestationIndex += 8 ;
135
155
136
- info.price.expo = int32 (encoded.toUint32 (index + attestationIndex));
137
- info.emaPrice.expo = info.price.expo;
138
- attestationIndex += 4 ;
156
+ {
157
+ // Status is an enum (encoded as uint8) with the following values:
158
+ // 0 = UNKNOWN: The price feed is not currently updating for an unknown reason.
159
+ // 1 = TRADING: The price feed is updating as expected.
160
+ // 2 = HALTED: The price feed is not currently updating because trading in the product has been halted.
161
+ // 3 = AUCTION: The price feed is not currently updating because an auction is setting the price.
162
+ uint8 status = encoded.toUint8 (index + attestationIndex);
163
+ attestationIndex += 1 ;
139
164
140
- info.emaPrice.price = int64 (encoded. toUint64 (index + attestationIndex));
141
- attestationIndex += 8 ;
165
+ // Unused uint32 numPublishers
166
+ attestationIndex += 4 ;
142
167
143
- info.emaPrice.conf = encoded. toUint64 (index + attestationIndex);
144
- attestationIndex += 8 ;
168
+ // Unused uint32 numPublishers
169
+ attestationIndex += 4 ;
145
170
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 ;
171
+ // Unused uint64 attestationTime
172
+ attestationIndex += 8 ;
157
173
158
- // Unused uint32 numPublishers
159
- attestationIndex += 4 ;
174
+ info.price.publishTime = encoded.toUint64 (index + attestationIndex);
175
+ info.emaPrice.publishTime = info.price.publishTime;
176
+ attestationIndex += 8 ;
160
177
161
- // Unused uint64 attestationTime
162
- attestationIndex += 8 ;
178
+ if (status == 1 ) { // status == TRADING
179
+ attestationIndex += 24 ;
180
+ } else {
181
+ // If status is not trading then the latest available price is
182
+ // the previous price info that are passed here.
163
183
164
- info.price.publishTime = encoded. toUint64 (index + attestationIndex);
165
- info.emaPrice .publishTime = info.price.publishTime ;
166
- attestationIndex += 8 ;
184
+ // Previous publish time
185
+ info.price .publishTime = encoded. toUint64 (index + attestationIndex) ;
186
+ attestationIndex += 8 ;
167
187
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.
188
+ // Previous price
189
+ info.price.price = int64 (encoded.toUint64 (index + attestationIndex));
190
+ attestationIndex += 8 ;
173
191
174
- // Previous publish time
175
- info.price.publishTime = encoded.toUint64 (index + attestationIndex);
176
- attestationIndex += 8 ;
192
+ // Previous confidence
193
+ info.price.conf = encoded.toUint64 (index + attestationIndex);
194
+ attestationIndex += 8 ;
177
195
178
- // Previous price
179
- info.price.price = int64 (encoded.toUint64 (index + attestationIndex));
180
- attestationIndex += 8 ;
196
+ // The EMA is last updated when the aggregate had trading status,
197
+ // so, we use previous publish time here too.
198
+ info.emaPrice.publishTime = info.price.publishTime;
199
+ }
200
+ }
181
201
182
- // Previous confidence
183
- info.price.conf = encoded.toUint64 (index + attestationIndex);
184
- attestationIndex += 8 ;
185
202
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
- }
203
+ require (attestationIndex <= attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes " );
191
204
192
- require (attestationIndex <= attestationSize, "INTERNAL: Consumed more than `attestationSize` bytes " );
205
+ // Respect specified attestation size for forward-compat
206
+ index += attestationSize;
193
207
194
- // Respect specified attestation size for forward-compat
195
- index += attestationSize ;
208
+ // Store the attestation
209
+ uint64 latestPublishTime = latestPriceInfoPublishTime (priceId) ;
196
210
197
- // Store the attestation
198
- uint64 latestPublishTime = latestPriceInfoPublishTime (priceId);
211
+ bool fresh = false ;
212
+ if (info.price.publishTime > latestPublishTime) {
213
+ freshPrices += 1 ;
214
+ fresh = true ;
215
+ setLatestPriceInfo (priceId, info);
216
+ }
199
217
200
- bool fresh = false ;
201
- if (info.price.publishTime > latestPublishTime) {
202
- freshPrices += 1 ;
203
- fresh = true ;
204
- setLatestPriceInfo (priceId, info);
218
+ emit PriceFeedUpdate (priceId, fresh, vm.emitterChainId, vm.sequence, latestPublishTime,
219
+ info.price.publishTime, info.price.price, info.price.conf);
205
220
}
206
221
207
- emit PriceFeedUpdate (priceId, fresh, vm.emitterChainId, vm.sequence, latestPublishTime,
208
- info.price.publishTime, info.price.price, info.price.conf);
209
- }
210
-
211
222
212
- emit BatchPriceFeedUpdate (vm.emitterChainId, vm.sequence, nAttestations, freshPrices);
223
+ emit BatchPriceFeedUpdate (vm.emitterChainId, vm.sequence, nAttestations, freshPrices);
224
+ }
213
225
}
214
226
215
227
function queryPriceFeed (bytes32 id ) public view override returns (PythStructs.PriceFeed memory priceFeed ){
0 commit comments