Skip to content

Commit 31e93f0

Browse files
authored
Merge pull request #2558 from pyth-network/ton-pyth-price-feed-ids
feat(target_chains/ton): add helper function to parse price IDs beyond a single cell and enhance update handling
2 parents 5ed1e16 + b48ce6b commit 31e93f0

File tree

3 files changed

+383
-14
lines changed

3 files changed

+383
-14
lines changed

target_chains/ton/contracts/contracts/Pyth.fc

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -369,17 +369,51 @@ cell create_price_feed_cell_chain(tuple price_feeds) {
369369
0);
370370
}
371371

372+
;; Helper function to parse price IDs from a slice, handling cell chain traversal
373+
;; Returns a tuple containing the parsed price IDs
374+
tuple parse_price_ids_from_slice(slice price_ids_slice) {
375+
int price_ids_len = price_ids_slice~load_uint(8);
376+
tuple price_ids = empty_tuple();
377+
378+
;; Process each price ID, handling potential cell boundaries
379+
int i = 0;
380+
while (i < price_ids_len) {
381+
builder price_id_builder = begin_cell();
382+
int bits_loaded = 0;
383+
384+
;; We need to load exactly 256 bits for each price ID
385+
while (bits_loaded < 256) {
386+
;; Calculate how many bits we can load from the current slice
387+
int bits_to_load = min(price_ids_slice.slice_bits(), 256 - bits_loaded);
388+
389+
;; Load and store those bits
390+
price_id_builder = price_id_builder.store_slice(price_ids_slice~load_bits(bits_to_load));
391+
bits_loaded += bits_to_load;
392+
393+
;; If we haven't loaded all 256 bits yet, we need to move to the next cell
394+
if (bits_loaded < 256) {
395+
;; Make sure we have a next cell to load from
396+
throw_unless(35, ~ price_ids_slice.slice_refs_empty?());
397+
price_ids_slice = price_ids_slice~load_ref().begin_parse();
398+
}
399+
}
400+
401+
;; Extract the complete price ID from the builder
402+
slice price_id_slice = price_id_builder.end_cell().begin_parse();
403+
int price_id = price_id_slice~load_uint(256);
404+
price_ids~tpush(price_id);
405+
i += 1;
406+
}
407+
408+
return price_ids;
409+
}
410+
372411
() 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, slice target_address, slice custom_payload) impure {
373412
try {
374413
load_data();
375414

376-
;; Load price_ids tuple
377-
int price_ids_len = price_ids_slice~load_uint(8);
378-
tuple price_ids = empty_tuple();
379-
repeat(price_ids_len) {
380-
int price_id = price_ids_slice~load_uint(256);
381-
price_ids~tpush(price_id);
382-
}
415+
;; Use the helper function to parse price IDs
416+
tuple price_ids = parse_price_ids_from_slice(price_ids_slice);
383417

384418
tuple price_feeds = parse_price_feeds_from_data(msg_value, update_data_slice, price_ids, min_publish_time, max_publish_time, false);
385419
send_price_feeds_response(price_feeds, msg_value, OP_PARSE_PRICE_FEED_UPDATES,
@@ -395,13 +429,8 @@ cell create_price_feed_cell_chain(tuple price_feeds) {
395429
try {
396430
load_data();
397431

398-
;; Load price_ids tuple
399-
int price_ids_len = price_ids_slice~load_uint(8);
400-
tuple price_ids = empty_tuple();
401-
repeat(price_ids_len) {
402-
int price_id = price_ids_slice~load_uint(256);
403-
price_ids~tpush(price_id);
404-
}
432+
;; Use the helper function to parse price IDs
433+
tuple price_ids = parse_price_ids_from_slice(price_ids_slice);
405434

406435
tuple price_feeds = parse_price_feeds_from_data(msg_value, update_data_slice, price_ids, publish_time, publish_time + max_staleness, true);
407436
send_price_feeds_response(price_feeds, msg_value, OP_PARSE_UNIQUE_PRICE_FEED_UPDATES, sender_address, target_address, custom_payload);

target_chains/ton/contracts/tests/PythTest.spec.ts

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ import {
4545
HERMES_ETH_UNIQUE_EXPO,
4646
HERMES_ETH_UNIQUE_PRICE,
4747
HERMES_ETH_UNIQUE_PUBLISH_TIME,
48+
HERMES_SOL_TON_PYTH_USDT_UPDATE,
49+
PYTH_PRICE_FEED_ID,
50+
SOL_PRICE_FEED_ID,
51+
TON_PRICE_FEED_ID,
52+
USDT_PRICE_FEED_ID,
53+
HERMES_SOL_UNIQUE_PUBLISH_TIME,
54+
HERMES_SOL_UNIQUE_PRICE,
55+
HERMES_SOL_UNIQUE_CONF,
56+
HERMES_SOL_UNIQUE_EXPO,
57+
HERMES_USDT_UNIQUE_PRICE,
58+
HERMES_USDT_UNIQUE_EXPO,
59+
HERMES_USDT_UNIQUE_CONF,
60+
HERMES_USDT_UNIQUE_PUBLISH_TIME,
4861
} from "./utils/pyth";
4962
import { GUARDIAN_SET_0, MAINNET_UPGRADE_VAAS } from "./utils/wormhole";
5063
import { DataSource } from "@pythnetwork/xc-admin-common";
@@ -1110,6 +1123,122 @@ describe("PythTest", () => {
11101123
);
11111124
});
11121125

1126+
it("should successfully parse price feed updates with more than 3 price feed ids", async () => {
1127+
await deployContract();
1128+
await updateGuardianSets(pythTest, deployer);
1129+
1130+
const sentValue = toNano("1");
1131+
const result = await pythTest.sendParsePriceFeedUpdates(
1132+
deployer.getSender(),
1133+
Buffer.from(HERMES_SOL_TON_PYTH_USDT_UPDATE, "hex"),
1134+
sentValue,
1135+
[
1136+
SOL_PRICE_FEED_ID,
1137+
TON_PRICE_FEED_ID,
1138+
PYTH_PRICE_FEED_ID,
1139+
USDT_PRICE_FEED_ID,
1140+
],
1141+
HERMES_SOL_UNIQUE_PUBLISH_TIME,
1142+
HERMES_SOL_UNIQUE_PUBLISH_TIME,
1143+
deployer.address,
1144+
CUSTOM_PAYLOAD,
1145+
);
1146+
1147+
// Verify transaction success and message count
1148+
expect(result.transactions).toHaveTransaction({
1149+
from: deployer.address,
1150+
to: pythTest.address,
1151+
success: true,
1152+
outMessagesCount: 1,
1153+
});
1154+
1155+
// Get the output message
1156+
const outMessage = result.transactions[1].outMessages.values()[0];
1157+
1158+
// Verify excess value is returned
1159+
expect(
1160+
(outMessage.info as CommonMessageInfoInternal).value.coins,
1161+
).toBeGreaterThan(0);
1162+
1163+
const cs = outMessage.body.beginParse();
1164+
1165+
// Verify message header
1166+
const op = cs.loadUint(32);
1167+
expect(op).toBe(5); // OP_PARSE_PRICE_FEED_UPDATES
1168+
1169+
// Verify number of price feeds
1170+
const numPriceFeeds = cs.loadUint(8);
1171+
expect(numPriceFeeds).toBe(4); // We expect SOL, TON, PYTH and USDT price feeds
1172+
1173+
// Load and verify price feeds
1174+
const priceFeedsCell = cs.loadRef();
1175+
let currentCell = priceFeedsCell;
1176+
1177+
// First price feed (SOL)
1178+
const solCs = currentCell.beginParse();
1179+
const solPriceId =
1180+
"0x" + solCs.loadUintBig(256).toString(16).padStart(64, "0");
1181+
expect(solPriceId).toBe(SOL_PRICE_FEED_ID);
1182+
1183+
const solPriceFeedCell = solCs.loadRef();
1184+
const solPriceFeedSlice = solPriceFeedCell.beginParse();
1185+
1186+
// Verify SOL current price
1187+
const solCurrentPriceCell = solPriceFeedSlice.loadRef();
1188+
const solCurrentPrice = solCurrentPriceCell.beginParse();
1189+
expect(solCurrentPrice.loadInt(64)).toBe(HERMES_SOL_UNIQUE_PRICE);
1190+
expect(solCurrentPrice.loadUint(64)).toBe(HERMES_SOL_UNIQUE_CONF);
1191+
expect(solCurrentPrice.loadInt(32)).toBe(HERMES_SOL_UNIQUE_EXPO);
1192+
expect(solCurrentPrice.loadUint(64)).toBe(HERMES_SOL_UNIQUE_PUBLISH_TIME);
1193+
1194+
// Move through TON and PYTH price feeds to reach USDT
1195+
currentCell = solCs.loadRef(); // Move to TON
1196+
const tonCs = currentCell.beginParse();
1197+
tonCs.loadUintBig(256); // Skip TON price ID
1198+
tonCs.loadRef(); // Skip TON price data
1199+
1200+
currentCell = tonCs.loadRef(); // Move to PYTH
1201+
const pythCs = currentCell.beginParse();
1202+
pythCs.loadUintBig(256); // Skip PYTH price ID
1203+
pythCs.loadRef(); // Skip PYTH price data
1204+
1205+
currentCell = pythCs.loadRef(); // Move to USDT
1206+
const usdtCs = currentCell.beginParse();
1207+
const usdtPriceId =
1208+
"0x" + usdtCs.loadUintBig(256).toString(16).padStart(64, "0");
1209+
expect(usdtPriceId).toBe(USDT_PRICE_FEED_ID);
1210+
1211+
const usdtPriceFeedCell = usdtCs.loadRef();
1212+
const usdtPriceFeedSlice = usdtPriceFeedCell.beginParse();
1213+
1214+
// Verify USDT current price
1215+
const usdtCurrentPriceCell = usdtPriceFeedSlice.loadRef();
1216+
const usdtCurrentPrice = usdtCurrentPriceCell.beginParse();
1217+
expect(usdtCurrentPrice.loadInt(64)).toBe(HERMES_USDT_UNIQUE_PRICE);
1218+
expect(usdtCurrentPrice.loadUint(64)).toBe(HERMES_USDT_UNIQUE_CONF);
1219+
expect(usdtCurrentPrice.loadInt(32)).toBe(HERMES_USDT_UNIQUE_EXPO);
1220+
expect(usdtCurrentPrice.loadUint(64)).toBe(HERMES_USDT_UNIQUE_PUBLISH_TIME);
1221+
1222+
// Verify this is the end of the chain
1223+
expect(usdtCs.remainingRefs).toBe(0);
1224+
1225+
// Verify sender address
1226+
const senderAddress = cs.loadAddress();
1227+
expect(senderAddress?.toString()).toBe(
1228+
deployer.getSender().address.toString(),
1229+
);
1230+
1231+
// Verify custom payload
1232+
const customPayloadCell = cs.loadRef();
1233+
const customPayloadSlice = customPayloadCell.beginParse();
1234+
const receivedPayload = Buffer.from(
1235+
customPayloadSlice.loadBuffer(CUSTOM_PAYLOAD.length),
1236+
);
1237+
expect(receivedPayload.toString("hex")).toBe(
1238+
CUSTOM_PAYLOAD.toString("hex"),
1239+
);
1240+
});
1241+
11131242
it("should successfully parse unique price feed updates", async () => {
11141243
await deployContract();
11151244
await updateGuardianSets(pythTest, deployer);
@@ -1229,6 +1358,122 @@ describe("PythTest", () => {
12291358
);
12301359
});
12311360

1361+
it("should successfully parse unique price feed updates with more than 3 price feed ids", async () => {
1362+
await deployContract();
1363+
await updateGuardianSets(pythTest, deployer);
1364+
1365+
const sentValue = toNano("1");
1366+
const result = await pythTest.sendParseUniquePriceFeedUpdates(
1367+
deployer.getSender(),
1368+
Buffer.from(HERMES_SOL_TON_PYTH_USDT_UPDATE, "hex"),
1369+
sentValue,
1370+
[
1371+
SOL_PRICE_FEED_ID,
1372+
TON_PRICE_FEED_ID,
1373+
PYTH_PRICE_FEED_ID,
1374+
USDT_PRICE_FEED_ID,
1375+
],
1376+
HERMES_SOL_UNIQUE_PUBLISH_TIME,
1377+
60,
1378+
deployer.address,
1379+
CUSTOM_PAYLOAD,
1380+
);
1381+
1382+
// Verify transaction success and message count
1383+
expect(result.transactions).toHaveTransaction({
1384+
from: deployer.address,
1385+
to: pythTest.address,
1386+
success: true,
1387+
outMessagesCount: 1,
1388+
});
1389+
1390+
// Get the output message
1391+
const outMessage = result.transactions[1].outMessages.values()[0];
1392+
1393+
// Verify excess value is returned
1394+
expect(
1395+
(outMessage.info as CommonMessageInfoInternal).value.coins,
1396+
).toBeGreaterThan(0);
1397+
1398+
const cs = outMessage.body.beginParse();
1399+
1400+
// Verify message header
1401+
const op = cs.loadUint(32);
1402+
expect(op).toBe(6); // OP_PARSE_UNIQUE_PRICE_FEED_UPDATES
1403+
1404+
// Verify number of price feeds
1405+
const numPriceFeeds = cs.loadUint(8);
1406+
expect(numPriceFeeds).toBe(4); // We expect SOL, TON, PYTH and USDT price feeds
1407+
1408+
// Load and verify price feeds
1409+
const priceFeedsCell = cs.loadRef();
1410+
let currentCell = priceFeedsCell;
1411+
1412+
// First price feed (SOL)
1413+
const solCs = currentCell.beginParse();
1414+
const solPriceId =
1415+
"0x" + solCs.loadUintBig(256).toString(16).padStart(64, "0");
1416+
expect(solPriceId).toBe(SOL_PRICE_FEED_ID);
1417+
1418+
const solPriceFeedCell = solCs.loadRef();
1419+
const solPriceFeedSlice = solPriceFeedCell.beginParse();
1420+
1421+
// Verify SOL current price
1422+
const solCurrentPriceCell = solPriceFeedSlice.loadRef();
1423+
const solCurrentPrice = solCurrentPriceCell.beginParse();
1424+
expect(solCurrentPrice.loadInt(64)).toBe(HERMES_SOL_UNIQUE_PRICE);
1425+
expect(solCurrentPrice.loadUint(64)).toBe(HERMES_SOL_UNIQUE_CONF);
1426+
expect(solCurrentPrice.loadInt(32)).toBe(HERMES_SOL_UNIQUE_EXPO);
1427+
expect(solCurrentPrice.loadUint(64)).toBe(HERMES_SOL_UNIQUE_PUBLISH_TIME);
1428+
1429+
// Move through TON and PYTH price feeds to reach USDT
1430+
currentCell = solCs.loadRef(); // Move to TON
1431+
const tonCs = currentCell.beginParse();
1432+
tonCs.loadUintBig(256); // Skip TON price ID
1433+
tonCs.loadRef(); // Skip TON price data
1434+
1435+
currentCell = tonCs.loadRef(); // Move to PYTH
1436+
const pythCs = currentCell.beginParse();
1437+
pythCs.loadUintBig(256); // Skip PYTH price ID
1438+
pythCs.loadRef(); // Skip PYTH price data
1439+
1440+
currentCell = pythCs.loadRef(); // Move to USDT
1441+
const usdtCs = currentCell.beginParse();
1442+
const usdtPriceId =
1443+
"0x" + usdtCs.loadUintBig(256).toString(16).padStart(64, "0");
1444+
expect(usdtPriceId).toBe(USDT_PRICE_FEED_ID);
1445+
1446+
const usdtPriceFeedCell = usdtCs.loadRef();
1447+
const usdtPriceFeedSlice = usdtPriceFeedCell.beginParse();
1448+
1449+
// Verify USDT current price
1450+
const usdtCurrentPriceCell = usdtPriceFeedSlice.loadRef();
1451+
const usdtCurrentPrice = usdtCurrentPriceCell.beginParse();
1452+
expect(usdtCurrentPrice.loadInt(64)).toBe(HERMES_USDT_UNIQUE_PRICE);
1453+
expect(usdtCurrentPrice.loadUint(64)).toBe(HERMES_USDT_UNIQUE_CONF);
1454+
expect(usdtCurrentPrice.loadInt(32)).toBe(HERMES_USDT_UNIQUE_EXPO);
1455+
expect(usdtCurrentPrice.loadUint(64)).toBe(HERMES_USDT_UNIQUE_PUBLISH_TIME);
1456+
1457+
// Verify this is the end of the chain
1458+
expect(usdtCs.remainingRefs).toBe(0);
1459+
1460+
// Verify sender address
1461+
const senderAddress = cs.loadAddress();
1462+
expect(senderAddress?.toString()).toBe(
1463+
deployer.getSender().address.toString(),
1464+
);
1465+
1466+
// Verify custom payload
1467+
const customPayloadCell = cs.loadRef();
1468+
const customPayloadSlice = customPayloadCell.beginParse();
1469+
const receivedPayload = Buffer.from(
1470+
customPayloadSlice.loadBuffer(CUSTOM_PAYLOAD.length),
1471+
);
1472+
expect(receivedPayload.toString("hex")).toBe(
1473+
CUSTOM_PAYLOAD.toString("hex"),
1474+
);
1475+
});
1476+
12321477
it("should fail to parse invalid price feed updates", async () => {
12331478
await deployContract();
12341479
await updateGuardianSets(pythTest, deployer);

0 commit comments

Comments
 (0)