diff --git a/contracts/failed/wallet_v5_asm_cond_multicheck.fc b/contracts/failed/wallet_v5_asm_cond_multicheck.fc new file mode 100644 index 0000000..acc41d8 --- /dev/null +++ b/contracts/failed/wallet_v5_asm_cond_multicheck.fc @@ -0,0 +1,268 @@ +#pragma version =0.4.4; + +#include "imports/stdlib.fc"; + +;; LDUQ: s - x s' -1 or s 0 --NULLROTRIFNOT--> s - x s' -1 or # s 0 +(int, slice, int) try_load_uint32(slice s) asm "32 LDUQ" "NULLROTRIFNOT"; + +;; LDUQ: s - x s' -1 or s 0 --IFNOTRET--> x s' or RET +(int, slice) load_uint32_or_ret(slice s) impure asm "32 LDUQ" "IFNOTRET"; + +{- + Equivalent to: + if (flags & 1) { + ;; ignore all bounced messages + return (); + } +-} +() return_if_bounce(int flags) impure asm "1 PUSHINT" "AND" "IFRET"; +() return_if_not_equal(int a, int b) impure asm "EQUAL" "IFNOTRET"; +() return_if_not(int a) impure asm "IFNOTRET"; + +(int) equals_a_return_if_not_any(int what, int a, int b) impure asm(a what b) "OVER" "EQUAL" "ROTREV" "EQUAL" "TUCK" "OR" "IFNOTRET"; + +(slice) udict_get_or_return(cell dict, int key_len, int index) impure asm(index dict key_len) "DICTUGET IFNOTRET"; + +;; Extensible wallet contract v5 + +;; Compresses 8+256-bit address into 256-bit uint by cutting off one bit from sha256. +;; This allows us to save on wrapping the address in a cell and make plugin requests cheaper. +;; This method also unpacks address hash if you pass packed hash with the original wc. +int pack_address(int wc, int hash) impure asm "SWAP INC XOR"; ;; hash ^ (wc+1) + +;; Stores pre-computed list of actions (mostly `action_send_msg`) in the actions register. +() set_actions(cell action_list) impure asm "c5 POP"; + +{- + Equivalent to: + ifnot (cs.preload_uint(1)) { + set_actions(cs.preload_ref()); + return (); + } +-} +() set_actions_if_simple(slice cs) impure asm "DUP" "1 PLDU" "IFNOTJMP:<{ PLDREF c5 POP }>" "DROP"; + +;; Dispatches already authenticated request. +() dispatch_complex_request_inline(int stored_seqno, slice cs, slice immutable_tail, int stored_subwallet, int public_key) impure inline { + var extensions = null(); + + ;; Recurse into extended actions until we reach standard actions + while (cs~load_uint(1)) { + int op = cs~load_uint(32); + + ;; Raw set_data + if (op == 0x1ff8ea0b) { + set_data(cs~load_ref()); + } + + ;; Add/remove extensions + if ((op == 0x1c40db9f) | (op == 0x5eaef4a4)) { + (int wc, int hash) = parse_std_addr(cs~load_msg_addr()); + int packed_addr = pack_address(wc, hash); + + ifnot (immutable_tail.null?()) { + ;; immutable_tail = immutable_tail.skip_bits(80 + 256); + extensions = immutable_tail.skip_bits(80 + 256).preload_dict(); + ;; ds.end_parse() NO MORE guarantees that there is no more data in immutable_tail + ;; immutable_tail~skip_bits(immutable_tail.slice_bits()); ;; more resiliency in case of extraneous data + immutable_tail = null(); + } + + ;; Add extension + if (op == 0x1c40db9f) { + (extensions, int success?) = extensions.udict_add_builder?(256, packed_addr, begin_cell().store_int(wc,8)); + throw_unless(39, success?); + } + ;; Remove extension + if (op == 0x5eaef4a4) { + (extensions, int success?) = extensions.udict_delete?(256, packed_addr); + throw_unless(40, success?); + } + + set_data(begin_cell() + .store_uint(stored_seqno, 32) + .store_uint(stored_subwallet, 80) + .store_uint(public_key, 256) + .store_dict(extensions) + .end_cell()); + } + + ;; Other actions are no-op + ;; FIXME: is it costlier to check for unsupported actions and throw? + cs = cs.preload_ref().begin_parse(); + } + ;; At this point we are at `action_list_basic$0 {n:#} actions:^(OutList n) = ActionList n 0;` + ;; Simply set the C5 register with all pre-computed actions: + set_actions(cs.preload_ref()); + return (); +} + +() dispatch_complex_request_iref(int stored_seqno, slice cs, slice immutable_tail, int stored_subwallet, int public_key) impure inline_ref { + dispatch_complex_request_inline(stored_seqno, cs, immutable_tail, stored_subwallet, public_key); +} + +() dispatch_request(int public_key, slice cs, slice immutable_tail, int stored_subwallet, int stored_seqno) impure inline { + {- + ifnot (cs.preload_uint(1)) { + set_actions(cs.preload_ref()); + return (); + } + -} + set_actions_if_simple(cs); + ;; <<<<<<<<<<---------- CONTEST TEST CUTOFF POINT ---------->>>>>>>>>> + ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ;; ------------------------------------------ INLINING BOUNDARY ------------------------------------------ + dispatch_complex_request_iref(stored_seqno, cs, immutable_tail, stored_subwallet, public_key); +} + +() dispatch_extension_request(slice cs, var dummy) impure inline { + {- + ifnot (cs.preload_uint(1)) { + set_actions(cs.preload_ref()); + return (); + } + -} + set_actions_if_simple(cs); + dummy~impure_touch(); ;; DROP merged to 2DROP! + ;; <<<<<<<<<<---------- CONTEST TEST CUTOFF POINT ---------->>>>>>>>>> + var ds = get_data().begin_parse(); + var stored_seqno = ds~load_uint(32); + var immutable_tail = ds; ;; stored_subwallet ~ public_key ~ extensions + var stored_subwallet = ds~load_uint(80); + var public_key = ds.preload_uint(256); + ;; Note: If after modifications code becomes so large it undesirably splits to cells, + ;; replace with dispatch_complex_request_iref. This won't affect test cases but will affect GGC slightly. + dispatch_complex_request_inline(stored_seqno, cs, immutable_tail, stored_subwallet, public_key); +} + +;; Verifies signed request, prevents replays and proceeds with `dispatch_request`. +() process_signed_request(slice body, int stored_seqno, slice immutable_tail, int stored_subwallet, int public_key) impure inline { + var signature = body~load_bits(512); + throw_unless(35, check_signature(slice_hash(body), signature, public_key)); + + var cs = body; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(80), cs~load_uint(32), cs~load_uint(32)); + + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(33, msg_seqno == stored_seqno); + throw_if(36, valid_until <= now()); + + accept_message(); + + ;; Store and commit the seqno increment to prevent replays even if the subsequent requests fail. + stored_seqno = stored_seqno + 1; + set_data(begin_cell() + .store_uint(stored_seqno, 32) + .store_slice(immutable_tail) ;; stored_subwallet ~ public_key ~ extensions + .end_cell()); + + commit(); + + dispatch_request(public_key, cs, immutable_tail, stored_subwallet, stored_seqno); +} + +() recv_external(slice body) impure { + int auth_kind = body~load_uint(32); + return_if_not_equal(auth_kind, 0x7369676E); ;; "sign" + ;; if (auth_kind == 0x7369676E) { ;; "sign" + var ds = get_data().begin_parse(); + var stored_seqno = ds~load_uint(32); + var immutable_tail = ds; ;; stored_subwallet ~ public_key ~ extensions + var stored_subwallet = ds~load_uint(80); + var public_key = ds.preload_uint(256); + process_signed_request(body, stored_seqno, immutable_tail, stored_subwallet, public_key); + return(); + ;; } +} + +() recv_internal(int msg_value, cell full_msg, slice body) impure { + var full_msg_slice = full_msg.begin_parse(); + var flags = full_msg_slice~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddressInt ... + {- + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + -} + ;; return_if_bounce(3); <- as a test (since there are no bounce tests) - this works (breaks tests)! + return_if_bounce(flags); + {- + (int auth_kind, body, int success) = body.try_load_uint32(); ;; why there is no such stuff built in compiler? + ifnot (success) { + ;; ignore simple transfers + return (); + } + -} + (int auth_kind, body) = body.load_uint32_or_ret(); ;; Now that's some hot garbage! + + ;; We accept two kinds of authenticated messages: + ;; - 0x6578746E "extn" authenticated by extension + ;; - 0x7369676E "sign" authenticated by signature + + int is_extn = equals_a_return_if_not_any(auth_kind, 0x6578746E, 0x7369676E); ;; "extn" "sign" + + if (is_extn) { + + var ds = get_data().begin_parse(); + ;; It is not required to read this data here, maybe ext is doing simple transfer where those are not needed + var extensions = ds.skip_bits(32 + 80 + 256).preload_dict(); + + ;; Authenticate extension by its address. + int packed_sender_addr = pack_address(parse_std_addr(full_msg_slice~load_msg_addr())); ;; no PLDMSGADDR exists + ;; var (_, success?) = extensions.udict_get?(256, packed_sender_addr); + {- + ifnot (success?) { + ;; Note that some random contract may have deposited funds with this prefix, + ;; so we accept the funds silently instead of throwing an error (wallet v4 does the same). + return (); + } + -} + ;; return_if_not(success?); + var dummy = extensions.udict_get_or_return(256, packed_sender_addr); + ;; dummy is for black magic optimization. will drop it later with 2drop + dispatch_extension_request(body, dummy); ;; Special route for external address authenticated request + return (); + + } + + ;; return_if_not_equal(auth_kind, 0x7369676E); ;; "sign" + + ;; if (auth_kind == 0x7369676E) { ;; "sign" + + var ds = get_data().begin_parse(); + var stored_seqno = ds~load_uint(32); + var immutable_tail = ds; ;; keep immutable tail for fast commit storage and extensions retrieval if needed + var stored_subwallet = ds~load_uint(80); + var public_key = ds.preload_uint(256); + + ;; Process the rest of the slice just like the signed request. + process_signed_request(body, stored_seqno, immutable_tail, stored_subwallet, public_key); + + return (); ;; Explicit returns escape function faster and const less gas (suddenly!) + + ;; } + +} + + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_wallet_id() method_id { + return get_data().begin_parse().skip_bits(32).preload_uint(80); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse().skip_bits(32 + 80); + return cs.preload_uint(256); +} + +;; Returns raw dictionary (or null if empty) where keys are packed addresses and the `wc` is stored in leafs. +;; User should unpack the address using the same packing function using `wc` to restore the original address. +cell get_extensions() method_id { + var ds = get_data().begin_parse().skip_bits(32 + 80 + 256); + return ds~load_dict(); +}