ソースを参照

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
Daniel Chew 7 ヶ月 前
コミット
31e93f0af7

+ 43 - 14
target_chains/ton/contracts/contracts/Pyth.fc

@@ -369,17 +369,51 @@ cell create_price_feed_cell_chain(tuple price_feeds) {
         0);
 }
 
+;; Helper function to parse price IDs from a slice, handling cell chain traversal
+;; Returns a tuple containing the parsed price IDs
+tuple parse_price_ids_from_slice(slice price_ids_slice) {
+    int price_ids_len = price_ids_slice~load_uint(8);
+    tuple price_ids = empty_tuple();
+
+    ;; Process each price ID, handling potential cell boundaries
+    int i = 0;
+    while (i < price_ids_len) {
+        builder price_id_builder = begin_cell();
+        int bits_loaded = 0;
+
+        ;; We need to load exactly 256 bits for each price ID
+        while (bits_loaded < 256) {
+            ;; Calculate how many bits we can load from the current slice
+            int bits_to_load = min(price_ids_slice.slice_bits(), 256 - bits_loaded);
+
+            ;; Load and store those bits
+            price_id_builder = price_id_builder.store_slice(price_ids_slice~load_bits(bits_to_load));
+            bits_loaded += bits_to_load;
+
+            ;; If we haven't loaded all 256 bits yet, we need to move to the next cell
+            if (bits_loaded < 256) {
+                ;; Make sure we have a next cell to load from
+                throw_unless(35, ~ price_ids_slice.slice_refs_empty?());
+                price_ids_slice = price_ids_slice~load_ref().begin_parse();
+            }
+        }
+
+        ;; Extract the complete price ID from the builder
+        slice price_id_slice = price_id_builder.end_cell().begin_parse();
+        int price_id = price_id_slice~load_uint(256);
+        price_ids~tpush(price_id);
+        i += 1;
+    }
+
+    return price_ids;
+}
+
 () 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 {
     try {
         load_data();
 
-        ;; Load price_ids tuple
-        int price_ids_len = price_ids_slice~load_uint(8);
-        tuple price_ids = empty_tuple();
-        repeat(price_ids_len) {
-            int price_id = price_ids_slice~load_uint(256);
-            price_ids~tpush(price_id);
-        }
+        ;; Use the helper function to parse price IDs
+        tuple price_ids = parse_price_ids_from_slice(price_ids_slice);
 
         tuple price_feeds = parse_price_feeds_from_data(msg_value, update_data_slice, price_ids, min_publish_time, max_publish_time, false);
         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) {
     try {
         load_data();
 
-        ;; Load price_ids tuple
-        int price_ids_len = price_ids_slice~load_uint(8);
-        tuple price_ids = empty_tuple();
-        repeat(price_ids_len) {
-            int price_id = price_ids_slice~load_uint(256);
-            price_ids~tpush(price_id);
-        }
+        ;; Use the helper function to parse price IDs
+        tuple price_ids = parse_price_ids_from_slice(price_ids_slice);
 
         tuple price_feeds = parse_price_feeds_from_data(msg_value, update_data_slice, price_ids, publish_time, publish_time + max_staleness, true);
         send_price_feeds_response(price_feeds, msg_value, OP_PARSE_UNIQUE_PRICE_FEED_UPDATES, sender_address, target_address, custom_payload);

+ 245 - 0
target_chains/ton/contracts/tests/PythTest.spec.ts

@@ -45,6 +45,19 @@ import {
   HERMES_ETH_UNIQUE_EXPO,
   HERMES_ETH_UNIQUE_PRICE,
   HERMES_ETH_UNIQUE_PUBLISH_TIME,
+  HERMES_SOL_TON_PYTH_USDT_UPDATE,
+  PYTH_PRICE_FEED_ID,
+  SOL_PRICE_FEED_ID,
+  TON_PRICE_FEED_ID,
+  USDT_PRICE_FEED_ID,
+  HERMES_SOL_UNIQUE_PUBLISH_TIME,
+  HERMES_SOL_UNIQUE_PRICE,
+  HERMES_SOL_UNIQUE_CONF,
+  HERMES_SOL_UNIQUE_EXPO,
+  HERMES_USDT_UNIQUE_PRICE,
+  HERMES_USDT_UNIQUE_EXPO,
+  HERMES_USDT_UNIQUE_CONF,
+  HERMES_USDT_UNIQUE_PUBLISH_TIME,
 } from "./utils/pyth";
 import { GUARDIAN_SET_0, MAINNET_UPGRADE_VAAS } from "./utils/wormhole";
 import { DataSource } from "@pythnetwork/xc-admin-common";
@@ -1110,6 +1123,122 @@ describe("PythTest", () => {
     );
   });
 
+  it("should successfully parse price feed updates with more than 3 price feed ids", async () => {
+    await deployContract();
+    await updateGuardianSets(pythTest, deployer);
+
+    const sentValue = toNano("1");
+    const result = await pythTest.sendParsePriceFeedUpdates(
+      deployer.getSender(),
+      Buffer.from(HERMES_SOL_TON_PYTH_USDT_UPDATE, "hex"),
+      sentValue,
+      [
+        SOL_PRICE_FEED_ID,
+        TON_PRICE_FEED_ID,
+        PYTH_PRICE_FEED_ID,
+        USDT_PRICE_FEED_ID,
+      ],
+      HERMES_SOL_UNIQUE_PUBLISH_TIME,
+      HERMES_SOL_UNIQUE_PUBLISH_TIME,
+      deployer.address,
+      CUSTOM_PAYLOAD,
+    );
+
+    // Verify transaction success and message count
+    expect(result.transactions).toHaveTransaction({
+      from: deployer.address,
+      to: pythTest.address,
+      success: true,
+      outMessagesCount: 1,
+    });
+
+    // Get the output message
+    const outMessage = result.transactions[1].outMessages.values()[0];
+
+    // Verify excess value is returned
+    expect(
+      (outMessage.info as CommonMessageInfoInternal).value.coins,
+    ).toBeGreaterThan(0);
+
+    const cs = outMessage.body.beginParse();
+
+    // Verify message header
+    const op = cs.loadUint(32);
+    expect(op).toBe(5); // OP_PARSE_PRICE_FEED_UPDATES
+
+    // Verify number of price feeds
+    const numPriceFeeds = cs.loadUint(8);
+    expect(numPriceFeeds).toBe(4); // We expect SOL, TON, PYTH and USDT price feeds
+
+    // Load and verify price feeds
+    const priceFeedsCell = cs.loadRef();
+    let currentCell = priceFeedsCell;
+
+    // First price feed (SOL)
+    const solCs = currentCell.beginParse();
+    const solPriceId =
+      "0x" + solCs.loadUintBig(256).toString(16).padStart(64, "0");
+    expect(solPriceId).toBe(SOL_PRICE_FEED_ID);
+
+    const solPriceFeedCell = solCs.loadRef();
+    const solPriceFeedSlice = solPriceFeedCell.beginParse();
+
+    // Verify SOL current price
+    const solCurrentPriceCell = solPriceFeedSlice.loadRef();
+    const solCurrentPrice = solCurrentPriceCell.beginParse();
+    expect(solCurrentPrice.loadInt(64)).toBe(HERMES_SOL_UNIQUE_PRICE);
+    expect(solCurrentPrice.loadUint(64)).toBe(HERMES_SOL_UNIQUE_CONF);
+    expect(solCurrentPrice.loadInt(32)).toBe(HERMES_SOL_UNIQUE_EXPO);
+    expect(solCurrentPrice.loadUint(64)).toBe(HERMES_SOL_UNIQUE_PUBLISH_TIME);
+
+    // Move through TON and PYTH price feeds to reach USDT
+    currentCell = solCs.loadRef(); // Move to TON
+    const tonCs = currentCell.beginParse();
+    tonCs.loadUintBig(256); // Skip TON price ID
+    tonCs.loadRef(); // Skip TON price data
+
+    currentCell = tonCs.loadRef(); // Move to PYTH
+    const pythCs = currentCell.beginParse();
+    pythCs.loadUintBig(256); // Skip PYTH price ID
+    pythCs.loadRef(); // Skip PYTH price data
+
+    currentCell = pythCs.loadRef(); // Move to USDT
+    const usdtCs = currentCell.beginParse();
+    const usdtPriceId =
+      "0x" + usdtCs.loadUintBig(256).toString(16).padStart(64, "0");
+    expect(usdtPriceId).toBe(USDT_PRICE_FEED_ID);
+
+    const usdtPriceFeedCell = usdtCs.loadRef();
+    const usdtPriceFeedSlice = usdtPriceFeedCell.beginParse();
+
+    // Verify USDT current price
+    const usdtCurrentPriceCell = usdtPriceFeedSlice.loadRef();
+    const usdtCurrentPrice = usdtCurrentPriceCell.beginParse();
+    expect(usdtCurrentPrice.loadInt(64)).toBe(HERMES_USDT_UNIQUE_PRICE);
+    expect(usdtCurrentPrice.loadUint(64)).toBe(HERMES_USDT_UNIQUE_CONF);
+    expect(usdtCurrentPrice.loadInt(32)).toBe(HERMES_USDT_UNIQUE_EXPO);
+    expect(usdtCurrentPrice.loadUint(64)).toBe(HERMES_USDT_UNIQUE_PUBLISH_TIME);
+
+    // Verify this is the end of the chain
+    expect(usdtCs.remainingRefs).toBe(0);
+
+    // Verify sender address
+    const senderAddress = cs.loadAddress();
+    expect(senderAddress?.toString()).toBe(
+      deployer.getSender().address.toString(),
+    );
+
+    // Verify custom payload
+    const customPayloadCell = cs.loadRef();
+    const customPayloadSlice = customPayloadCell.beginParse();
+    const receivedPayload = Buffer.from(
+      customPayloadSlice.loadBuffer(CUSTOM_PAYLOAD.length),
+    );
+    expect(receivedPayload.toString("hex")).toBe(
+      CUSTOM_PAYLOAD.toString("hex"),
+    );
+  });
+
   it("should successfully parse unique price feed updates", async () => {
     await deployContract();
     await updateGuardianSets(pythTest, deployer);
@@ -1229,6 +1358,122 @@ describe("PythTest", () => {
     );
   });
 
+  it("should successfully parse unique price feed updates with more than 3 price feed ids", async () => {
+    await deployContract();
+    await updateGuardianSets(pythTest, deployer);
+
+    const sentValue = toNano("1");
+    const result = await pythTest.sendParseUniquePriceFeedUpdates(
+      deployer.getSender(),
+      Buffer.from(HERMES_SOL_TON_PYTH_USDT_UPDATE, "hex"),
+      sentValue,
+      [
+        SOL_PRICE_FEED_ID,
+        TON_PRICE_FEED_ID,
+        PYTH_PRICE_FEED_ID,
+        USDT_PRICE_FEED_ID,
+      ],
+      HERMES_SOL_UNIQUE_PUBLISH_TIME,
+      60,
+      deployer.address,
+      CUSTOM_PAYLOAD,
+    );
+
+    // Verify transaction success and message count
+    expect(result.transactions).toHaveTransaction({
+      from: deployer.address,
+      to: pythTest.address,
+      success: true,
+      outMessagesCount: 1,
+    });
+
+    // Get the output message
+    const outMessage = result.transactions[1].outMessages.values()[0];
+
+    // Verify excess value is returned
+    expect(
+      (outMessage.info as CommonMessageInfoInternal).value.coins,
+    ).toBeGreaterThan(0);
+
+    const cs = outMessage.body.beginParse();
+
+    // Verify message header
+    const op = cs.loadUint(32);
+    expect(op).toBe(6); // OP_PARSE_UNIQUE_PRICE_FEED_UPDATES
+
+    // Verify number of price feeds
+    const numPriceFeeds = cs.loadUint(8);
+    expect(numPriceFeeds).toBe(4); // We expect SOL, TON, PYTH and USDT price feeds
+
+    // Load and verify price feeds
+    const priceFeedsCell = cs.loadRef();
+    let currentCell = priceFeedsCell;
+
+    // First price feed (SOL)
+    const solCs = currentCell.beginParse();
+    const solPriceId =
+      "0x" + solCs.loadUintBig(256).toString(16).padStart(64, "0");
+    expect(solPriceId).toBe(SOL_PRICE_FEED_ID);
+
+    const solPriceFeedCell = solCs.loadRef();
+    const solPriceFeedSlice = solPriceFeedCell.beginParse();
+
+    // Verify SOL current price
+    const solCurrentPriceCell = solPriceFeedSlice.loadRef();
+    const solCurrentPrice = solCurrentPriceCell.beginParse();
+    expect(solCurrentPrice.loadInt(64)).toBe(HERMES_SOL_UNIQUE_PRICE);
+    expect(solCurrentPrice.loadUint(64)).toBe(HERMES_SOL_UNIQUE_CONF);
+    expect(solCurrentPrice.loadInt(32)).toBe(HERMES_SOL_UNIQUE_EXPO);
+    expect(solCurrentPrice.loadUint(64)).toBe(HERMES_SOL_UNIQUE_PUBLISH_TIME);
+
+    // Move through TON and PYTH price feeds to reach USDT
+    currentCell = solCs.loadRef(); // Move to TON
+    const tonCs = currentCell.beginParse();
+    tonCs.loadUintBig(256); // Skip TON price ID
+    tonCs.loadRef(); // Skip TON price data
+
+    currentCell = tonCs.loadRef(); // Move to PYTH
+    const pythCs = currentCell.beginParse();
+    pythCs.loadUintBig(256); // Skip PYTH price ID
+    pythCs.loadRef(); // Skip PYTH price data
+
+    currentCell = pythCs.loadRef(); // Move to USDT
+    const usdtCs = currentCell.beginParse();
+    const usdtPriceId =
+      "0x" + usdtCs.loadUintBig(256).toString(16).padStart(64, "0");
+    expect(usdtPriceId).toBe(USDT_PRICE_FEED_ID);
+
+    const usdtPriceFeedCell = usdtCs.loadRef();
+    const usdtPriceFeedSlice = usdtPriceFeedCell.beginParse();
+
+    // Verify USDT current price
+    const usdtCurrentPriceCell = usdtPriceFeedSlice.loadRef();
+    const usdtCurrentPrice = usdtCurrentPriceCell.beginParse();
+    expect(usdtCurrentPrice.loadInt(64)).toBe(HERMES_USDT_UNIQUE_PRICE);
+    expect(usdtCurrentPrice.loadUint(64)).toBe(HERMES_USDT_UNIQUE_CONF);
+    expect(usdtCurrentPrice.loadInt(32)).toBe(HERMES_USDT_UNIQUE_EXPO);
+    expect(usdtCurrentPrice.loadUint(64)).toBe(HERMES_USDT_UNIQUE_PUBLISH_TIME);
+
+    // Verify this is the end of the chain
+    expect(usdtCs.remainingRefs).toBe(0);
+
+    // Verify sender address
+    const senderAddress = cs.loadAddress();
+    expect(senderAddress?.toString()).toBe(
+      deployer.getSender().address.toString(),
+    );
+
+    // Verify custom payload
+    const customPayloadCell = cs.loadRef();
+    const customPayloadSlice = customPayloadCell.beginParse();
+    const receivedPayload = Buffer.from(
+      customPayloadSlice.loadBuffer(CUSTOM_PAYLOAD.length),
+    );
+    expect(receivedPayload.toString("hex")).toBe(
+      CUSTOM_PAYLOAD.toString("hex"),
+    );
+  });
+
   it("should fail to parse invalid price feed updates", async () => {
     await deployContract();
     await updateGuardianSets(pythTest, deployer);

ファイルの差分が大きいため隠しています
+ 65 - 0
target_chains/ton/contracts/tests/utils/pyth.ts


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません