Skip to content
Merged
5 changes: 1 addition & 4 deletions target_chains/ton/contracts/contracts/Main.fc
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
#include "imports/stdlib.fc";
#include "common/errors.fc";
#include "common/storage.fc";
#include "common/op.fc";
#include "Wormhole.fc";
#include "Pyth.fc";

;; Opcodes
const int OP_UPDATE_GUARDIAN_SET = 1;
const int OP_EXECUTE_GOVERNANCE_ACTION = 2;

;; Internal message handler
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) { ;; ignore empty messages
Expand Down
156 changes: 151 additions & 5 deletions target_chains/ton/contracts/contracts/Pyth.fc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include "common/utils.fc";
#include "common/constants.fc";
#include "common/merkle_tree.fc";
#include "common/governance_actions.fc";
#include "common/gas.fc";
#include "./Wormhole.fc";

cell store_price(int price, int conf, int expo, int publish_time) {
Expand Down Expand Up @@ -59,7 +61,7 @@ slice read_and_verify_header(slice data) {
return (price, conf, expo, publish_time);
}

(int) get_update_fee(slice data) method_id {
int get_update_fee(slice data) method_id {
load_data();
slice cs = read_and_verify_header(data);
int wormhole_proof_size_bytes = cs~load_uint(16);
Expand All @@ -68,6 +70,11 @@ slice read_and_verify_header(slice data) {
return single_update_fee * num_updates;
}

int get_single_update_fee() method_id {
load_data();
return single_update_fee;
}

int get_governance_data_source_index() method_id {
load_data();
return governance_data_source_index;
Expand All @@ -83,6 +90,17 @@ int get_last_executed_governance_sequence() method_id {
return last_executed_governance_sequence;
}

int get_is_valid_data_source(cell data_source) method_id {
load_data();
int data_source_key = cell_hash(data_source);
(slice value, int found?) = is_valid_data_source.udict_get?(256, data_source_key);
if (found?) {
return value~load_int(1);
} else {
return 0;
}
}

(int, int, int, int) get_price_unsafe(int price_feed_id) method_id {
load_data();
(slice result, int success) = latest_price_feeds.udict_get?(256, price_feed_id);
Expand Down Expand Up @@ -148,10 +166,13 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
cs = new_cs;

int num_updates = cs~load_uint(8);
int fee = single_update_fee * num_updates;
int update_fee = single_update_fee * num_updates;
int compute_fee = get_compute_fee(WORKCHAIN, UPDATE_PRICE_FEEDS_GAS);
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 fee
throw_unless(ERROR_INSUFFICIENT_FEE, msg_value >= fee);
;; Check if the sender has sent enough TON to cover the update_fee
throw_unless(ERROR_INSUFFICIENT_FEE, remaining_msg_value >= update_fee);

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

Expand Down Expand Up @@ -203,6 +224,131 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
store_data();
}

() execute_governance_action(slice in_msg_body) impure {
() verify_governance_vm(int emitter_chain_id, int emitter_address, int sequence) impure {
(int gov_chain_id, int gov_address) = parse_data_source(governance_data_source);
throw_unless(ERROR_INVALID_GOVERNANCE_DATA_SOURCE, emitter_chain_id == gov_chain_id);
throw_unless(ERROR_INVALID_GOVERNANCE_DATA_SOURCE, emitter_address == gov_address);
throw_if(ERROR_OLD_GOVERNANCE_MESSAGE, sequence <= last_executed_governance_sequence);
last_executed_governance_sequence = sequence;
}

(int, int, slice) parse_governance_instruction(slice payload) {
int magic = payload~load_uint(32);
throw_unless(ERROR_INVALID_GOVERNANCE_MAGIC, magic == GOVERNANCE_MAGIC);

int module = payload~load_uint(8);
throw_unless(ERROR_INVALID_GOVERNANCE_MODULE, module == GOVERNANCE_MODULE);

int action = payload~load_uint(8);

int target_chain_id = payload~load_uint(16);

return (target_chain_id, action, payload);
}

int apply_decimal_expo(int value, int expo) {
int result = value;
repeat (expo) {
result *= 10;
}
return result;
}

() execute_upgrade_contract(slice payload) impure {
;; TODO: Implement
}
Comment on lines +257 to 259
Copy link
Contributor Author

Choose a reason for hiding this comment

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

will implement in a following PR


() execute_authorize_governance_data_source_transfer(slice payload) impure {
;; Verify the claim VAA
(_, _, _, _, int emitter_chain_id, int emitter_address, int sequence, _, slice claim_payload, _) = parse_and_verify_wormhole_vm(payload);

;; Parse the claim payload
(int target_chain_id, int action, slice claim_payload) = parse_governance_instruction(claim_payload);

;; Verify that this is a valid governance action for this chain
throw_if(ERROR_INVALID_GOVERNANCE_TARGET, (target_chain_id != 0) & (target_chain_id != chain_id));
throw_unless(ERROR_INVALID_GOVERNANCE_ACTION, action == REQUEST_GOVERNANCE_DATA_SOURCE_TRANSFER);

;; Extract the new governance data source index from the claim payload
int new_governance_data_source_index = claim_payload~load_uint(32);

;; Verify that the new index is greater than the current index
int current_index = governance_data_source_index;
throw_if(ERROR_OLD_GOVERNANCE_MESSAGE, new_governance_data_source_index <= current_index);

;; Update the governance data source
governance_data_source = begin_cell()
.store_uint(emitter_chain_id, 16)
.store_uint(emitter_address, 256)
.end_cell();

governance_data_source_index = new_governance_data_source_index;

;; Update the last executed governance sequence
last_executed_governance_sequence = sequence;
}

() execute_set_data_sources(slice payload) impure {
int num_sources = payload~load_uint(8);
cell new_data_sources = new_dict();

repeat(num_sources) {
(cell data_source, slice new_payload) = read_and_store_large_data(payload, 272); ;; 272 = 256 + 16
payload = new_payload;
slice data_source_slice = data_source.begin_parse();
int emitter_chain_id = data_source_slice~load_uint(16);
int emitter_address = data_source_slice~load_uint(256);
cell data_source = begin_cell()
.store_uint(emitter_chain_id, 16)
.store_uint(emitter_address, 256)
.end_cell();
int data_source_key = cell_hash(data_source);
new_data_sources~udict_set(256, data_source_key, begin_cell().store_int(true, 1).end_cell().begin_parse());
}

is_valid_data_source = new_data_sources;
}

() execute_set_fee(slice payload) impure {
int value = payload~load_uint(64);
int expo = payload~load_uint(64);
int new_fee = apply_decimal_expo(value, expo);
single_update_fee = new_fee;
}

() execute_governance_payload(int action, slice payload) impure {
if (action == UPGRADE_CONTRACT) {
execute_upgrade_contract(payload);
} elseif (action == AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER) {
execute_authorize_governance_data_source_transfer(payload);
} elseif (action == SET_DATA_SOURCES) {
execute_set_data_sources(payload);
} elseif (action == SET_FEE) {
execute_set_fee(payload);
} elseif (action == SET_VALID_PERIOD) {
;; Unsupported governance action
throw(ERROR_INVALID_GOVERNANCE_ACTION);
} elseif (action == REQUEST_GOVERNANCE_DATA_SOURCE_TRANSFER) {
;; RequestGovernanceDataSourceTransfer can only be part of
;; AuthorizeGovernanceDataSourceTransfer message
throw(ERROR_INVALID_GOVERNANCE_ACTION);
} else {
throw(ERROR_INVALID_GOVERNANCE_ACTION);
}
}

() execute_governance_action(slice in_msg_body) impure {
load_data();

(_, _, _, _, int emitter_chain_id, int emitter_address, int sequence, _, slice payload, _) = parse_and_verify_wormhole_vm(in_msg_body);

verify_governance_vm(emitter_chain_id, emitter_address, sequence);

(int target_chain_id, int action, slice payload) = parse_governance_instruction(payload);

throw_if(ERROR_INVALID_GOVERNANCE_TARGET, (target_chain_id != 0) & (target_chain_id != chain_id));

execute_governance_payload(action, payload);

store_data();
}
18 changes: 17 additions & 1 deletion target_chains/ton/contracts/contracts/common/constants.fc
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
const int ACCUMULATOR_MAGIC = 0x504e4155; ;; "PNAU" (Pyth Network Accumulator Update)
const int ACCUMULATOR_WORMHOLE_MAGIC = 0x41555756; ;; Stands for AUWV (Accumulator Update Wormhole Verficiation)
const int ACCUMULATOR_WORMHOLE_MAGIC = 0x41555756; ;; "AUWV" (Accumulator Update Wormhole Verficiation)
const int GOVERNANCE_MAGIC = 0x5054474d; ;; "PTGM" (Pyth Governance Message)
const int GOVERNANCE_MODULE = 1;
const int MAJOR_VERSION = 1;
const int MINIMUM_ALLOWED_MINOR_VERSION = 0;

const int GUARDIAN_SET_EXPIRY = 86400; ;; 1 day in seconds
const int UPGRADE_MODULE = 0x0000000000000000000000000000000000000000000000000000000000436f7265; ;; "Core" (left-padded to 256 bits) in hex

const int WORMHOLE_MERKLE_UPDATE_TYPE = 0;

{-
The main workchain ID in TON. Currently, TON has two blockchains:
1. Masterchain: Used for system-level operations and consensus.
2. Basechain/Workchain: The primary chain for user accounts and smart contracts.

While TON supports up to 2^32 workchains, currently only Workchain 0 is active.
This constant defines the default workchain for smart contract deployment and interactions.

Note: Gas costs differ between chains:
- Basechain: 1 gas = 400 nanotons = 0.0000004 TON
- Masterchain: 1 gas = 10000 nanotons = 0.00001 TON (25x more expensive)
-}
const int WORKCHAIN = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this?

Copy link
Contributor Author

@cctdaniel cctdaniel Sep 12, 2024

Choose a reason for hiding this comment

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

there are 2 blockchains on TON: https://docs.ton.org/learn/overviews/ton-blockchain#blockchain
and they have different costs depending on the chains: https://docs.ton.org/develop/smart-contracts/fees#gas

will add comment to the file to make it clearer

8 changes: 8 additions & 0 deletions target_chains/ton/contracts/contracts/common/errors.fc
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ const int ERROR_INVALID_UPDATE_DATA_TYPE = 1028;
const int ERROR_INVALID_MESSAGE_TYPE = 1029;
const int ERROR_INSUFFICIENT_FEE = 1030;
const int ERROR_INVALID_PROOF_SIZE = 1031;
const int ERROR_INVALID_GOVERNANCE_DATA_SOURCE = 1032;
const int ERROR_OLD_GOVERNANCE_MESSAGE = 1033;
const int ERROR_INVALID_GOVERNANCE_TARGET = 1034;
const int ERROR_INVALID_GOVERNANCE_MAGIC = 1035;
const int ERROR_INVALID_GOVERNANCE_MODULE = 1036;

;; Common
const int ERROR_INSUFFICIENT_GAS = 1037;
4 changes: 4 additions & 0 deletions target_chains/ton/contracts/contracts/common/gas.fc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
int get_compute_fee(int workchain, int gas_used) asm(gas_used workchain) "GETGASFEE";

;; The actual gas used for the transaction is 350166 but we add ~10% (385182.6) and round up (390000) to be on the safe side because the amount of gas used can vary based on the current state of the blockchain
const int UPDATE_PRICE_FEEDS_GAS = 390000;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

calculated by following the best practice suggested here which is to run tests and get the actual gas_used amount

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const int UPGRADE_CONTRACT = 0;
const int AUTHORIZE_GOVERNANCE_DATA_SOURCE_TRANSFER = 1;
const int SET_DATA_SOURCES = 2;
const int SET_FEE = 3;
const int SET_VALID_PERIOD = 4;
const int REQUEST_GOVERNANCE_DATA_SOURCE_TRANSFER = 5;
1 change: 1 addition & 0 deletions target_chains/ton/contracts/contracts/common/op.fc
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
const int OP_UPDATE_GUARDIAN_SET = 1;
const int OP_UPDATE_PRICE_FEEDS = 2;
const int OP_EXECUTE_GOVERNANCE_ACTION = 3;
10 changes: 10 additions & 0 deletions target_chains/ton/contracts/contracts/tests/PythTest.fc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
update_guardian_set(data.begin_parse());
} elseif (op == OP_UPDATE_PRICE_FEEDS) {
update_price_feeds(msg_value, data.begin_parse());
} elseif (op == OP_EXECUTE_GOVERNANCE_ACTION) {
execute_governance_action(data.begin_parse());
} else {
throw(0xffff); ;; Throw exception for unknown op
}
Expand All @@ -49,6 +51,10 @@
return get_update_fee(in_msg_body);
}

(int) test_get_single_update_fee() method_id {
return get_single_update_fee();
}

(int) test_get_chain_id() method_id {
return get_chain_id();
}
Expand All @@ -64,3 +70,7 @@
(cell) test_get_governance_data_source() method_id {
return get_governance_data_source();
}

(int) test_get_is_valid_data_source(cell data_source) method_id {
return get_is_valid_data_source(data_source);
}
Loading
Loading