Skip to content

Commit d7d17e5

Browse files
committed
add pyth-connecor example
Signed-off-by: master_jedy <[email protected]>
1 parent b6be282 commit d7d17e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+9468
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.env
2+
yarn.lock
3+
node_modules
4+
build
5+
dist
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Pyth-connector example
2+
Provides onchain-getter: User -> User JettonWallet -> App -> Pyth -> App -> ...
3+
and proxy call: User -> Pyth -> App -> ... pyth usage examples.
4+
5+
This example can be used as a separate module providing tools for sandbox testing: exports functions for deploying and configuring a local pyth contract
6+
7+
It shows techniques how to use the pyth oracle in finacial applications.
8+
The demonstration is fully sandboxed and doesn't need real on-chain contracts nor testnet neither mainnet.
9+
Usage of hermes client is also not required: prices can be formed locally, e.g. **{TON: 3.12345, USDC: 0.998, USDT: 0.999}.**
10+
11+
## Project structure
12+
13+
- `contracts` - source code of all the smart contracts of the project and their dependencies.
14+
- `wrappers` - wrapper classes (implementing `Contract` from ton-core) for the contracts, including any [de]serialization primitives and compilation functions.
15+
- `tests` - tests for the contracts.
16+
- `scripts` - scripts used by the project, mainly the deployment scripts.
17+
18+
## How to use
19+
First you need to install dependencies, node v22 is required, you can use nvm to install it: `nvm use 22` .
20+
Then install dependencies, just run `yarn`
21+
22+
### Build
23+
to build the module you can run`yarn build`
24+
25+
### Contracts
26+
To prebuild contracts run`yarn contracts`
27+
28+
### Test
29+
`yarn test:unit`
30+
31+
### Deploy
32+
You don't need to deploy this example's contracts to testnet/mainnet,
33+
34+
## Important Note on Message Handling
35+
36+
When using the Pyth price feed in the recommended flow (User/App -> Pyth -> Protocol), be aware that:
37+
38+
### Security Warning ⚠️
39+
40+
**CRITICAL**: Integrators MUST validate the sender address in their receive function to ensure messages are coming from the Pyth Oracle contract. Failure to do so could allow attackers to:
41+
42+
- Send invalid price responses
43+
- Impersonate users via the sender_address and custom_payload fields
44+
- Potentially drain the protocol
45+
46+
### Message Bouncing Behavior
47+
48+
- If the target protocol bounces the message (e.g., due to invalid custom payload or other errors), the forwarded TON will remain in the Pyth contract and will not be automatically refunded to the original sender.
49+
- This could be significant when dealing with large amounts of TON (e.g., in DeFi operations).
50+
- Integrators should implement proper error handling and refund mechanisms in their applications.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#include "imports/stdlib.fc";
2+
#include "common/errors.fc";
3+
#include "common/storage.fc";
4+
#include "common/op.fc";
5+
#include "Wormhole.fc";
6+
#include "Pyth.fc";
7+
8+
;; @title Pyth Network Price Oracle Contract for TON
9+
;; @notice This contract serves as the main entry point for the Pyth Network price oracle on TON.
10+
;; @dev The contract handles various operations including:
11+
;; - Updating guardian sets for Wormhole message verification
12+
;; - Updating price feeds with the latest price data
13+
;; - Executing governance actions
14+
;; - Upgrading the contract code
15+
;; - Parsing price feed updates for clients
16+
;;
17+
;; The contract uses Wormhole's cross-chain messaging protocol to verify price updates
18+
;; and governance actions. It maintains a dictionary of price feeds indexed by price ID.
19+
;; Each price feed contains the current price, confidence interval, exponent, and publish time.
20+
21+
;; Internal message handler
22+
;; @param my_balance - Current contract balance
23+
;; @param msg_value - Amount of TON sent with the message
24+
;; @param in_msg_full - Full incoming message cell
25+
;; @param in_msg_body - Message body as a slice
26+
;; @returns () - No return value
27+
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
28+
if (in_msg_body.slice_empty?()) { ;; ignore empty messages
29+
return ();
30+
}
31+
32+
;; * A 32-bit (big-endian) unsigned integer `op`, identifying the `operation` to be performed, or the `method` of the smart contract to be invoked.
33+
int op = in_msg_body~load_uint(32);
34+
cell data = in_msg_body~load_ref();
35+
slice data_slice = data.begin_parse();
36+
37+
;; Get sender address from message
38+
slice cs = in_msg_full.begin_parse();
39+
int flags = cs~load_uint(4);
40+
if (flags & 1) { ;; ignore all bounced messages
41+
return ();
42+
}
43+
slice sender_address = cs~load_msg_addr(); ;; load sender address
44+
45+
;; * The remainder of the message body is specific for each supported value of `op`.
46+
if (op == OP_UPDATE_GUARDIAN_SET) {
47+
;; @notice Updates the guardian set based on a Wormhole VAA
48+
;; @param data_slice - Slice containing the VAA with guardian set update information
49+
update_guardian_set(data_slice);
50+
} elseif (op == OP_UPDATE_PRICE_FEEDS) {
51+
;; @notice Updates price feeds with the latest price data
52+
;; @param msg_value - Amount of TON sent with the message (used for fee calculation)
53+
;; @param data_slice - Slice containing the price feed update data
54+
update_price_feeds(msg_value, data_slice);
55+
} elseif (op == OP_EXECUTE_GOVERNANCE_ACTION) {
56+
;; @notice Executes a governance action based on a Wormhole VAA
57+
;; @param data_slice - Slice containing the VAA with governance action information
58+
execute_governance_action(data_slice);
59+
} elseif (op == OP_UPGRADE_CONTRACT) {
60+
;; @notice Upgrades the contract code
61+
;; @param data - Cell containing the new contract code
62+
execute_upgrade_contract(data);
63+
} elseif (op == OP_PARSE_PRICE_FEED_UPDATES) {
64+
;; @notice Parses price feed updates and returns the results to the caller
65+
;; @param msg_value - Amount of TON sent with the message (used for fee calculation)
66+
;; @param data_slice - Slice containing the price feed update data
67+
;; @param price_ids_slice - Slice containing the price IDs to filter for
68+
;; @param min_publish_time - Minimum publish time for price updates to be considered
69+
;; @param max_publish_time - Maximum publish time for price updates to be considered
70+
;; @param sender_address - Address of the sender (for response)
71+
;; @param target_address - Address to send the response to
72+
;; @param custom_payload - Custom payload to include in the response
73+
cell price_ids_cell = in_msg_body~load_ref();
74+
slice price_ids_slice = price_ids_cell.begin_parse();
75+
int min_publish_time = in_msg_body~load_uint(64);
76+
int max_publish_time = in_msg_body~load_uint(64);
77+
slice target_address = in_msg_body~load_msg_addr();
78+
cell custom_payload_cell = in_msg_body~load_ref();
79+
slice custom_payload = custom_payload_cell.begin_parse();
80+
parse_price_feed_updates(msg_value, data_slice, price_ids_slice, min_publish_time, max_publish_time, sender_address, target_address, custom_payload);
81+
} elseif (op == OP_PARSE_UNIQUE_PRICE_FEED_UPDATES) {
82+
;; @notice Parses unique price feed updates (only the latest for each price ID) and returns the results to the caller
83+
;; @param msg_val; @notice Parses unique price feed updates (only the latest for each price ID) and returns the results to the caller
84+
;; @param msg_value - Amount of TON sent with the message (used for fee calculation)
85+
;; @param data_slice - Slice containing the price feed update data
86+
;; @param price_ids_slice - Slice containing the price IDs to filter for
87+
;; @param publish_time - Target publish time for price updates
88+
;; @param max_staleness - Maximum allowed staleness of price updates (in seconds)
89+
;; @param sender_address - Address of the sender (for response)
90+
;; @param target_address - Address to send the response to
91+
;; @param custom_payload - Custom payload to include in the response
92+
cell price_ids_cell = in_msg_body~load_ref();
93+
slice price_ids_slice = price_ids_cell.begin_parse();
94+
int publish_time = in_msg_body~load_uint(64);
95+
int max_staleness = in_msg_body~load_uint(64);
96+
slice target_address = in_msg_body~load_msg_addr();
97+
cell custom_payload_cell = in_msg_body~load_ref();
98+
slice custom_payload = custom_payload_cell.begin_parse();
99+
parse_unique_price_feed_updates(msg_value, data_slice, price_ids_slice, publish_time, max_staleness, sender_address, target_address, custom_payload);
100+
} else {
101+
throw(0xffff); ;; Throw exception for unknown op
102+
}
103+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{-
2+
This test contract serves two main purposes:
3+
1. It allows testing of non-getter functions in FunC without requiring specific opcodes for each function.
4+
2. It provides access to internal functions through wrapper getter functions.
5+
6+
This approach is common in FunC development, where a separate test contract is used for unit testing.
7+
It enables more comprehensive testing of the contract's functionality, including internal operations
8+
that are not directly accessible through standard getter methods.
9+
-}
10+
{-
11+
The only difference from the Main.fc is that it uses patched Pyth functions,
12+
which don't verify prices sources and allow to run tests with locally generated prices.
13+
-}
14+
#include "imports/stdlib.fc";
15+
#include "tests/PythNoCheck.fc";
16+
#include "Wormhole.fc";
17+
#include "common/op.fc";
18+
19+
() recv_internal(int balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
20+
if (in_msg_body.slice_empty?()) {
21+
return ();
22+
}
23+
24+
;; Get sender address from message
25+
slice cs = in_msg_full.begin_parse();
26+
int flags = cs~load_uint(4); ;; skip flags
27+
if (flags & 1) {
28+
return ();
29+
}
30+
slice sender_address = cs~load_msg_addr(); ;; load sender address
31+
32+
int op = in_msg_body~load_uint(32);
33+
cell data = in_msg_body~load_ref();
34+
slice data_slice = data.begin_parse();
35+
36+
if (op == OP_UPDATE_GUARDIAN_SET) {
37+
update_guardian_set(data_slice);
38+
} elseif (op == OP_UPDATE_PRICE_FEEDS) {
39+
update_price_feeds(msg_value, data_slice);
40+
} elseif (op == OP_EXECUTE_GOVERNANCE_ACTION) {
41+
execute_governance_action(data_slice);
42+
} elseif (op == OP_UPGRADE_CONTRACT) {
43+
execute_upgrade_contract(data);
44+
} elseif (op == OP_PARSE_PRICE_FEED_UPDATES) {
45+
cell price_ids_cell = in_msg_body~load_ref();
46+
slice price_ids_slice = price_ids_cell.begin_parse();
47+
int min_publish_time = in_msg_body~load_uint(64);
48+
int max_publish_time = in_msg_body~load_uint(64);
49+
slice target_address = in_msg_body~load_msg_addr();
50+
cell custom_payload_cell = in_msg_body~load_ref();
51+
slice custom_payload = custom_payload_cell.begin_parse();
52+
parse_price_feed_updates(msg_value, data_slice, price_ids_slice, min_publish_time, max_publish_time, sender_address, target_address, custom_payload);
53+
} elseif (op == OP_PARSE_UNIQUE_PRICE_FEED_UPDATES) {
54+
cell price_ids_cell = in_msg_body~load_ref();
55+
slice price_ids_slice = price_ids_cell.begin_parse();
56+
int publish_time = in_msg_body~load_uint(64);
57+
int max_staleness = in_msg_body~load_uint(64);
58+
slice target_address = in_msg_body~load_msg_addr();
59+
cell custom_payload_cell = in_msg_body~load_ref();
60+
slice custom_payload = custom_payload_cell.begin_parse();
61+
parse_unique_price_feed_updates(msg_value, data_slice, price_ids_slice, publish_time, max_staleness, sender_address, target_address, custom_payload);
62+
} else {
63+
throw(0xffff); ;; Throw exception for unknown op
64+
}
65+
}

0 commit comments

Comments
 (0)