6
6
#include "common/merkle_tree.fc";
7
7
#include "common/governance_actions.fc";
8
8
#include "common/gas.fc";
9
+ #include "common/op.fc";
9
10
#include "./Wormhole.fc";
10
11
11
12
cell store_price(int price, int conf, int expo, int publish_time) {
@@ -156,16 +157,7 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
156
157
return payload~load_uint(160); ;; Return root_digest
157
158
}
158
159
159
-
160
- () update_price_feeds(int msg_value, slice data) impure {
161
- load_data();
162
- slice cs = read_and_verify_header(data);
163
-
164
- int wormhole_proof_size_bytes = cs~load_uint(16);
165
- (cell wormhole_proof, slice new_cs) = read_and_store_large_data(cs, wormhole_proof_size_bytes * 8);
166
- cs = new_cs;
167
-
168
- int num_updates = cs~load_uint(8);
160
+ () calculate_and_validate_fees(int msg_value, int num_updates) impure {
169
161
int update_fee = single_update_fee * num_updates;
170
162
int compute_fee = get_compute_fee(
171
163
WORKCHAIN,
@@ -176,30 +168,264 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
176
168
177
169
;; Check if the sender has sent enough TON to cover the update_fee
178
170
throw_unless(ERROR_INSUFFICIENT_FEE, remaining_msg_value >= update_fee);
171
+ }
172
+
173
+ (int) find_price_id_index(tuple price_ids, int price_id) {
174
+ int len = price_ids.tlen();
175
+ int i = 0;
176
+ while (i < len) {
177
+ if (price_ids.at(i) == price_id) {
178
+ return i;
179
+ }
180
+ i += 1;
181
+ }
182
+ return -1; ;; Not found
183
+ }
184
+
185
+
186
+ tuple parse_price_feeds_from_data(int msg_value, slice data, tuple price_ids, int min_publish_time, int max_publish_time, int unique) {
187
+ slice cs = read_and_verify_header(data);
188
+
189
+ int wormhole_proof_size_bytes = cs~load_uint(16);
190
+ (cell wormhole_proof, slice new_cs) = read_and_store_large_data(cs, wormhole_proof_size_bytes * 8);
191
+ cs = new_cs;
192
+
193
+ int num_updates = cs~load_uint(8);
194
+
195
+ calculate_and_validate_fees(msg_value, num_updates);
179
196
180
197
(_, _, _, _, int emitter_chain_id, int emitter_address, _, _, slice payload, _) = parse_and_verify_wormhole_vm(wormhole_proof.begin_parse());
181
198
182
199
;; Check if the data source is valid
183
200
cell data_source = begin_cell()
184
201
.store_uint(emitter_chain_id, 16)
185
202
.store_uint(emitter_address, 256)
186
- .end_cell();
203
+ .end_cell();
187
204
188
205
;; Dictionary doesn't support cell as key, so we use cell_hash to create a 256-bit integer key
189
206
int data_source_key = cell_hash(data_source);
190
-
191
207
(slice value, int found?) = is_valid_data_source.udict_get?(256, data_source_key);
192
208
throw_unless(ERROR_UPDATE_DATA_SOURCE_NOT_FOUND, found?);
193
209
int valid = value~load_int(1);
194
210
throw_unless(ERROR_INVALID_UPDATE_DATA_SOURCE, valid);
195
211
196
-
197
212
int root_digest = parse_pyth_payload_in_wormhole_vm(payload);
198
213
214
+ ;; Create dictionary to store price feeds in order (dict has a udict_get_next? method which returns the next key in order)
215
+ cell ordered_feeds = new_dict();
216
+ ;; Track which price IDs we've found
217
+ cell found_price_ids = new_dict();
218
+
219
+ int index = 0;
220
+
199
221
repeat(num_updates) {
200
- (int price_id, int price, int conf, int expo, int publish_time, _ , int ema_price, int ema_conf, slice new_cs) = read_and_verify_message(cs, root_digest);
222
+ (int price_id, int price, int conf, int expo, int publish_time, int prev_publish_time , int ema_price, int ema_conf, slice new_cs) = read_and_verify_message(cs, root_digest);
201
223
cs = new_cs;
202
224
225
+ int price_ids_len = price_ids.tlen();
226
+
227
+ ;; Check if we've already processed this price_id to avoid duplicates
228
+ (_, int already_processed?) = found_price_ids.udict_get?(256, price_id);
229
+ if (~ already_processed?) { ;; Only process if we haven't seen this price_id yet
230
+ int should_include = (price_ids_len == 0)
231
+ | ((price_ids_len > 0)
232
+ & (publish_time >= min_publish_time)
233
+ & (publish_time <= max_publish_time)
234
+ & ((unique == 0) | (min_publish_time > prev_publish_time)));
235
+
236
+ if (should_include) {
237
+ ;; Create price feed cell containing both current and EMA prices
238
+ cell price_feed_cell = begin_cell()
239
+ .store_ref(store_price(price, conf, expo, publish_time))
240
+ .store_ref(store_price(ema_price, ema_conf, expo, publish_time))
241
+ .end_cell();
242
+
243
+ if (price_ids_len == 0) {
244
+ ordered_feeds~udict_set(8, index, begin_cell()
245
+ .store_uint(price_id, 256)
246
+ .store_ref(price_feed_cell)
247
+ .end_cell().begin_parse());
248
+ index += 1;
249
+ } else {
250
+ index = find_price_id_index(price_ids, price_id);
251
+ if (index >= 0) {
252
+ ordered_feeds~udict_set(8, index, begin_cell()
253
+ .store_uint(price_id, 256)
254
+ .store_ref(price_feed_cell)
255
+ .end_cell().begin_parse());
256
+ }
257
+ }
258
+
259
+ ;; Mark this price ID as found
260
+ found_price_ids~udict_set(256, price_id, begin_cell().store_int(true, 1).end_cell().begin_parse());
261
+ }
262
+ }
263
+ }
264
+
265
+ throw_if(ERROR_INVALID_UPDATE_DATA_LENGTH, ~ cs.slice_empty?());
266
+
267
+ ;; Verify all requested price IDs were found
268
+ if (price_ids.tlen() > 0) {
269
+ int i = 0;
270
+ repeat(price_ids.tlen()) {
271
+ int requested_id = price_ids.at(i);
272
+ (_, int found?) = found_price_ids.udict_get?(256, requested_id);
273
+ throw_unless(ERROR_PRICE_FEED_NOT_FOUND_WITHIN_RANGE, found?);
274
+ i += 1;
275
+ }
276
+ }
277
+
278
+ ;; Create final ordered tuple from dictionary
279
+ tuple price_feeds = empty_tuple();
280
+ int index = -1;
281
+ do {
282
+ (index, slice value, int success) = ordered_feeds.udict_get_next?(8, index);
283
+ if (success) {
284
+ tuple price_feed = empty_tuple();
285
+ price_feed~tpush(value~load_uint(256)); ;; price_id
286
+ price_feed~tpush(value~load_ref()); ;; price_feed_cell
287
+ price_feeds~tpush(price_feed);
288
+ }
289
+ } until (~ success);
290
+
291
+ return price_feeds;
292
+ }
293
+
294
+ ;; Creates a chain of cells from price feeds, with each cell containing exactly one price_id (256 bits)
295
+ ;; and one ref to the price feed cell. Returns the head of the chain.
296
+ ;; Each cell now contains exactly:
297
+ ;; - One price_id (256 bits)
298
+ ;; - One ref to price_feed_cell
299
+ ;; - One optional ref to next cell in chain
300
+ ;; This approach is:
301
+ ;; - More consistent with TON's cell model
302
+ ;; - Easier to traverse and read individual price feeds
303
+ ;; - Cleaner separation of data
304
+ ;; - More predictable in terms of cell structure
305
+ cell create_price_feed_cell_chain(tuple price_feeds) {
306
+ cell result = null();
307
+
308
+ int i = price_feeds.tlen() - 1;
309
+ while (i >= 0) {
310
+ tuple price_feed = price_feeds.at(i);
311
+ int price_id = price_feed.at(0);
312
+ cell price_feed_cell = price_feed.at(1);
313
+
314
+ ;; Create new cell with single price feed and chain to previous result
315
+ builder current_builder = begin_cell()
316
+ .store_uint(price_id, 256) ;; Store price_id
317
+ .store_ref(price_feed_cell); ;; Store price data ref
318
+
319
+ ;; Chain to previous cells if they exist
320
+ if (~ cell_null?(result)) {
321
+ current_builder = current_builder.store_ref(result);
322
+ }
323
+
324
+ result = current_builder.end_cell();
325
+ i -= 1;
326
+ }
327
+
328
+ return result;
329
+ }
330
+
331
+ () send_price_feeds_response(tuple price_feeds, int msg_value, int op, slice sender_address) impure {
332
+ ;; Build response cell with price feeds
333
+ builder response = begin_cell()
334
+ .store_uint(op, 32) ;; Response op
335
+ .store_uint(price_feeds.tlen(), 8); ;; Number of price feeds
336
+
337
+ ;; Create and store price feed cell chain
338
+ cell price_feeds_cell = create_price_feed_cell_chain(price_feeds);
339
+ response = response.store_ref(price_feeds_cell);
340
+
341
+ ;; Build the complete message cell (https://docs.ton.org/v3/documentation/smart-contracts/message-management/sending-messages#message-layout)
342
+ cell msg = begin_cell()
343
+ .store_uint(0x18, 6)
344
+ .store_slice(sender_address)
345
+ .store_coins(0) ;; Will fill in actual amount after fee calculations
346
+ .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
347
+ .store_ref(response.end_cell())
348
+ .end_cell();
349
+
350
+ int num_price_feeds = price_feeds.tlen();
351
+
352
+ ;; Number of cells in the message
353
+ ;; - 2 cells: msg + response
354
+ int cells = 2 + num_price_feeds;
355
+
356
+ ;; Bit layout per TL-B spec (https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb):
357
+ ;; - 6 bits: optimized way of serializing the tag and the first 4 fields
358
+ ;; - 256 bits: owner address
359
+ ;; - 128 bits: coins (VarUInteger 16) from grams$_ amount:(VarUInteger 16) = Grams
360
+ ;; - 107 bits: other data (extra_currencies + ihr_fee + fwd_fee + lt of transaction + unixtime of transaction + no init-field flag + inplace message body flag)
361
+ ;; - PRICE_FEED_BITS * num_price_feeds: space for each price feed
362
+ int bits = 6 + 256 + 128 + 107 + (PRICE_FEED_BITS * num_price_feeds);
363
+ int fwd_fee = get_forward_fee(cells, bits, WORKCHAIN);
364
+
365
+ ;; Calculate all fees
366
+ int compute_fee = get_compute_fee(WORKCHAIN, get_gas_consumed());
367
+ int update_fee = single_update_fee * price_feeds.tlen();
368
+
369
+ ;; Calculate total fees and remaining excess
370
+ int total_fees = compute_fee + update_fee + fwd_fee;
371
+ int excess = msg_value - total_fees;
372
+
373
+ ;; Send response message back to sender with exact excess amount
374
+ send_raw_message(begin_cell()
375
+ .store_uint(0x18, 6)
376
+ .store_slice(sender_address)
377
+ .store_coins(excess)
378
+ .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
379
+ .store_ref(response.end_cell())
380
+ .end_cell(),
381
+ 0);
382
+ }
383
+
384
+ () parse_price_feed_updates(int msg_value, slice update_data_slice, slice price_ids_slice, int min_publish_time, int max_publish_time, slice sender_address) impure {
385
+ load_data();
386
+
387
+ ;; Load price_ids tuple
388
+ int price_ids_len = price_ids_slice~load_uint(8);
389
+ tuple price_ids = empty_tuple();
390
+ repeat(price_ids_len) {
391
+ int price_id = price_ids_slice~load_uint(256);
392
+ price_ids~tpush(price_id);
393
+ }
394
+
395
+ tuple price_feeds = parse_price_feeds_from_data(msg_value, update_data_slice, price_ids, min_publish_time, max_publish_time, false);
396
+ send_price_feeds_response(price_feeds, msg_value, OP_PARSE_PRICE_FEED_UPDATES, sender_address);
397
+ }
398
+
399
+ () parse_unique_price_feed_updates(int msg_value, slice update_data_slice, slice price_ids_slice, int publish_time, int max_staleness, slice sender_address) impure {
400
+ load_data();
401
+
402
+ ;; Load price_ids tuple
403
+ int price_ids_len = price_ids_slice~load_uint(8);
404
+ tuple price_ids = empty_tuple();
405
+ repeat(price_ids_len) {
406
+ int price_id = price_ids_slice~load_uint(256);
407
+ price_ids~tpush(price_id);
408
+ }
409
+
410
+ tuple price_feeds = parse_price_feeds_from_data(msg_value, update_data_slice, price_ids, publish_time, publish_time + max_staleness, true);
411
+ send_price_feeds_response(price_feeds, msg_value, OP_PARSE_UNIQUE_PRICE_FEED_UPDATES, sender_address);
412
+ }
413
+
414
+ () update_price_feeds(int msg_value, slice data) impure {
415
+ load_data();
416
+ tuple price_feeds = parse_price_feeds_from_data(msg_value, data, empty_tuple(), 0, 0, false);
417
+ int num_updates = price_feeds.tlen();
418
+
419
+ int i = 0;
420
+ while(i < num_updates) {
421
+ tuple price_feed = price_feeds.at(i);
422
+ int price_id = price_feed.at(0);
423
+ cell price_feed_cell = price_feed.at(1);
424
+ slice price_feed = price_feed_cell.begin_parse();
425
+ slice price = price_feed~load_ref().begin_parse();
426
+ slice ema_price = price_feed~load_ref().begin_parse();
427
+ (int price_, int conf, int expo, int publish_time) = parse_price(price);
428
+
203
429
(slice latest_price_info, int found?) = latest_price_feeds.udict_get?(256, price_id);
204
430
int latest_publish_time = 0;
205
431
if (found?) {
@@ -213,17 +439,11 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
213
439
}
214
440
215
441
if (publish_time > latest_publish_time) {
216
- cell price_feed = begin_cell()
217
- .store_ref(store_price(price, conf, expo, publish_time))
218
- .store_ref(store_price(ema_price, ema_conf, expo, publish_time))
219
- .end_cell();
220
-
221
- latest_price_feeds~udict_set(256, price_id, begin_cell().store_ref(price_feed).end_cell().begin_parse());
442
+ latest_price_feeds~udict_set(256, price_id, begin_cell().store_ref(price_feed_cell).end_cell().begin_parse());
222
443
}
444
+ i += 1;
223
445
}
224
446
225
- throw_if(ERROR_INVALID_UPDATE_DATA_LENGTH, ~ cs.slice_empty?());
226
-
227
447
store_data();
228
448
}
229
449
0 commit comments