|
2 | 2 | #include "common/errors.fc";
|
3 | 3 | #include "common/storage.fc";
|
4 | 4 | #include "common/utils.fc";
|
| 5 | +#include "common/constants.fc"; |
| 6 | +#include "common/merkle_tree.fc"; |
5 | 7 | #include "./Wormhole.fc";
|
6 | 8 |
|
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 | +} |
10 | 17 |
|
11 |
| -slice verify_header(slice data) { |
| 18 | +slice read_and_verify_header(slice data) { |
12 | 19 | int magic = data~load_uint(32);
|
13 | 20 | throw_unless(ERROR_INVALID_MAGIC, magic == ACCUMULATOR_MAGIC);
|
14 | 21 | int major_version = data~load_uint(8);
|
15 | 22 | throw_unless(ERROR_INVALID_MAJOR_VERSION, major_version == MAJOR_VERSION);
|
16 | 23 | int minor_version = data~load_uint(8);
|
17 | 24 | throw_if(ERROR_INVALID_MINOR_VERSION, minor_version < MINIMUM_ALLOWED_MINOR_VERSION);
|
18 | 25 | int trailing_header_size = data~load_uint(8);
|
19 |
| - ;; skip trailing headers and update type (uint8) |
| 26 | + ;; skip trailing headers |
20 | 27 | 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); |
22 | 30 | return data;
|
23 | 31 | }
|
24 | 32 |
|
| 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 | + |
25 | 62 | (int) get_update_fee(slice data) method_id {
|
26 | 63 | load_data();
|
27 |
| - slice cs = verify_header(data); |
| 64 | + slice cs = read_and_verify_header(data); |
28 | 65 | int wormhole_proof_size_bytes = cs~load_uint(16);
|
29 | 66 | (cell wormhole_proof, slice cs) = read_and_store_large_data(cs, wormhole_proof_size_bytes * 8);
|
30 | 67 | int num_updates = cs~load_uint(8);
|
31 | 68 | return single_update_fee * num_updates;
|
32 | 69 | }
|
33 | 70 |
|
| 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 | +} |
34 | 80 |
|
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; |
41 | 84 | }
|
42 | 85 |
|
43 | 86 | (int, int, int, int) get_price_unsafe(int price_feed_id) method_id {
|
@@ -74,3 +117,92 @@ slice verify_header(slice data) {
|
74 | 117 | throw_if(ERROR_OUTDATED_PRICE, current_time - publish_time > time_period);
|
75 | 118 | return (price, conf, expo, publish_time);
|
76 | 119 | }
|
| 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