Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
17 changes: 17 additions & 0 deletions target_chains/ton/contracts/contracts/Main.fc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
cell data = in_msg_body~load_ref();
slice data_slice = data.begin_parse();

;; Get sender address from message
slice cs = in_msg_full.begin_parse();
cs~skip_bits(4); ;; skip flags
slice sender_address = cs~load_msg_addr(); ;; load sender address

;; * The remainder of the message body is specific for each supported value of `op`.
if (op == OP_UPDATE_GUARDIAN_SET) {
update_guardian_set(data_slice);
Expand All @@ -25,6 +30,18 @@
execute_governance_action(data_slice);
} elseif (op == OP_UPGRADE_CONTRACT) {
execute_upgrade_contract(data);
} elseif (op == OP_PARSE_PRICE_FEED_UPDATES) {
cell price_ids_cell = in_msg_body~load_ref();
slice price_ids_slice = price_ids_cell.begin_parse();
int min_publish_time = in_msg_body~load_uint(64);
int max_publish_time = in_msg_body~load_uint(64);
parse_price_feed_updates(msg_value, data_slice, price_ids_slice, min_publish_time, max_publish_time, sender_address);
} elseif (op == OP_PARSE_UNIQUE_PRICE_FEED_UPDATES) {
cell price_ids_cell = in_msg_body~load_ref();
slice price_ids_slice = price_ids_cell.begin_parse();
int publish_time = in_msg_body~load_uint(64);
int max_staleness = in_msg_body~load_uint(64);
parse_unique_price_feed_updates(msg_value, data_slice, price_ids_slice, publish_time, max_staleness, sender_address);
} else {
throw(0xffff); ;; Throw exception for unknown op
}
Expand Down
159 changes: 132 additions & 27 deletions target_chains/ton/contracts/contracts/Pyth.fc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "common/merkle_tree.fc";
#include "common/governance_actions.fc";
#include "common/gas.fc";
#include "common/op.fc";
#include "./Wormhole.fc";

cell store_price(int price, int conf, int expo, int publish_time) {
Expand Down Expand Up @@ -156,7 +157,20 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
return payload~load_uint(160); ;; Return root_digest
}

tuple parse_price_feeds_from_data(slice data) {
() calculate_and_validate_fees(int msg_value, int num_updates) impure {
int update_fee = single_update_fee * num_updates;
int compute_fee = get_compute_fee(
WORKCHAIN,
UPDATE_PRICE_FEEDS_BASE_GAS + (UPDATE_PRICE_FEEDS_PER_UPDATE_GAS * num_updates)
);
throw_unless(ERROR_INSUFFICIENT_GAS, msg_value >= compute_fee);
int remaining_msg_value = msg_value - compute_fee;

;; Check if the sender has sent enough TON to cover the update_fee
throw_unless(ERROR_INSUFFICIENT_FEE, remaining_msg_value >= update_fee);
}

tuple parse_price_feeds_from_data(int msg_value, slice data, tuple price_ids, int min_publish_time, int max_publish_time, int unique) {
slice cs = read_and_verify_header(data);

int wormhole_proof_size_bytes = cs~load_uint(16);
Expand All @@ -165,6 +179,8 @@ tuple parse_price_feeds_from_data(slice data) {

int num_updates = cs~load_uint(8);

calculate_and_validate_fees(msg_value, num_updates);

(_, _, _, _, int emitter_chain_id, int emitter_address, _, _, slice payload, _) = parse_and_verify_wormhole_vm(wormhole_proof.begin_parse());

;; Check if the data source is valid
Expand All @@ -186,47 +202,136 @@ tuple parse_price_feeds_from_data(slice data) {
tuple price_feeds = empty_tuple();

repeat(num_updates) {
(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);
(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);
cs = new_cs;
int should_include = (price_ids.tlen() == 0)
| ((price_ids.tlen() > 0)
& (publish_time >= min_publish_time)
& (publish_time <= max_publish_time)
& ((unique == 0) | (min_publish_time > prev_publish_time)));

if (should_include) {
;; Create price feed cell containing both current and EMA prices
cell price_feed_cell = begin_cell()
.store_ref(store_price(price, conf, expo, publish_time))
.store_ref(store_price(ema_price, ema_conf, expo, publish_time))
.end_cell();

tuple price_feed = empty_tuple();
price_feed~tpush(price_id);
price_feed~tpush(price_feed_cell);
price_feeds~tpush(price_feed);
}

;; Create price feed cell containing both current and EMA prices
cell price_feed_cell = begin_cell()
.store_ref(store_price(price, conf, expo, publish_time))
.store_ref(store_price(ema_price, ema_conf, expo, publish_time))
.end_cell();

tuple price_feed = empty_tuple();
price_feed~tpush(price_id);
price_feed~tpush(price_feed_cell);
price_feeds~tpush(price_feed);
}

throw_if(ERROR_INVALID_UPDATE_DATA_LENGTH, ~ cs.slice_empty?());

return price_feeds;
}

;; Returns a tuple of price feeds (max 255 due to TON limitations of tuple size)
tuple parse_price_feed_updates(slice data) method_id {
load_data();
tuple price_feeds = parse_price_feeds_from_data(data);
return price_feeds;
;; Creates a chain of cells from price feeds, with each cell containing exactly one price_id (256 bits)
;; and one ref to the price feed cell. Returns the head of the chain.
;; Each cell now contains exactly:
;; - One price_id (256 bits)
;; - One ref to price_feed_cell
;; - One optional ref to next cell in chain
;; This approach is:
;; - More consistent with TON's cell model
;; - Easier to traverse and read individual price feeds
;; - Cleaner separation of data
;; - More predictable in terms of cell structure
cell create_price_feed_cell_chain(tuple price_feeds) {
cell result = null();

int i = price_feeds.tlen() - 1;
while (i >= 0) {
tuple price_feed = price_feeds.at(i);
int price_id = price_feed.at(0);
Copy link
Contributor Author

@cctdaniel cctdaniel Nov 11, 2024

Choose a reason for hiding this comment

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

0 is price_id, 1 is price_feed_cell

cell price_feed_cell = price_feed.at(1);

;; Create new cell with single price feed and chain to previous result
builder current_builder = begin_cell()
.store_uint(price_id, 256) ;; Store price_id
.store_ref(price_feed_cell); ;; Store price data ref

;; Chain to previous cells if they exist
if (~ cell_null?(result)) {
current_builder = current_builder.store_ref(result);
}

result = current_builder.end_cell();
i -= 1;
}

return result;
}

() update_price_feeds(int msg_value, slice data) impure {
load_data();
tuple price_feeds = parse_price_feeds_from_data(data);
int num_updates = price_feeds.tlen();
int update_fee = single_update_fee * num_updates;
() send_price_feeds_response(tuple price_feeds, int msg_value, int op, slice sender_address) impure {
;; Build response cell with price feeds
builder response = begin_cell()
.store_uint(op, 32) ;; Response op
.store_uint(price_feeds.tlen(), 8); ;; Number of price feeds

;; Create and store price feed cell chain
cell price_feeds_cell = create_price_feed_cell_chain(price_feeds);
response = response.store_ref(price_feeds_cell);

;; Calculate remaining value after fees
int compute_fee = get_compute_fee(
WORKCHAIN,
UPDATE_PRICE_FEEDS_BASE_GAS + (UPDATE_PRICE_FEEDS_PER_UPDATE_GAS * num_updates)
UPDATE_PRICE_FEEDS_BASE_GAS + (UPDATE_PRICE_FEEDS_PER_UPDATE_GAS * price_feeds.tlen())
);
throw_unless(ERROR_INSUFFICIENT_GAS, msg_value >= compute_fee);
int remaining_msg_value = msg_value - compute_fee;
int update_fee = single_update_fee * price_feeds.tlen();
int excess = msg_value - compute_fee - update_fee;

;; Send response message back to sender with exact excess amount
send_raw_message(begin_cell()
.store_uint(0x18, 6)
.store_slice(sender_address)
.store_coins(excess)
.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_ref(response.end_cell())
.end_cell(),
0);
}

;; Check if the sender has sent enough TON to cover the update_fee
throw_unless(ERROR_INSUFFICIENT_FEE, remaining_msg_value >= update_fee);
() 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 {
load_data();

;; Load price_ids tuple
int price_ids_len = price_ids_slice~load_uint(8);

Choose a reason for hiding this comment

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

Should price_ids_len == 0 be supported? This will then be treated like a call from update_price_feeds within parse_price_feeds_from_data and everything will be included, meaning min_publish_time and max_publish_time are ignored.

tuple price_ids = empty_tuple();
repeat(price_ids_len) {
int price_id = price_ids_slice~load_uint(256);
price_ids~tpush(price_id);
}

tuple price_feeds = parse_price_feeds_from_data(msg_value, update_data_slice, price_ids, min_publish_time, max_publish_time, false);
throw_if(ERROR_NO_PRICE_FEEDS_FOUND, price_feeds.tlen() == 0);
send_price_feeds_response(price_feeds, msg_value, OP_PARSE_PRICE_FEED_UPDATES, sender_address);
}

() 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 {
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);
}

tuple price_feeds = parse_price_feeds_from_data(msg_value, update_data_slice, price_ids, publish_time, publish_time + max_staleness, true);
throw_if(ERROR_NO_PRICE_FEEDS_FOUND, price_feeds.tlen() == 0);
send_price_feeds_response(price_feeds, msg_value, OP_PARSE_UNIQUE_PRICE_FEED_UPDATES, sender_address);
}

() update_price_feeds(int msg_value, slice data) impure {
load_data();
tuple price_feeds = parse_price_feeds_from_data(msg_value, data, empty_tuple(), 0, 0, false);
int num_updates = price_feeds.tlen();

int i = 0;
while(i < num_updates) {
Expand Down
1 change: 1 addition & 0 deletions target_chains/ton/contracts/contracts/common/errors.fc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const int ERROR_INVALID_GOVERNANCE_MAGIC = 2016;
const int ERROR_INVALID_GOVERNANCE_MODULE = 2017;
const int ERROR_INVALID_CODE_HASH = 2018;
const int ERROR_INVALID_PAYLOAD_LENGTH = 2019;
const int ERROR_NO_PRICE_FEEDS_FOUND = 2020;

;; Common
const int ERROR_INSUFFICIENT_GAS = 3000;
2 changes: 2 additions & 0 deletions target_chains/ton/contracts/contracts/common/op.fc
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ const int OP_UPDATE_GUARDIAN_SET = 1;
const int OP_UPDATE_PRICE_FEEDS = 2;
const int OP_EXECUTE_GOVERNANCE_ACTION = 3;
const int OP_UPGRADE_CONTRACT = 4;
const int OP_PARSE_PRICE_FEED_UPDATES = 5;
const int OP_PARSE_UNIQUE_PRICE_FEED_UPDATES = 6;
22 changes: 18 additions & 4 deletions target_chains/ton/contracts/contracts/tests/PythTest.fc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
int op = in_msg_body~load_uint(32);
cell data = in_msg_body~load_ref();
slice data_slice = data.begin_parse();

;; Get sender address from message
slice cs = in_msg_full.begin_parse();
cs~skip_bits(4); ;; skip flags
slice sender_address = cs~load_msg_addr(); ;; load sender address

if (op == OP_UPDATE_GUARDIAN_SET) {
update_guardian_set(data_slice);
} elseif (op == OP_UPDATE_PRICE_FEEDS) {
Expand All @@ -29,6 +35,18 @@
execute_governance_action(data_slice);
} elseif (op == OP_UPGRADE_CONTRACT) {
execute_upgrade_contract(data);
} elseif (op == OP_PARSE_PRICE_FEED_UPDATES) {
cell price_ids_cell = in_msg_body~load_ref();
slice price_ids_slice = price_ids_cell.begin_parse();
int min_publish_time = in_msg_body~load_uint(64);
int max_publish_time = in_msg_body~load_uint(64);
parse_price_feed_updates(msg_value, data_slice, price_ids_slice, min_publish_time, max_publish_time, sender_address);
} elseif (op == OP_PARSE_UNIQUE_PRICE_FEED_UPDATES) {
cell price_ids_cell = in_msg_body~load_ref();
slice price_ids_slice = price_ids_cell.begin_parse();
int publish_time = in_msg_body~load_uint(64);
int max_staleness = in_msg_body~load_uint(64);
parse_unique_price_feed_updates(msg_value, data_slice, price_ids_slice, publish_time, max_staleness, sender_address);
} else {
throw(0xffff); ;; Throw exception for unknown op
}
Expand Down Expand Up @@ -77,7 +95,3 @@
(int) test_get_is_valid_data_source(cell data_source) method_id {
return get_is_valid_data_source(data_source);
}

(tuple) test_parse_price_feed_updates(slice data) method_id {
return parse_price_feed_updates(data);
}
22 changes: 18 additions & 4 deletions target_chains/ton/contracts/contracts/tests/PythTestUpgraded.fc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
int op = in_msg_body~load_uint(32);
cell data = in_msg_body~load_ref();
slice data_slice = data.begin_parse();

;; Get sender address from message
slice cs = in_msg_full.begin_parse();
cs~skip_bits(4); ;; skip flags
slice sender_address = cs~load_msg_addr(); ;; load sender address

if (op == OP_UPDATE_GUARDIAN_SET) {
update_guardian_set(data_slice);
} elseif (op == OP_UPDATE_PRICE_FEEDS) {
Expand All @@ -23,6 +29,18 @@
execute_governance_action(data_slice);
} elseif (op == OP_UPGRADE_CONTRACT) {
execute_upgrade_contract(data);
} elseif (op == OP_PARSE_PRICE_FEED_UPDATES) {
cell price_ids_cell = in_msg_body~load_ref();
slice price_ids_slice = price_ids_cell.begin_parse();
int min_publish_time = in_msg_body~load_uint(64);
int max_publish_time = in_msg_body~load_uint(64);
parse_price_feed_updates(msg_value, data_slice, price_ids_slice, min_publish_time, max_publish_time, sender_address);
} elseif (op == OP_PARSE_UNIQUE_PRICE_FEED_UPDATES) {
cell price_ids_cell = in_msg_body~load_ref();
slice price_ids_slice = price_ids_cell.begin_parse();
int publish_time = in_msg_body~load_uint(64);
int max_staleness = in_msg_body~load_uint(64);
parse_unique_price_feed_updates(msg_value, data_slice, price_ids_slice, publish_time, max_staleness, sender_address);
} else {
throw(0xffff); ;; Throw exception for unknown op
}
Expand Down Expand Up @@ -72,10 +90,6 @@
return get_is_valid_data_source(data_source);
}

(tuple) test_parse_price_feed_updates(slice data) method_id {
return parse_price_feed_updates(data);
}

;; Add a new function to demonstrate the upgrade
(int) test_new_function() method_id {
return 1;
Expand Down
Loading
Loading