mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	* Add legacy_tester for existing funC contracts * Add storage-contracts and pragma options
		
			
				
	
	
		
			382 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			382 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| ;; Simple wallet smart contract
 | |
| 
 | |
| #include "stdlib.fc";
 | |
| 
 | |
| (int, int) get_bridge_config() impure inline_ref {
 | |
|   cell bridge_config = config_param(71);
 | |
|   if (bridge_config.cell_null?()) {
 | |
|     bridge_config = config_param(-71);
 | |
|   }
 | |
|   if (bridge_config.cell_null?()) {
 | |
|     return (0, 0);
 | |
|   }
 | |
|   slice ds = bridge_config.begin_parse();
 | |
|   if (ds.slice_bits() < 512) {
 | |
|     return (0, 0);
 | |
|   }
 | |
|   ;; wc always equals to -1
 | |
|   int bridge_address = ds~load_uint(256);
 | |
|   int oracles_address = ds~load_uint(256);
 | |
|   return (bridge_address, oracles_address);
 | |
| }
 | |
| 
 | |
| _ unpack_state() inline_ref {
 | |
|   var ds = begin_parse(get_data());
 | |
|   var res = (ds~load_uint(32), ds~load_uint(8), ds~load_uint(8), ds~load_uint(64), ds~load_dict(), ds~load_dict(), ds~load_uint(32));
 | |
|   ds.end_parse();
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| _ pack_state(cell pending_queries, cell owner_infos, int last_cleaned, int k, int n, int wallet_id, int spend_delay) inline_ref {
 | |
|   return begin_cell()
 | |
|     .store_uint(wallet_id, 32)
 | |
|     .store_uint(n, 8)
 | |
|     .store_uint(k, 8)
 | |
|     .store_uint(last_cleaned, 64)
 | |
|     .store_dict(owner_infos)
 | |
|     .store_dict(pending_queries)
 | |
|     .store_uint(spend_delay,32)
 | |
|   .end_cell();
 | |
| }
 | |
| 
 | |
| _ pack_owner_info(int public_key, int flood) inline_ref {
 | |
|   return begin_cell()
 | |
|     .store_uint(public_key, 256)
 | |
|     .store_uint(flood, 8);
 | |
| }
 | |
| 
 | |
| _ unpack_owner_info(slice cs) inline_ref {
 | |
|   return (cs~load_uint(256), cs~load_uint(8));
 | |
| }
 | |
| 
 | |
| (int, int) check_signatures(cell public_keys, cell signatures, int hash, int cnt_bits) inline_ref {
 | |
|   int cnt = 0;
 | |
| 
 | |
|   do {
 | |
|     slice cs = signatures.begin_parse();
 | |
|     slice signature = cs~load_bits(512);
 | |
| 
 | |
|     int i = cs~load_uint(8);
 | |
|     signatures = cs~load_dict();
 | |
| 
 | |
|     (slice public_key, var found?) = public_keys.udict_get?(8, i);
 | |
|     throw_unless(37, found?);
 | |
|     throw_unless(38, check_signature(hash, signature, public_key.preload_uint(256)));
 | |
| 
 | |
|     int mask = (1 << i);
 | |
|     int old_cnt_bits = cnt_bits;
 | |
|     cnt_bits |= mask;
 | |
|     int should_check = cnt_bits != old_cnt_bits;
 | |
|     cnt -= should_check;
 | |
|   } until (cell_null?(signatures));
 | |
| 
 | |
|   return (cnt, cnt_bits);
 | |
| }
 | |
| 
 | |
| () recv_internal(slice in_msg) impure {
 | |
|   ;; do nothing for internal messages
 | |
| }
 | |
| 
 | |
| (int, cell, int, int) parse_msg(slice in_msg) inline_ref {
 | |
|   int mode = in_msg~load_uint(8);
 | |
|   var msg = in_msg~load_ref();
 | |
|   var msg' = msg.begin_parse();
 | |
|   msg'~load_uint(4); ;; flags
 | |
|   msg'~load_msg_addr(); ;; src
 | |
|   (int wc, int addr) = parse_std_addr(msg'~load_msg_addr()); ;; dest
 | |
|   return (mode, msg, wc, addr);
 | |
| }
 | |
| 
 | |
| () check_proposed_query(slice in_msg) impure inline {
 | |
|   throw_unless(43, (slice_refs(in_msg) == 1) & (slice_bits(in_msg) == 8));
 | |
|   (_, _, int wc, _) = parse_msg(in_msg);
 | |
|   wc~impure_touch();
 | |
| }
 | |
| 
 | |
| (int, int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?, int root_i) inline_ref {
 | |
|   if (found?) {
 | |
|     throw_unless(35, query~load_int(1));
 | |
|     (int creator_i, int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(8), query~load_uint(n), query);
 | |
|     throw_unless(36, slice_hash(msg) == slice_hash(in_msg));
 | |
|     return (creator_i, cnt, cnt_bits, msg);
 | |
|   }
 | |
|   check_proposed_query(in_msg);
 | |
| 
 | |
|   return (root_i, 0, 0, in_msg);
 | |
| }
 | |
| 
 | |
| (cell, ()) dec_flood(cell owner_infos, int creator_i) {
 | |
|   (slice owner_info, var found?) = owner_infos.udict_get?(8, creator_i);
 | |
|   (int public_key, int flood) = unpack_owner_info(owner_info);
 | |
|   owner_infos~udict_set_builder(8, creator_i, pack_owner_info(public_key, flood - 1));
 | |
|   return (owner_infos, ());
 | |
| }
 | |
| 
 | |
| () try_init() impure inline_ref {
 | |
|   ;; first query without signatures is always accepted
 | |
|   (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries, int spend_delay) = unpack_state();
 | |
|   throw_if(37, last_cleaned);
 | |
|   accept_message();
 | |
|   set_data(pack_state(pending_queries, owner_infos, 1, k, n, wallet_id, spend_delay));
 | |
| }
 | |
| 
 | |
| (cell, cell) update_pending_queries(cell pending_queries, cell owner_infos, slice msg, int query_id, int creator_i, int cnt, int cnt_bits, int n, int k) impure inline_ref {
 | |
|   if (cnt >= k) {
 | |
|     accept_message();
 | |
|     (int bridge_address, int oracles_address) = get_bridge_config();
 | |
|     (_, int my_addr) = parse_std_addr(my_address());
 | |
|     var (mode, msg', wc, addr) = parse_msg(msg);
 | |
|     if ( ((wc == -1) & (addr == bridge_address)) | (oracles_address != my_addr) ) {
 | |
|        send_raw_message(msg', mode);
 | |
|     }
 | |
|     pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1));
 | |
|     owner_infos~dec_flood(creator_i);
 | |
|   } else {
 | |
|     pending_queries~udict_set_builder(64, query_id, begin_cell()
 | |
|       .store_uint(1, 1)
 | |
|       .store_uint(creator_i, 8)
 | |
|       .store_uint(cnt, 8)
 | |
|       .store_uint(cnt_bits, n)
 | |
|       .store_slice(msg));
 | |
|   }
 | |
|   return (pending_queries, owner_infos);
 | |
| }
 | |
| 
 | |
| (int, int) calc_boc_size(int cells, int bits, slice root) {
 | |
|   cells += 1;
 | |
|   bits += root.slice_bits();
 | |
| 
 | |
|   while (root.slice_refs()) {
 | |
|     (cells, bits) = calc_boc_size(cells, bits, root~load_ref().begin_parse());
 | |
|   }
 | |
| 
 | |
|   return (cells, bits);
 | |
| }
 | |
| 
 | |
| () recv_external(slice in_msg) impure {
 | |
|   ;; empty message triggers init
 | |
|   if (slice_empty?(in_msg)) {
 | |
|     return try_init();
 | |
|   }
 | |
| 
 | |
|   ;; Check root signature
 | |
|   slice root_signature = in_msg~load_bits(512);
 | |
|   int root_hash = slice_hash(in_msg);
 | |
|   int root_i = in_msg~load_uint(8);
 | |
| 
 | |
|   (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries, int spend_delay) = unpack_state();
 | |
| 
 | |
|   throw_unless(38, now() > spend_delay);
 | |
|   last_cleaned -= last_cleaned == 0;
 | |
| 
 | |
|   (slice owner_info, var found?) = owner_infos.udict_get?(8, root_i);
 | |
|   throw_unless(31, found?);
 | |
|   (int public_key, int flood) = unpack_owner_info(owner_info);
 | |
|   throw_unless(32, check_signature(root_hash, root_signature, public_key));
 | |
| 
 | |
|   cell signatures = in_msg~load_dict();
 | |
| 
 | |
|   var hash = slice_hash(in_msg);
 | |
|   int query_wallet_id = in_msg~load_uint(32);
 | |
|   throw_unless(42, query_wallet_id == wallet_id);
 | |
| 
 | |
|   int query_id = in_msg~load_uint(64);
 | |
| 
 | |
|   (int cnt, int bits) = calc_boc_size(0, 0, in_msg);
 | |
|   throw_if(40, (cnt > 8) | (bits > 2048));
 | |
| 
 | |
|   (slice query, var found?) = pending_queries.udict_get?(64, query_id);
 | |
| 
 | |
|   ifnot (found?) {
 | |
|     flood += 1;
 | |
|     throw_if(39, flood > 10);
 | |
|   }
 | |
| 
 | |
|   var bound = (now() << 32);
 | |
|   throw_if(33, query_id < bound);
 | |
| 
 | |
|   (int creator_i, int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?, root_i);
 | |
|   int mask = 1 << root_i;
 | |
|   throw_if(34, cnt_bits & mask);
 | |
|   cnt_bits |= mask;
 | |
|   cnt += 1;
 | |
| 
 | |
|   throw_if(41, ~ found? & (cnt < k) & (bound + ((60 * 60) << 32) > query_id));
 | |
| 
 | |
|   set_gas_limit(100000);
 | |
| 
 | |
|   ifnot (found?) {
 | |
|     owner_infos~udict_set_builder(8, root_i, pack_owner_info(public_key, flood));
 | |
|   }
 | |
| 
 | |
|   (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k);
 | |
|   set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id, spend_delay));
 | |
| 
 | |
|   commit();
 | |
| 
 | |
|   int need_save = 0;
 | |
|   ifnot (cell_null?(signatures) | (cnt >= k)) {
 | |
|     (int new_cnt, cnt_bits) = check_signatures(owner_infos, signatures, hash, cnt_bits);
 | |
|     cnt += new_cnt;
 | |
|     (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k);
 | |
|     need_save = -1;
 | |
|   }
 | |
| 
 | |
|   accept_message();
 | |
|   bound -= (64 << 32);   ;; clean up records expired more than 64 seconds ago
 | |
|   int old_last_cleaned = last_cleaned;
 | |
|   do {
 | |
|     var (pending_queries', i, query, f) = pending_queries.udict_delete_get_min(64);
 | |
|     f~touch();
 | |
|     if (f) {
 | |
|       f = (i < bound);
 | |
|     }
 | |
|     if (f) {
 | |
|       if (query~load_int(1)) {
 | |
|         owner_infos~dec_flood(query~load_uint(8));
 | |
|       }
 | |
|       pending_queries = pending_queries';
 | |
|       last_cleaned = i;
 | |
|       need_save = -1;
 | |
|     }
 | |
|   } until (~ f);
 | |
| 
 | |
|   if (need_save) {
 | |
|     set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id, spend_delay));
 | |
|   }
 | |
| }
 | |
| 
 | |
| ;; Get methods
 | |
| ;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten)
 | |
| (int, int) get_query_state(int query_id) method_id {
 | |
|   (_, int n, _, int last_cleaned, _, cell pending_queries, _) = unpack_state();
 | |
|   (slice cs, var found) = pending_queries.udict_get?(64, query_id);
 | |
|   if (found) {
 | |
|     if (cs~load_int(1)) {
 | |
|       cs~load_uint(8 + 8);
 | |
|       return (0, cs~load_uint(n));
 | |
|     } else {
 | |
|       return (-1, 0);
 | |
|     }
 | |
|   }  else {
 | |
|     return (-(query_id <= last_cleaned), 0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| int processed?(int query_id) method_id {
 | |
|   (int x, _) = get_query_state(query_id);
 | |
|   return x;
 | |
| }
 | |
| 
 | |
| cell create_init_state(int wallet_id, int n, int k, cell owners_info, int spend_delay) method_id {
 | |
|   return pack_state(new_dict(), owners_info, 0, k, n, wallet_id, spend_delay);
 | |
| }
 | |
| 
 | |
| cell merge_list(cell a, cell b) {
 | |
|   if (cell_null?(a)) {
 | |
|     return b;
 | |
|   }
 | |
|   if (cell_null?(b)) {
 | |
|     return a;
 | |
|   }
 | |
|   slice as = a.begin_parse();
 | |
|   if (as.slice_refs() != 0) {
 | |
|     cell tail = merge_list(as~load_ref(), b);
 | |
|     return begin_cell().store_slice(as).store_ref(tail).end_cell();
 | |
|   }
 | |
| 
 | |
|   as~skip_last_bits(1);
 | |
|   ;; as~skip_bits(1);
 | |
|   return begin_cell().store_slice(as).store_dict(b).end_cell();
 | |
| 
 | |
| }
 | |
| 
 | |
| cell get_public_keys() method_id {
 | |
|   (_, _, _, _, cell public_keys, _, _) = unpack_state();
 | |
|   return public_keys;
 | |
| }
 | |
| 
 | |
| (int, int) check_query_signatures(cell query) method_id {
 | |
|   slice cs = query.begin_parse();
 | |
|   slice root_signature = cs~load_bits(512);
 | |
|   int root_hash = slice_hash(cs);
 | |
|   int root_i = cs~load_uint(8);
 | |
| 
 | |
|   cell public_keys = get_public_keys();
 | |
|   (slice public_key, var found?) = public_keys.udict_get?(8, root_i);
 | |
|   throw_unless(31, found?);
 | |
|   throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256)));
 | |
| 
 | |
|   int mask = 1 << root_i;
 | |
| 
 | |
|   cell signatures = cs~load_dict();
 | |
|   if (cell_null?(signatures)) {
 | |
|     return (1, mask);
 | |
|   }
 | |
|   (int cnt, mask) = check_signatures(public_keys, signatures, slice_hash(cs), mask);
 | |
|   return (cnt + 1, mask);
 | |
| }
 | |
| 
 | |
| int message_signed_by_id?(int id, int query_id) method_id {
 | |
|   (_, int n, _, _, _, cell pending_queries, _) = unpack_state();
 | |
|   (var cs, var f) = pending_queries.udict_get?(64, query_id);
 | |
|   if (f) {
 | |
|     if (cs~load_int(1)) {
 | |
|       int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n);
 | |
|       if (cnt_bits & (1 << id)) {
 | |
|         return -1;
 | |
|       }
 | |
|       return 0;
 | |
|     }
 | |
|     return -1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| cell messages_by_mask(int mask) method_id {
 | |
|   (_, int n, _, _, _, cell pending_queries, _) = unpack_state();
 | |
|   int i = -1;
 | |
|   cell a = new_dict();
 | |
|   do {
 | |
|     (i, var cs, var f) = pending_queries.udict_get_next?(64, i);
 | |
|     if (f) {
 | |
|       if (cs~load_int(1)) {
 | |
|         int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n);
 | |
|         if (cnt_bits & mask) {
 | |
|           a~udict_set_builder(64, i, begin_cell().store_slice(cs));
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } until (~ f);
 | |
|   return a;
 | |
| }
 | |
| 
 | |
| cell get_messages_unsigned_by_id(int id) method_id {
 | |
|   return messages_by_mask(1 << id);
 | |
| }
 | |
| 
 | |
| cell get_messages_unsigned() method_id {
 | |
|   return messages_by_mask(~ 0);
 | |
| }
 | |
| 
 | |
| (int, int) get_n_k() method_id {
 | |
|   (_, int n, int k, _, _, _, _) = unpack_state();
 | |
|   return (n, k);
 | |
| }
 | |
| 
 | |
| cell merge_inner_queries(cell a, cell b) method_id {
 | |
|   slice ca = a.begin_parse();
 | |
|   slice cb = b.begin_parse();
 | |
|   cell list_a = ca~load_dict();
 | |
|   cell list_b = cb~load_dict();
 | |
|   throw_unless(31, slice_hash(ca) == slice_hash(cb));
 | |
|   return begin_cell()
 | |
|     .store_dict(merge_list(list_a, list_b))
 | |
|     .store_slice(ca)
 | |
|   .end_cell();
 | |
| }
 | |
| 
 | |
| int get_lock_timeout() method_id {
 | |
|   (_, _, _, _, _, _, int spend_delay) = unpack_state();
 | |
|   return spend_delay;
 | |
| }
 |