Skip to content

Commit 0714994

Browse files
authored
feat(target_chains/ton): add pyth contract functions (price_unsafe, price_no_older_than, ema_price_unsafe, ema_price_no_older_than) (#1845)
* organize file structure * add price_unsafe and ema_price_unsafe * add price_no_older_than and ema_price_no_older_than * add tests for price_unsafe and ema_price_unsafe * fix ci * add tests for price_no_older_than and ema_price_no_older_than * address comments * update lock file
1 parent 795a84e commit 0714994

File tree

19 files changed

+513
-84
lines changed

19 files changed

+513
-84
lines changed

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include "imports/stdlib.fc";
2+
#include "common/errors.fc";
3+
#include "common/storage.fc";
4+
#include "Wormhole.fc";
5+
6+
;; Opcodes
7+
const int OP_UPDATE_GUARDIAN_SET = 1;
8+
const int OP_EXECUTE_GOVERNANCE_ACTION = 2;
9+
10+
;; Internal message handler
11+
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
12+
if (in_msg_body.slice_empty?()) { ;; ignore empty messages
13+
return ();
14+
}
15+
16+
;; * A 32-bit (big-endian) unsigned integer `op`, identifying the `operation` to be performed, or the `method` of the smart contract to be invoked.
17+
int op = in_msg_body~load_uint(32);
18+
;; * A 64-bit (big-endian) unsigned integer `query_id`, used in all query-response internal messages to indicate that a response is related to a query (the `query_id` of a response must be equal to the `query_id` of the corresponding query). If `op` is not a query-response method (e.g., it invokes a method that is not expected to send an answer), then `query_id` may be omitted.
19+
int query_id = in_msg_body~load_uint(64);
20+
21+
;; * The remainder of the message body is specific for each supported value of `op`.
22+
if (op == OP_UPDATE_GUARDIAN_SET) {
23+
update_guardian_set(in_msg_body);
24+
} elseif (op == OP_EXECUTE_GOVERNANCE_ACTION) {
25+
execute_governance_action(in_msg_body);
26+
} else {
27+
throw(0xffff); ;; Throw exception for unknown op
28+
}
29+
}
Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,46 @@
11
#include "imports/stdlib.fc";
2-
#include "Wormhole.fc";
2+
#include "common/errors.fc";
3+
#include "common/storage.fc";
34

45
;; Opcodes
56
const int OP_UPDATE_GUARDIAN_SET = 1;
67
const int OP_EXECUTE_GOVERNANCE_ACTION = 2;
78

8-
;; Internal message handler
9-
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
10-
if (in_msg_body.slice_empty?()) { ;; ignore empty messages
11-
return ();
12-
}
9+
(int, int, int, int) parse_price(slice price_feed) {
10+
int price = price_feed~load_int(256);
11+
int conf = price_feed~load_uint(64);
12+
int expo = price_feed~load_int(32);
13+
int publish_time = price_feed~load_uint(64);
14+
return (price, conf, expo, publish_time);
15+
}
16+
17+
(int, int, int, int) price_unsafe(int price_feed_id) method_id {
18+
(slice result, int success) = latest_price_feeds.udict_get?(256, price_feed_id);
19+
throw_unless(ERROR_PRICE_FEED_NOT_FOUND, success);
20+
slice price_feed = result~load_ref().begin_parse();
21+
slice price = price_feed~load_ref().begin_parse();
22+
return parse_price(price);
23+
}
1324

14-
;; * A 32-bit (big-endian) unsigned integer `op`, identifying the `operation` to be performed, or the `method` of the smart contract to be invoked.
15-
int op = in_msg_body~load_uint(32);
16-
;; * A 64-bit (big-endian) unsigned integer `query_id`, used in all query-response internal messages to indicate that a response is related to a query (the `query_id` of a response must be equal to the `query_id` of the corresponding query). If `op` is not a query-response method (e.g., it invokes a method that is not expected to send an answer), then `query_id` may be omitted.
17-
int query_id = in_msg_body~load_uint(64);
25+
(int, int, int, int) price_no_older_than(int time_period, int price_feed_id) method_id {
26+
(int price, int conf, int expo, int publish_time) = price_unsafe(price_feed_id);
27+
int current_time = now();
28+
throw_if(ERROR_OUTDATED_PRICE, current_time - publish_time > time_period);
29+
return (price, conf, expo, publish_time);
30+
}
31+
32+
(int, int, int, int) ema_price_unsafe(int price_feed_id) method_id {
33+
(slice result, int success) = latest_price_feeds.udict_get?(256, price_feed_id);
34+
throw_unless(ERROR_PRICE_FEED_NOT_FOUND, success);
35+
slice price_feed = result~load_ref().begin_parse();
36+
slice price = price_feed~load_ref().begin_parse();
37+
slice ema_price = price_feed~load_ref().begin_parse();
38+
return parse_price(ema_price);
39+
}
1840

19-
;; * The remainder of the message body is specific for each supported value of `op`.
20-
if (op == OP_UPDATE_GUARDIAN_SET) {
21-
update_guardian_set(in_msg_body);
22-
} elseif (op == OP_EXECUTE_GOVERNANCE_ACTION) {
23-
execute_governance_action(in_msg_body);
24-
} else {
25-
throw(0xffff); ;; Throw exception for unknown op
26-
}
41+
(int, int, int, int) ema_price_no_older_than(int time_period, int price_feed_id) method_id {
42+
(int price, int conf, int expo, int publish_time) = ema_price_unsafe(price_feed_id);
43+
int current_time = now();
44+
throw_if(ERROR_OUTDATED_PRICE, current_time - publish_time > time_period);
45+
return (price, conf, expo, publish_time);
2746
}

target_chains/ton/contracts/contracts/Wormhole.fc

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "imports/stdlib.fc";
2-
#include "imports/errors.fc";
3-
#include "imports/utils.fc";
4-
#include "imports/storage.fc";
2+
#include "common/errors.fc";
3+
#include "common/utils.fc";
4+
#include "common/storage.fc";
55

66
;; Signature verification function
77
;; ECRECOVER: Recovers the signer's address from the signature
@@ -55,32 +55,6 @@ const int UPGRADE_MODULE = 0x000000000000000000000000000000000000000000000000000
5555
return (expiration_time, keys, key_count);
5656
}
5757

58-
;; store_data stores data in the contract
59-
() store_data() impure inline_ref {
60-
begin_cell()
61-
.store_uint(current_guardian_set_index, 32)
62-
.store_dict(guardian_sets)
63-
.store_uint(chain_id, 16)
64-
.store_uint(governance_chain_id, 16)
65-
.store_uint(governance_contract, 256)
66-
.store_dict(consumed_governance_actions)
67-
.end_cell()
68-
.set_data();
69-
}
70-
71-
;; load_data populates storage variables using stored data
72-
() load_data() impure inline_ref {
73-
var ds = get_data().begin_parse();
74-
current_guardian_set_index = ds~load_uint(32);
75-
guardian_sets = ds~load_dict();
76-
(int expiration_time, cell keys, int key_count) = get_guardian_set_internal(current_guardian_set_index);
77-
chain_id = ds~load_uint(16);
78-
governance_chain_id = ds~load_uint(16);
79-
governance_contract = ds~load_uint(256);
80-
consumed_governance_actions = ds~load_dict();
81-
ds.end_parse();
82-
}
83-
8458

8559
;; Get methods
8660
int get_current_guardian_set_index() method_id {

target_chains/ton/contracts/contracts/imports/errors.fc renamed to target_chains/ton/contracts/contracts/common/errors.fc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
;; Error codes enum
2+
3+
;; Wormhole
24
const int ERROR_INVALID_GUARDIAN_SET = 1000;
35
const int ERROR_INVALID_VERSION = 1001;
46
const int ERROR_GUARDIAN_SET_NOT_FOUND = 1002;
@@ -18,3 +20,7 @@ const int ERROR_INVALID_GUARDIAN_SET_UPGRADE_LENGTH = 1015;
1820
const int ERROR_INVALID_GOVERNANCE_CHAIN = 1016;
1921
const int ERROR_INVALID_GOVERNANCE_CONTRACT = 1017;
2022
const int ERROR_INVALID_GUARDIAN_ADDRESS = 1018;
23+
24+
;; Pyth
25+
const int ERROR_PRICE_FEED_NOT_FOUND = 1019;
26+
const int ERROR_OUTDATED_PRICE = 1020;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include "../imports/stdlib.fc";
2+
3+
;; Pyth
4+
;; Price struct: {price: int, conf: int, expo: int, publish_time: int}
5+
;; PriceFeed struct: {price: Price, ema_price: Price}
6+
global cell latest_price_feeds; ;; Dictionary of PriceFeed structs, keyed by price_feed_id (256-bit)
7+
8+
;; Wormhole
9+
global int current_guardian_set_index;
10+
;; GuardianSet struct: {expiration_time: int, keys: cell}
11+
;; The 'keys' cell is a dictionary with the following structure:
12+
;; - Key: 8-bit unsigned integer (guardian index)
13+
;; - Value: 160-bit unsigned integer (guardian address)
14+
global cell guardian_sets;
15+
global int chain_id;
16+
global int governance_chain_id;
17+
;; GovernanceContract struct: {chain_id: int, address: slice}
18+
global int governance_contract;
19+
global cell consumed_governance_actions;
20+
21+
22+
() store_data() impure inline_ref {
23+
begin_cell()
24+
.store_dict(latest_price_feeds)
25+
.store_uint(current_guardian_set_index, 32)
26+
.store_dict(guardian_sets)
27+
.store_uint(chain_id, 16)
28+
.store_uint(governance_chain_id, 16)
29+
.store_uint(governance_contract, 256)
30+
.store_dict(consumed_governance_actions)
31+
.end_cell()
32+
.set_data();
33+
}
34+
35+
;; load_data populates storage variables using stored data
36+
() load_data() impure inline_ref {
37+
var ds = get_data().begin_parse();
38+
latest_price_feeds = ds~load_dict();
39+
current_guardian_set_index = ds~load_uint(32);
40+
guardian_sets = ds~load_dict();
41+
chain_id = ds~load_uint(16);
42+
governance_chain_id = ds~load_uint(16);
43+
governance_contract = ds~load_uint(256);
44+
consumed_governance_actions = ds~load_dict();
45+
ds.end_parse();
46+
}

target_chains/ton/contracts/contracts/imports/storage.fc

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include "../imports/stdlib.fc";
2+
#include "../Pyth.fc";
3+
4+
() recv_internal(int balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
5+
;; nop;
6+
}
7+
8+
(int, int, int, int) test_price_unsafe(int price_feed_id) method_id {
9+
load_data();
10+
return price_unsafe(price_feed_id);
11+
}
12+
13+
(int, int, int, int) test_price_no_older_than(int time_period, int price_feed_id) method_id {
14+
load_data();
15+
return price_no_older_than(time_period, price_feed_id);
16+
}
17+
18+
(int, int, int, int) test_ema_price_unsafe(int price_feed_id) method_id {
19+
load_data();
20+
return ema_price_unsafe(price_feed_id);
21+
}
22+
23+
(int, int, int, int) test_ema_price_no_older_than(int time_period, int price_feed_id) method_id {
24+
load_data();
25+
return ema_price_no_older_than(time_period, price_feed_id);
26+
}

target_chains/ton/contracts/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
{
2-
"name": "pyth",
2+
"name": "@pythnetwork/pyth-ton",
33
"version": "0.0.1",
44
"scripts": {
55
"start": "blueprint run",
66
"build": "echo Pyth | blueprint build",
77
"test": "jest --verbose"
88
},
99
"devDependencies": {
10+
"@pythnetwork/price-service-sdk": "workspace:*",
1011
"@ton/blueprint": "^0.22.0",
12+
"@ton/core": "~0",
13+
"@ton/crypto": "^3.2.0",
1114
"@ton/sandbox": "^0.20.0",
1215
"@ton/test-utils": "^0.4.2",
16+
"@ton/ton": "^13.11.2",
1317
"@types/jest": "^29.5.12",
1418
"@types/node": "^20.14.10",
1519
"jest": "^29.7.0",
1620
"prettier": "^3.3.2",
17-
"@ton/ton": "^13.11.2",
18-
"@ton/core": "~0",
19-
"@ton/crypto": "^3.2.0",
2021
"ts-jest": "^29.2.0",
2122
"ts-node": "^10.9.2",
2223
"typescript": "^5.5.3"

0 commit comments

Comments
 (0)