;; Simple wallet smart contract _ 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.end_parse(); return res; } _ pack_state(cell pending_queries, cell owner_infos, int last_cleaned, int k, int n, int wallet_id) 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) .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, 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); } 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) = unpack_state(); throw_if(37, last_cleaned); accept_message(); set_data(pack_state(pending_queries, owner_infos, 1, k, n, wallet_id)); } (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(); while (msg.slice_refs()) { var mode = msg~load_uint(8); send_raw_message(msg~load_ref(), 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) = unpack_state(); 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)); 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)); } } ;; 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) method_id { return pack_state(new_dict(), owners_info, 0, k, n, wallet_id); } 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); } 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(); }