Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 43 additions & 14 deletions target_chains/ton/contracts/contracts/Pyth.fc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
245 changes: 245 additions & 0 deletions target_chains/ton/contracts/tests/PythTest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -1110,6 +1123,122 @@ describe("PythTest", () => {
);
});

it("should successfully parse price feed updates with more than 3 price feed ids", async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add checks for the last feed in the list as well? just to make sure all work fine.

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);
Expand Down Expand Up @@ -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);
Expand Down
Loading
Loading