Skip to content

Commit de25f58

Browse files
authored
fix(target_chains/ton): audit fixes (#2092)
* use saturating sub * use saturating sub for ema price * validate data sources length * use the right error type * fix magic number * add impure to functions that throw * dont redefine global vars * use global var directly * remove unused variables * remove debugging code * remove unused variable num_data_sources * remove unused variable data_sources
1 parent cd67cd8 commit de25f58

File tree

8 files changed

+21
-57
lines changed

8 files changed

+21
-57
lines changed

target_chains/ton/contracts/contracts/Pyth.fc

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ cell store_price(int price, int conf, int expo, int publish_time) {
1717
.end_cell();
1818
}
1919

20-
slice read_and_verify_header(slice data) {
20+
slice read_and_verify_header(slice data) impure {
2121
int magic = data~load_uint(32);
2222
throw_unless(ERROR_INVALID_MAGIC, magic == ACCUMULATOR_MAGIC);
2323
int major_version = data~load_uint(8);
@@ -39,7 +39,7 @@ slice read_and_verify_header(slice data) {
3939
slice cs = read_and_verify_proof(root_digest, message, cs);
4040

4141
int message_type = message~load_uint(8);
42-
throw_unless(ERROR_INVALID_MESSAGE_TYPE, message_type == 0); ;; 0 corresponds to PriceFeed
42+
throw_unless(ERROR_INVALID_MESSAGE_TYPE, message_type == PRICE_FEED_MESSAGE_TYPE);
4343

4444
int price_id = message~load_uint(256);
4545
int price = message~load_int(64);
@@ -80,11 +80,6 @@ int get_governance_data_source_index() method_id {
8080
return governance_data_source_index;
8181
}
8282

83-
cell get_data_sources() method_id {
84-
load_data();
85-
return data_sources;
86-
}
87-
8883
cell get_governance_data_source() method_id {
8984
load_data();
9085
return governance_data_source;
@@ -119,7 +114,7 @@ int get_is_valid_data_source(cell data_source) method_id {
119114
load_data();
120115
(int price, int conf, int expo, int publish_time) = get_price_unsafe(price_feed_id);
121116
int current_time = now();
122-
throw_if(ERROR_OUTDATED_PRICE, current_time - publish_time > time_period);
117+
throw_if(ERROR_OUTDATED_PRICE, max(0, current_time - publish_time) > time_period);
123118
return (price, conf, expo, publish_time);
124119
}
125120

@@ -137,7 +132,7 @@ int get_is_valid_data_source(cell data_source) method_id {
137132
load_data();
138133
(int price, int conf, int expo, int publish_time) = get_ema_price_unsafe(price_feed_id);
139134
int current_time = now();
140-
throw_if(ERROR_OUTDATED_PRICE, current_time - publish_time > time_period);
135+
throw_if(ERROR_OUTDATED_PRICE, max(0, current_time - publish_time) > time_period);
141136
return (price, conf, expo, publish_time);
142137
}
143138

@@ -202,7 +197,7 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
202197
int root_digest = parse_pyth_payload_in_wormhole_vm(payload);
203198

204199
repeat(num_updates) {
205-
(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);
200+
(int price_id, int price, int conf, int expo, int publish_time, _, int ema_price, int ema_conf, slice new_cs) = read_and_verify_message(cs, root_digest);
206201
cs = new_cs;
207202

208203
(slice latest_price_info, int found?) = latest_price_feeds.udict_get?(256, price_id);
@@ -240,7 +235,7 @@ int parse_pyth_payload_in_wormhole_vm(slice payload) impure {
240235
last_executed_governance_sequence = sequence;
241236
}
242237

243-
(int, int, slice) parse_governance_instruction(slice payload) {
238+
(int, int, slice) parse_governance_instruction(slice payload) impure {
244239
int magic = payload~load_uint(32);
245240
throw_unless(ERROR_INVALID_GOVERNANCE_MAGIC, magic == GOVERNANCE_MAGIC);
246241

@@ -334,6 +329,9 @@ int apply_decimal_expo(int value, int expo) {
334329
new_data_sources~udict_set(256, data_source_key, begin_cell().store_int(true, 1).end_cell().begin_parse());
335330
}
336331

332+
;; Verify that all data in the payload was processed
333+
throw_unless(ERROR_INVALID_PAYLOAD_LENGTH, payload.slice_empty?());
334+
337335
is_valid_data_source = new_data_sources;
338336
}
339337

target_chains/ton/contracts/contracts/Wormhole.fc

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,6 @@
1414
"NULLSWAPIFNOT" ;; If recovery failed, insert null under the top of the stack
1515
"NULLSWAPIFNOT2"; ;; If recovery failed, insert two more nulls under the top of the stack
1616

17-
;; For troubleshooting purposes
18-
() dump_guardian_sets(cell keys_dict) impure {
19-
int key = -1;
20-
do {
21-
(key, slice value, int found) = keys_dict.udict_get_next?(32, key);
22-
if (found) {
23-
~dump(key);
24-
~dump(value);
25-
}
26-
} until (~ found);
27-
}
28-
29-
3017
;; Internal helper methods
3118
(int, cell, int) parse_guardian_set(slice guardian_set) {
3219
slice cs = guardian_set~load_ref().begin_parse();
@@ -130,7 +117,7 @@ int governance_action_is_consumed(int hash) method_id {
130117
load_data();
131118
;; Parse VM fields
132119
int version = in_msg_body~load_uint(8);
133-
throw_unless(ERROR_INVALID_VERSION, version == 1);
120+
throw_unless(ERROR_INVALID_VERSION, version == WORMHOLE_VM_VERSION);
134121
int vm_guardian_set_index = in_msg_body~load_uint(32);
135122
;; Verify and check if guardian set is valid
136123
(int expiration_time, cell keys_dict, int key_count) = get_guardian_set_internal(vm_guardian_set_index);
@@ -189,16 +176,16 @@ int governance_action_is_consumed(int hash) method_id {
189176
);
190177
}
191178

192-
(int, int, int, cell, int) parse_encoded_upgrade(int current_guardian_set_index, slice payload) impure {
179+
(int, int, int, cell, int) parse_encoded_upgrade(int guardian_set_index, slice payload) impure {
193180
int module = payload~load_uint(256);
194181
throw_unless(ERROR_INVALID_MODULE, module == UPGRADE_MODULE);
195182

196183
int action = payload~load_uint(8);
197-
throw_unless(ERROR_INVALID_GOVERNANCE_ACTION, action == 2);
184+
throw_unless(ERROR_INVALID_GOVERNANCE_ACTION, action == GUARDIAN_SET_UPGRADE_ACTION);
198185

199186
int chain = payload~load_uint(16);
200187
int new_guardian_set_index = payload~load_uint(32);
201-
throw_unless(ERROR_NEW_GUARDIAN_SET_INDEX_IS_INVALID, new_guardian_set_index == (current_guardian_set_index + 1));
188+
throw_unless(ERROR_NEW_GUARDIAN_SET_INDEX_IS_INVALID, new_guardian_set_index == (guardian_set_index + 1));
202189

203190
int guardian_length = payload~load_uint(8);
204191
throw_unless(ERROR_INVALID_GUARDIAN_SET_KEYS_LENGTH, guardian_length > 0);
@@ -232,10 +219,8 @@ int governance_action_is_consumed(int hash) method_id {
232219
(int version, int vm_guardian_set_index, int timestamp, int nonce, int emitter_chain_id, int emitter_address, int sequence, int consistency_level, slice payload, int hash) = parse_and_verify_wormhole_vm(in_msg_body);
233220

234221
;; Verify the emitter chain and address
235-
int governance_chain_id = get_governance_chain_id();
236222
throw_unless(ERROR_INVALID_GOVERNANCE_CHAIN, emitter_chain_id == governance_chain_id);
237-
int governance_contract_address = get_governance_contract();
238-
throw_unless(ERROR_INVALID_GOVERNANCE_CONTRACT, emitter_address == governance_contract_address);
223+
throw_unless(ERROR_INVALID_GOVERNANCE_CONTRACT, emitter_address == governance_contract);
239224

240225
;; Check if the governance action has already been consumed
241226
throw_if(ERROR_GOVERNANCE_ACTION_ALREADY_CONSUMED, governance_action_is_consumed(hash));

target_chains/ton/contracts/contracts/common/constants.fc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ const int GOVERNANCE_MODULE = 1;
55
const int MAJOR_VERSION = 1;
66
const int MINIMUM_ALLOWED_MINOR_VERSION = 0;
77

8+
const int WORMHOLE_VM_VERSION = 1;
9+
810
const int GUARDIAN_SET_EXPIRY = 86400; ;; 1 day in seconds
911
const int UPGRADE_MODULE = 0x0000000000000000000000000000000000000000000000000000000000436f7265; ;; "Core" (left-padded to 256 bits) in hex
12+
const int GUARDIAN_SET_UPGRADE_ACTION = 2;
1013

1114
const int WORMHOLE_MERKLE_UPDATE_TYPE = 0;
1215

16+
const int PRICE_FEED_MESSAGE_TYPE = 0;
17+
1318
{-
1419
The main workchain ID in TON. Currently, TON has two blockchains:
1520
1. Masterchain: Used for system-level operations and consensus.

target_chains/ton/contracts/contracts/common/errors.fc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const int ERROR_INVALID_GOVERNANCE_TARGET = 2015;
4242
const int ERROR_INVALID_GOVERNANCE_MAGIC = 2016;
4343
const int ERROR_INVALID_GOVERNANCE_MODULE = 2017;
4444
const int ERROR_INVALID_CODE_HASH = 2018;
45+
const int ERROR_INVALID_PAYLOAD_LENGTH = 2019;
4546

4647
;; Common
4748
const int ERROR_INSUFFICIENT_GAS = 3000;

target_chains/ton/contracts/contracts/common/storage.fc

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ global int single_update_fee;
88
;; DataSource struct: (emitter_chain_id: int, emitter_address: int)
99
;; emitter_chain_id is a 16-bit unsigned integer
1010
;; emitter_address is a 256-bit unsigned integer
11-
global cell data_sources; ;; Dictionary of DataSource tuples, keyed by u8
12-
global int num_data_sources;
1311
global cell is_valid_data_source; ;; Dictionary of int (0 as false, -1 as true), keyed by DataSource cell_hash
1412
global int upgrade_code_hash; ;; 256-bit unsigned integer
1513

@@ -37,8 +35,6 @@ global int governance_data_source_index; ;; u32
3735
.end_cell();
3836

3937
cell data_sources_cell = begin_cell()
40-
.store_dict(data_sources)
41-
.store_uint(num_data_sources, 32)
4238
.store_dict(is_valid_data_source)
4339
.end_cell();
4440

@@ -78,8 +74,6 @@ global int governance_data_source_index; ;; u32
7874

7975
cell data_sources_cell = ds~load_ref();
8076
slice data_sources_slice = data_sources_cell.begin_parse();
81-
data_sources = data_sources_slice~load_dict();
82-
num_data_sources = data_sources_slice~load_uint(32);
8377
is_valid_data_source = data_sources_slice~load_dict();
8478

8579
cell guardian_set_cell = ds~load_ref();

target_chains/ton/contracts/contracts/tests/PythTest.fc

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,3 @@
7777
(int) test_get_is_valid_data_source(cell data_source) method_id {
7878
return get_is_valid_data_source(data_source);
7979
}
80-
81-
(cell) test_get_data_sources() method_id {
82-
return get_data_sources();
83-
}

target_chains/ton/contracts/tests/PythTest.spec.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -974,11 +974,4 @@ describe("PythTest", () => {
974974
// Verify that the contract has not been upgraded by attempting to call the new method
975975
await expect(pythTest.getNewFunction()).rejects.toThrow();
976976
});
977-
978-
it("should correctly get data sources", async () => {
979-
await deployContract();
980-
981-
const dataSources = await pythTest.getDataSources();
982-
expect(dataSources).toEqual(DATA_SOURCES);
983-
});
984977
});

target_chains/ton/contracts/wrappers/BaseWrapper.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,24 +79,18 @@ export class BaseWrapper implements Contract {
7979
priceDict.set(BigInt(config.priceFeedId), priceFeedCell);
8080
}
8181

82-
// Create a dictionary for data sources
83-
const dataSourcesDict = Dictionary.empty(
84-
Dictionary.Keys.Uint(8),
85-
Dictionary.Values.Cell()
86-
);
8782
// Create a dictionary for valid data sources
8883
const isValidDataSourceDict = Dictionary.empty(
8984
Dictionary.Keys.BigUint(256),
9085
Dictionary.Values.Bool()
9186
);
9287

9388
if (config.dataSources) {
94-
config.dataSources.forEach((source, index) => {
89+
config.dataSources.forEach((source) => {
9590
const sourceCell = beginCell()
9691
.storeUint(source.emitterChain, 16)
9792
.storeBuffer(Buffer.from(source.emitterAddress, "hex"))
9893
.endCell();
99-
dataSourcesDict.set(index, sourceCell);
10094
const cellHash = BigInt("0x" + sourceCell.hash().toString("hex"));
10195
isValidDataSourceDict.set(cellHash, true);
10296
});
@@ -110,8 +104,6 @@ export class BaseWrapper implements Contract {
110104

111105
// Group data sources information
112106
const dataSourcesCell = beginCell()
113-
.storeDict(dataSourcesDict)
114-
.storeUint(config.dataSources ? config.dataSources.length : 0, 32)
115107
.storeDict(isValidDataSourceDict)
116108
.endCell();
117109

0 commit comments

Comments
 (0)