-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This quite did not worked out. git good.
- Loading branch information
Showing
1 changed file
with
268 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} |