Skip to content

Commit 498edea

Browse files
authored
feat(target_chains/ton): add update_price_feeds (#1870)
* add new storage * add update_price_feeds * precommit * update update_price_feeds * Revert changes to apps/hermes/server/Cargo.lock * feat(target_chains/ton): add more tests (#1872) * add more placeholder test functions * add logic for tests * precommit * add test for get_chain_id * move execute_governance_action to Pyth.fc * add comments to test contracts * precommit * address comments * update pnpm-lock * address comments * address comments * precommit
1 parent 152099a commit 498edea

File tree

19 files changed

+898
-164
lines changed

19 files changed

+898
-164
lines changed

pnpm-lock.yaml

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

target_chains/ton/contracts/contracts/Main.fc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "common/errors.fc";
33
#include "common/storage.fc";
44
#include "Wormhole.fc";
5+
#include "Pyth.fc";
56

67
;; Opcodes
78
const int OP_UPDATE_GUARDIAN_SET = 1;

target_chains/ton/contracts/contracts/Pyth.fc

Lines changed: 145 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,85 @@
22
#include "common/errors.fc";
33
#include "common/storage.fc";
44
#include "common/utils.fc";
5+
#include "common/constants.fc";
6+
#include "common/merkle_tree.fc";
57
#include "./Wormhole.fc";
68

7-
const int ACCUMULATOR_MAGIC = 0x504e4155; ;; "PNAU" (Pyth Network Accumulator Update)
8-
const int MAJOR_VERSION = 1;
9-
const int MINIMUM_ALLOWED_MINOR_VERSION = 0;
9+
cell store_price(int price, int conf, int expo, int publish_time) {
10+
return begin_cell()
11+
.store_int(price, 64)
12+
.store_uint(conf, 64)
13+
.store_int(expo, 32)
14+
.store_uint(publish_time, 64)
15+
.end_cell();
16+
}
1017

11-
slice verify_header(slice data) {
18+
slice read_and_verify_header(slice data) {
1219
int magic = data~load_uint(32);
1320
throw_unless(ERROR_INVALID_MAGIC, magic == ACCUMULATOR_MAGIC);
1421
int major_version = data~load_uint(8);
1522
throw_unless(ERROR_INVALID_MAJOR_VERSION, major_version == MAJOR_VERSION);
1623
int minor_version = data~load_uint(8);
1724
throw_if(ERROR_INVALID_MINOR_VERSION, minor_version < MINIMUM_ALLOWED_MINOR_VERSION);
1825
int trailing_header_size = data~load_uint(8);
19-
;; skip trailing headers and update type (uint8)
26+
;; skip trailing headers
2027
data~skip_bits(trailing_header_size);
21-
data~skip_bits(8);
28+
int update_type = data~load_uint(8);
29+
throw_unless(ERROR_INVALID_UPDATE_DATA_TYPE, update_type == WORMHOLE_MERKLE_UPDATE_TYPE);
2230
return data;
2331
}
2432

33+
(int, int, int, int, int, int, int, int, slice) read_and_verify_message(slice cs, int root_digest) impure {
34+
int message_size = cs~load_uint(16);
35+
(cell message, slice cs) = read_and_store_large_data(cs, message_size * 8);
36+
slice message = message.begin_parse();
37+
slice cs = read_and_verify_proof(root_digest, message, cs);
38+
39+
int message_type = message~load_uint(8);
40+
throw_unless(ERROR_INVALID_MESSAGE_TYPE, message_type == 0); ;; 0 corresponds to PriceFeed
41+
42+
int price_id = message~load_uint(256);
43+
int price = message~load_int(64);
44+
int conf = message~load_uint(64);
45+
int expo = message~load_int(32);
46+
int publish_time = message~load_uint(64);
47+
int prev_publish_time = message~load_uint(64);
48+
int ema_price = message~load_int(64);
49+
int ema_conf = message~load_uint(64);
50+
51+
return (price_id, price, conf, expo, publish_time, prev_publish_time, ema_price, ema_conf, cs);
52+
}
53+
54+
(int, int, int, int) parse_price(slice price_feed) {
55+
int price = price_feed~load_int(64);
56+
int conf = price_feed~load_uint(64);
57+
int expo = price_feed~load_int(32);
58+
int publish_time = price_feed~load_uint(64);
59+
return (price, conf, expo, publish_time);
60+
}
61+
2562
(int) get_update_fee(slice data) method_id {
2663
load_data();
27-
slice cs = verify_header(data);
64+
slice cs = read_and_verify_header(data);
2865
int wormhole_proof_size_bytes = cs~load_uint(16);
2966
(cell wormhole_proof, slice cs) = read_and_store_large_data(cs, wormhole_proof_size_bytes * 8);
3067
int num_updates = cs~load_uint(8);
3168
return single_update_fee * num_updates;
3269
}
3370

71+
int get_governance_data_source_index() method_id {
72+
load_data();
73+
return governance_data_source_index;
74+
}
75+
76+
cell get_governance_data_source() method_id {
77+
load_data();
78+
return governance_data_source;
79+
}
3480

35-
(int, int, int, int) parse_price(slice price_feed) {
36-
int price = price_feed~load_int(256);
37-
int conf = price_feed~load_uint(64);
38-
int expo = price_feed~load_int(32);
39-
int publish_time = price_feed~load_uint(64);
40-
return (price, conf, expo, publish_time);
81+
int get_last_executed_governance_sequence() method_id {
82+
load_data();
83+
return last_executed_governance_sequence;
4184
}
4285

4386
(int, int, int, int) get_price_unsafe(int price_feed_id) method_id {
@@ -74,3 +117,92 @@ slice verify_header(slice data) {
74117
throw_if(ERROR_OUTDATED_PRICE, current_time - publish_time > time_period);
75118
return (price, conf, expo, publish_time);
76119
}
120+
121+
(int, int) parse_data_source(cell data_source) {
122+
slice ds = data_source.begin_parse();
123+
int emitter_chain = ds~load_uint(16);
124+
int emitter_address = ds~load_uint(256);
125+
return (emitter_chain, emitter_address);
126+
}
127+
128+
int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
129+
int accumulator_wormhole_magic = payload~load_uint(32);
130+
throw_unless(ERROR_INVALID_MAGIC, accumulator_wormhole_magic == ACCUMULATOR_WORMHOLE_MAGIC);
131+
132+
int update_type = payload~load_uint(8);
133+
throw_unless(ERROR_INVALID_UPDATE_DATA_TYPE, update_type == WORMHOLE_MERKLE_UPDATE_TYPE);
134+
135+
payload~load_uint(64); ;; Skip slot
136+
payload~load_uint(32); ;; Skip ring_size
137+
138+
return payload~load_uint(160); ;; Return root_digest
139+
}
140+
141+
142+
() update_price_feeds(int msg_value, slice data) impure {
143+
load_data();
144+
slice cs = read_and_verify_header(data);
145+
146+
int wormhole_proof_size_bytes = cs~load_uint(16);
147+
(cell wormhole_proof, slice new_cs) = read_and_store_large_data(cs, wormhole_proof_size_bytes * 8);
148+
cs = new_cs;
149+
150+
int num_updates = cs~load_uint(8);
151+
int fee = single_update_fee * num_updates;
152+
153+
;; Check if the sender has sent enough TON to cover the fee
154+
throw_unless(ERROR_INSUFFICIENT_FEE, msg_value >= fee);
155+
156+
(_, _, _, _, int emitter_chain_id, int emitter_address, _, _, slice payload, _) = parse_and_verify_wormhole_vm(wormhole_proof.begin_parse());
157+
158+
;; Check if the data source is valid
159+
cell data_source = begin_cell()
160+
.store_uint(emitter_chain_id, 16)
161+
.store_uint(emitter_address, 256)
162+
.end_cell();
163+
164+
;; Dictionary doesn't support cell as key, so we use cell_hash to create a 256-bit integer key
165+
int data_source_key = cell_hash(data_source);
166+
167+
(slice value, int found?) = is_valid_data_source.udict_get?(256, data_source_key);
168+
throw_unless(ERROR_UPDATE_DATA_SOURCE_NOT_FOUND, found?);
169+
int valid = value~load_int(1);
170+
throw_unless(ERROR_INVALID_UPDATE_DATA_SOURCE, valid);
171+
172+
173+
int root_digest = parse_pyth_payload_in_wormhole_vm(payload);
174+
175+
repeat(num_updates) {
176+
(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);
177+
cs = new_cs;
178+
179+
(slice latest_price_info, int found?) = latest_price_feeds.udict_get?(256, price_id);
180+
int latest_publish_time = 0;
181+
if (found?) {
182+
slice price_feed_slice = latest_price_info~load_ref().begin_parse();
183+
slice price_slice = price_feed_slice~load_ref().begin_parse();
184+
185+
price_slice~load_int(64); ;; Skip price
186+
price_slice~load_uint(64); ;; Skip conf
187+
price_slice~load_int(32); ;; Skip expo
188+
latest_publish_time = price_slice~load_uint(64);
189+
}
190+
191+
if (publish_time > latest_publish_time) {
192+
cell price_feed = begin_cell()
193+
.store_ref(store_price(price, conf, expo, publish_time))
194+
.store_ref(store_price(ema_price, ema_conf, expo, publish_time))
195+
.end_cell();
196+
197+
latest_price_feeds~udict_set(256, price_id, begin_cell().store_ref(price_feed).end_cell().begin_parse());
198+
}
199+
}
200+
201+
throw_if(ERROR_INVALID_UPDATE_DATA_LENGTH, ~ cs.slice_empty?());
202+
203+
store_data();
204+
}
205+
206+
() execute_governance_action(slice in_msg_body) impure {
207+
;; TODO: Implement
208+
}

0 commit comments

Comments
 (0)