Ton
382 строки · 11.1 Кб
1;; Simple wallet smart contract
2
3#include "stdlib.fc";
4
5(int, int) get_bridge_config() impure inline_ref {
6cell bridge_config = config_param(71);
7if (bridge_config.cell_null?()) {
8bridge_config = config_param(-71);
9}
10if (bridge_config.cell_null?()) {
11return (0, 0);
12}
13slice ds = bridge_config.begin_parse();
14if (ds.slice_bits() < 512) {
15return (0, 0);
16}
17;; wc always equals to -1
18int bridge_address = ds~load_uint(256);
19int oracles_address = ds~load_uint(256);
20return (bridge_address, oracles_address);
21}
22
23_ unpack_state() inline_ref {
24var ds = begin_parse(get_data());
25var 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));
26ds.end_parse();
27return res;
28}
29
30_ pack_state(cell pending_queries, cell owner_infos, int last_cleaned, int k, int n, int wallet_id, int spend_delay) inline_ref {
31return begin_cell()
32.store_uint(wallet_id, 32)
33.store_uint(n, 8)
34.store_uint(k, 8)
35.store_uint(last_cleaned, 64)
36.store_dict(owner_infos)
37.store_dict(pending_queries)
38.store_uint(spend_delay,32)
39.end_cell();
40}
41
42_ pack_owner_info(int public_key, int flood) inline_ref {
43return begin_cell()
44.store_uint(public_key, 256)
45.store_uint(flood, 8);
46}
47
48_ unpack_owner_info(slice cs) inline_ref {
49return (cs~load_uint(256), cs~load_uint(8));
50}
51
52(int, int) check_signatures(cell public_keys, cell signatures, int hash, int cnt_bits) inline_ref {
53int cnt = 0;
54
55do {
56slice cs = signatures.begin_parse();
57slice signature = cs~load_bits(512);
58
59int i = cs~load_uint(8);
60signatures = cs~load_dict();
61
62(slice public_key, var found?) = public_keys.udict_get?(8, i);
63throw_unless(37, found?);
64throw_unless(38, check_signature(hash, signature, public_key.preload_uint(256)));
65
66int mask = (1 << i);
67int old_cnt_bits = cnt_bits;
68cnt_bits |= mask;
69int should_check = cnt_bits != old_cnt_bits;
70cnt -= should_check;
71} until (cell_null?(signatures));
72
73return (cnt, cnt_bits);
74}
75
76() recv_internal(slice in_msg) impure {
77;; do nothing for internal messages
78}
79
80(int, cell, int, int) parse_msg(slice in_msg) inline_ref {
81int mode = in_msg~load_uint(8);
82var msg = in_msg~load_ref();
83var msg' = msg.begin_parse();
84msg'~load_uint(4); ;; flags
85msg'~load_msg_addr(); ;; src
86(int wc, int addr) = parse_std_addr(msg'~load_msg_addr()); ;; dest
87return (mode, msg, wc, addr);
88}
89
90() check_proposed_query(slice in_msg) impure inline {
91throw_unless(43, (slice_refs(in_msg) == 1) & (slice_bits(in_msg) == 8));
92(_, _, int wc, _) = parse_msg(in_msg);
93wc~impure_touch();
94}
95
96(int, int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?, int root_i) inline_ref {
97if (found?) {
98throw_unless(35, query~load_int(1));
99(int creator_i, int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(8), query~load_uint(n), query);
100throw_unless(36, slice_hash(msg) == slice_hash(in_msg));
101return (creator_i, cnt, cnt_bits, msg);
102}
103check_proposed_query(in_msg);
104
105return (root_i, 0, 0, in_msg);
106}
107
108(cell, ()) dec_flood(cell owner_infos, int creator_i) {
109(slice owner_info, var found?) = owner_infos.udict_get?(8, creator_i);
110(int public_key, int flood) = unpack_owner_info(owner_info);
111owner_infos~udict_set_builder(8, creator_i, pack_owner_info(public_key, flood - 1));
112return (owner_infos, ());
113}
114
115() try_init() impure inline_ref {
116;; first query without signatures is always accepted
117(int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries, int spend_delay) = unpack_state();
118throw_if(37, last_cleaned);
119accept_message();
120set_data(pack_state(pending_queries, owner_infos, 1, k, n, wallet_id, spend_delay));
121}
122
123(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 {
124if (cnt >= k) {
125accept_message();
126(int bridge_address, int oracles_address) = get_bridge_config();
127(_, int my_addr) = parse_std_addr(my_address());
128var (mode, msg', wc, addr) = parse_msg(msg);
129if ( ((wc == -1) & (addr == bridge_address)) | (oracles_address != my_addr) ) {
130send_raw_message(msg', mode);
131}
132pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1));
133owner_infos~dec_flood(creator_i);
134} else {
135pending_queries~udict_set_builder(64, query_id, begin_cell()
136.store_uint(1, 1)
137.store_uint(creator_i, 8)
138.store_uint(cnt, 8)
139.store_uint(cnt_bits, n)
140.store_slice(msg));
141}
142return (pending_queries, owner_infos);
143}
144
145(int, int) calc_boc_size(int cells, int bits, slice root) {
146cells += 1;
147bits += root.slice_bits();
148
149while (root.slice_refs()) {
150(cells, bits) = calc_boc_size(cells, bits, root~load_ref().begin_parse());
151}
152
153return (cells, bits);
154}
155
156() recv_external(slice in_msg) impure {
157;; empty message triggers init
158if (slice_empty?(in_msg)) {
159return try_init();
160}
161
162;; Check root signature
163slice root_signature = in_msg~load_bits(512);
164int root_hash = slice_hash(in_msg);
165int root_i = in_msg~load_uint(8);
166
167(int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries, int spend_delay) = unpack_state();
168
169throw_unless(38, now() > spend_delay);
170last_cleaned -= last_cleaned == 0;
171
172(slice owner_info, var found?) = owner_infos.udict_get?(8, root_i);
173throw_unless(31, found?);
174(int public_key, int flood) = unpack_owner_info(owner_info);
175throw_unless(32, check_signature(root_hash, root_signature, public_key));
176
177cell signatures = in_msg~load_dict();
178
179var hash = slice_hash(in_msg);
180int query_wallet_id = in_msg~load_uint(32);
181throw_unless(42, query_wallet_id == wallet_id);
182
183int query_id = in_msg~load_uint(64);
184
185(int cnt, int bits) = calc_boc_size(0, 0, in_msg);
186throw_if(40, (cnt > 8) | (bits > 2048));
187
188(slice query, var found?) = pending_queries.udict_get?(64, query_id);
189
190ifnot (found?) {
191flood += 1;
192throw_if(39, flood > 10);
193}
194
195var bound = (now() << 32);
196throw_if(33, query_id < bound);
197
198(int creator_i, int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?, root_i);
199int mask = 1 << root_i;
200throw_if(34, cnt_bits & mask);
201cnt_bits |= mask;
202cnt += 1;
203
204throw_if(41, ~ found? & (cnt < k) & (bound + ((60 * 60) << 32) > query_id));
205
206set_gas_limit(100000);
207
208ifnot (found?) {
209owner_infos~udict_set_builder(8, root_i, pack_owner_info(public_key, flood));
210}
211
212(pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k);
213set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id, spend_delay));
214
215commit();
216
217int need_save = 0;
218ifnot (cell_null?(signatures) | (cnt >= k)) {
219(int new_cnt, cnt_bits) = check_signatures(owner_infos, signatures, hash, cnt_bits);
220cnt += new_cnt;
221(pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k);
222need_save = -1;
223}
224
225accept_message();
226bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago
227int old_last_cleaned = last_cleaned;
228do {
229var (pending_queries', i, query, f) = pending_queries.udict_delete_get_min(64);
230f~touch();
231if (f) {
232f = (i < bound);
233}
234if (f) {
235if (query~load_int(1)) {
236owner_infos~dec_flood(query~load_uint(8));
237}
238pending_queries = pending_queries';
239last_cleaned = i;
240need_save = -1;
241}
242} until (~ f);
243
244if (need_save) {
245set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id, spend_delay));
246}
247}
248
249;; Get methods
250;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten)
251(int, int) get_query_state(int query_id) method_id {
252(_, int n, _, int last_cleaned, _, cell pending_queries, _) = unpack_state();
253(slice cs, var found) = pending_queries.udict_get?(64, query_id);
254if (found) {
255if (cs~load_int(1)) {
256cs~load_uint(8 + 8);
257return (0, cs~load_uint(n));
258} else {
259return (-1, 0);
260}
261} else {
262return (-(query_id <= last_cleaned), 0);
263}
264}
265
266int processed?(int query_id) method_id {
267(int x, _) = get_query_state(query_id);
268return x;
269}
270
271cell create_init_state(int wallet_id, int n, int k, cell owners_info, int spend_delay) method_id {
272return pack_state(new_dict(), owners_info, 0, k, n, wallet_id, spend_delay);
273}
274
275cell merge_list(cell a, cell b) {
276if (cell_null?(a)) {
277return b;
278}
279if (cell_null?(b)) {
280return a;
281}
282slice as = a.begin_parse();
283if (as.slice_refs() != 0) {
284cell tail = merge_list(as~load_ref(), b);
285return begin_cell().store_slice(as).store_ref(tail).end_cell();
286}
287
288as~skip_last_bits(1);
289;; as~skip_bits(1);
290return begin_cell().store_slice(as).store_dict(b).end_cell();
291
292}
293
294cell get_public_keys() method_id {
295(_, _, _, _, cell public_keys, _, _) = unpack_state();
296return public_keys;
297}
298
299(int, int) check_query_signatures(cell query) method_id {
300slice cs = query.begin_parse();
301slice root_signature = cs~load_bits(512);
302int root_hash = slice_hash(cs);
303int root_i = cs~load_uint(8);
304
305cell public_keys = get_public_keys();
306(slice public_key, var found?) = public_keys.udict_get?(8, root_i);
307throw_unless(31, found?);
308throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256)));
309
310int mask = 1 << root_i;
311
312cell signatures = cs~load_dict();
313if (cell_null?(signatures)) {
314return (1, mask);
315}
316(int cnt, mask) = check_signatures(public_keys, signatures, slice_hash(cs), mask);
317return (cnt + 1, mask);
318}
319
320int message_signed_by_id?(int id, int query_id) method_id {
321(_, int n, _, _, _, cell pending_queries, _) = unpack_state();
322(var cs, var f) = pending_queries.udict_get?(64, query_id);
323if (f) {
324if (cs~load_int(1)) {
325int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n);
326if (cnt_bits & (1 << id)) {
327return -1;
328}
329return 0;
330}
331return -1;
332}
333return 0;
334}
335
336cell messages_by_mask(int mask) method_id {
337(_, int n, _, _, _, cell pending_queries, _) = unpack_state();
338int i = -1;
339cell a = new_dict();
340do {
341(i, var cs, var f) = pending_queries.udict_get_next?(64, i);
342if (f) {
343if (cs~load_int(1)) {
344int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n);
345if (cnt_bits & mask) {
346a~udict_set_builder(64, i, begin_cell().store_slice(cs));
347}
348}
349}
350} until (~ f);
351return a;
352}
353
354cell get_messages_unsigned_by_id(int id) method_id {
355return messages_by_mask(1 << id);
356}
357
358cell get_messages_unsigned() method_id {
359return messages_by_mask(~ 0);
360}
361
362(int, int) get_n_k() method_id {
363(_, int n, int k, _, _, _, _) = unpack_state();
364return (n, k);
365}
366
367cell merge_inner_queries(cell a, cell b) method_id {
368slice ca = a.begin_parse();
369slice cb = b.begin_parse();
370cell list_a = ca~load_dict();
371cell list_b = cb~load_dict();
372throw_unless(31, slice_hash(ca) == slice_hash(cb));
373return begin_cell()
374.store_dict(merge_list(list_a, list_b))
375.store_slice(ca)
376.end_cell();
377}
378
379int get_lock_timeout() method_id {
380(_, _, _, _, _, _, int spend_delay) = unpack_state();
381return spend_delay;
382}
383