diff --git a/.gitignore b/.gitignore index ef9f336..6f05462 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules temp -build .idea .env diff --git a/Specification.md b/Specification.md index ed44015..fdfef5d 100644 --- a/Specification.md +++ b/Specification.md @@ -180,7 +180,7 @@ actions$_ {m:#} {n:#} actions:(ActionList n m) = InnerRequest; Contract state: ```tl-b wallet_id$_ global_id:# wc:int8 version:(## 8) subwallet_number:# = WalletID; -contract_state$_ seqno:int33 wallet_id:WalletID public_key:(## 256) extensions_dict:(HashmapE 256 int8) = ContractState; +contract_state$_ signature_auth_disabled:(## 1) seqno:# wallet_id:WalletID public_key:(## 256) extensions_dict:(HashmapE 256 int8) = ContractState; ``` ## Source code diff --git a/build/library-deployer.compiled.json b/build/library-deployer.compiled.json new file mode 100644 index 0000000..3acdc09 --- /dev/null +++ b/build/library-deployer.compiled.json @@ -0,0 +1 @@ +{"hex":"b5ee9c72410106010030000114ff00f4a413f4bcf2c80b0102012003020006f2f0010202d1050400193b511cbec1b232483ec13b552000053c00601cfc59c2"} \ No newline at end of file diff --git a/build/wallet_v5.compiled.json b/build/wallet_v5.compiled.json new file mode 100644 index 0000000..d76e9c0 --- /dev/null +++ b/build/wallet_v5.compiled.json @@ -0,0 +1 @@ +{"hash":"8b59a99853b0a3c983a6a4a3a695805892813736027e7a765bf6ce5b17b0b0d7","hashBase64":"i1mpmFOwo8mDpqSjppWAWJKBNzYCfnp2W/bOWxewsNc=","hex":"b5ee9c7241021401000282000114ff00f4a413f4bcf2c80b01020120020d020148030402ded020d749c120915b8f6420d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e07070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e012020d70b1f82107369676ebaf2e08a7f700f01e48eefeda2edfb228308d722038308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ddf9109a29945f0bdb31e1f2c087df02b35007b0f2d0845125baf2e0855037baf2e086f823bbf2d0882392f800de01a47fc8ca00cb1f01cf16c9ed542192f80fdedb3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd09084a9e7"} \ No newline at end of file diff --git a/contracts/imports/stdlib.fc b/contracts/imports/stdlib.fc index fa048f6..241993d 100644 --- a/contracts/imports/stdlib.fc +++ b/contracts/imports/stdlib.fc @@ -1,6 +1,21 @@ ;; Standard library for funC ;; +{- + This file is part of TON FunC Standard Library. + + FunC Standard Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + FunC Standard Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + +-} + {- # Tuple manipulation primitives The names and the types are mostly self-explaining. @@ -26,7 +41,7 @@ forall X -> tuple cons(X head, tuple tail) asm "CONS"; forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; ;;; Extracts the tail and the head of lisp-style list. -forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; +forall X -> (tuple, X) list_next(tuple list) asm(-> 1 0) "UNCONS"; ;;; Returns the head of lisp-style list. forall X -> X car(tuple list) asm "CAR"; @@ -244,11 +259,9 @@ cont bless(slice s) impure asm "BLESS"; () commit() impure asm "COMMIT"; ;;; Not implemented -;;() buy_gas(int gram) impure asm "BUYGAS"; - ;;; Computes the amount of gas that can be bought for `amount` nanoTONs, ;;; and sets `gl` accordingly in the same way as [set_gas_limit]. -() buy_gas(int amount) impure asm "BUYGAS"; +;;() buy_gas(int amount) impure asm "BUYGAS"; ;;; Computes the minimum of two integers [x] and [y]. int min(int x, int y) asm "MIN"; @@ -285,12 +298,12 @@ slice begin_parse(cell c) asm "CTOS"; () end_parse(slice s) impure asm "ENDS"; ;;; Loads the first reference from the slice. -(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; +(slice, cell) load_ref(slice s) asm(-> 1 0) "LDREF"; ;;; Preloads the first reference from the slice. cell preload_ref(slice s) asm "PLDREF"; - {- Functions below are commented because are implemented on compilator level for optimisation -} +{- Functions below are commented because are implemented on compilator level for optimisation -} ;;; Loads a signed [len]-bit integer from a slice [s]. ;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; @@ -311,8 +324,8 @@ cell preload_ref(slice s) asm "PLDREF"; ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; ;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`). -(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; -(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_grams(slice s) asm(-> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm(-> 1 0) "LDVARUINT16"; ;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; @@ -330,7 +343,7 @@ slice slice_last(slice s, int len) asm "SDCUTLAST"; ;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. ;;; (returns `null` if `nothing` constructor is used). -(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; +(slice, cell) load_dict(slice s) asm(-> 1 0) "LDDICT"; ;;; Preloads a dictionary `D` from `slice` [s]. cell preload_dict(slice s) asm "PLDDICT"; @@ -342,7 +355,7 @@ slice skip_dict(slice s) asm "SKIPDICT"; ;;; In other words loads 1 bit and if it is true ;;; loads first ref and return it with slice remainder ;;; otherwise returns `null` and slice remainder -(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +(slice, cell) load_maybe_ref(slice s) asm(-> 1 0) "LDOPTREF"; ;;; Preloads (Maybe ^Cell) from `slice` [s]. cell preload_maybe_ref(slice s) asm "PLDOPTREF"; @@ -434,7 +447,7 @@ builder store_slice(builder b, slice s) asm "STSLICER"; ;;; ;;; Store amounts of TonCoins to the builder as VarUInteger 16 builder store_grams(builder b, int x) asm "STGRAMS"; -builder store_coins(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STVARUINT16"; ;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. ;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. @@ -485,7 +498,7 @@ builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; ;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, ;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. -(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; +(slice, slice) load_msg_addr(slice s) asm(-> 1 0) "LDMSGADDR"; ;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. ;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. @@ -618,8 +631,6 @@ int get_seed() impure asm "RANDSEED"; () randomize_lt() impure asm "LTIME" "ADDRAND"; ;;; Checks whether the data parts of two slices coinside -int equal_slice_bits(slice a, slice b) asm "SDEQ"; -int equal_slices(slice a, slice b) asm "SDEQ"; - +int equal_slices_bits(slice a, slice b) asm "SDEQ"; ;;; Concatenates two builders builder store_builder(builder to, builder from) asm "STBR"; \ No newline at end of file diff --git a/contracts/wallet_v5.fc b/contracts/wallet_v5.fc index e8497e4..28cf307 100644 --- a/contracts/wallet_v5.fc +++ b/contracts/wallet_v5.fc @@ -2,349 +2,297 @@ #include "imports/stdlib.fc"; -const int size::stored_seqno = 33; -const int size::stored_subwallet = 80; +const int error::signature_disabled = 132; +const int error::invalid_seqno = 133; +const int error::invalid_wallet_id = 134; +const int error::invalid_signature = 135; +const int error::expired = 136; +const int error::external_send_message_must_have_ignore_errors_send_mode = 137; +const int error::invalid_message_operation = 138; +const int error::add_extension = 139; +const int error::remove_extension = 140; +const int error::unsupported_action = 141; +const int error::disable_signature_when_extensions_is_empty = 142; +const int error::this_signature_mode_already_set = 143; +const int error::remove_last_extension_when_signature_disabled = 144; +const int error::extension_wrong_workchain = 145; +const int error::only_extension_can_change_signature_mode = 146; +const int error::invalid_c5 = 147; + +const int size::bool = 1; +const int size::seqno = 32; +const int size::wallet_id = 32; const int size::public_key = 256; - -const int size::subwallet_id = 80; const int size::valid_until = 32; -const int size::msg_seqno = 32; +const int size::message_flags = 4; +const int size::signature = 512; +const int size::message_operation_prefix = 32; +const int size::address_hash_size = 256; +const int size::query_id = 64; + +const int prefix::signed_external = 0x7369676E; +const int prefix::signed_internal = 0x73696E74; +const int prefix::extension_action = 0x6578746E; -const int size::flags = 4; +(slice, int) check_and_remove_add_extension_prefix(slice body) impure asm "x{02} SDBEGINSQ"; +(slice, int) check_and_remove_remove_extension_prefix(slice body) impure asm "x{03} SDBEGINSQ"; +(slice, int) check_and_remove_set_signature_allowed_prefix(slice body) impure asm "x{04} SDBEGINSQ"; -() return_if(int cond) impure asm "IFRET"; -() return_unless(int cond) impure asm "IFNOTRET"; +;;; returns the number of trailing zeroes in slice s. +int count_trailing_zeroes(slice s) asm "SDCNTTRAIL0"; -(slice) udict_get_or_return(cell dict, int key_len, int index) impure asm(index dict key_len) "DICTUGET" "IFNOTRET"; +;;; returns the last 0 ≤ l ≤ 1023 bits of s. +slice get_last_bits(slice s, int l) asm "SDCUTLAST"; +;;; returns all but the last 0 ≤ l ≤ 1023 bits of s. +slice remove_last_bits(slice s, int l) asm "SDSKIPLAST"; -(slice) enforce_and_remove_sign_prefix(slice body) impure asm "x{7369676E} SDBEGINS"; -(slice, int) check_and_remove_extn_prefix(slice body) impure asm "x{6578746E} SDBEGINSQ"; -(slice, int) check_and_remove_sint_prefix(slice body) impure asm "x{73696E74} SDBEGINSQ"; +;; `action_send_msg` has 0x0ec3c86d prefix +;; https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L380 +slice enforce_and_remove_action_send_msg_prefix(slice body) impure asm "x{0ec3c86d} SDBEGINS"; -;; (slice, int) check_and_remove_set_data_prefix(slice body) impure asm "x{1ff8ea0b} SDBEGINSQ"; -(slice, int) check_and_remove_add_extension_prefix(slice body) impure asm "x{1c40db9f} SDBEGINSQ"; -(slice, int) check_and_remove_remove_extension_prefix(slice body) impure asm "x{5eaef4a4} SDBEGINSQ"; -(slice, int) check_and_remove_set_signature_auth_allowed_prefix(slice body) impure asm "x{20cbb95a} SDBEGINSQ"; +;;; put raw list of OutActions to C5 register. +;;; OutList TLB-schema - https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L378 +;;; C5 register - https://docs.ton.org/tvm.pdf, page 11 +() set_c5_actions(cell action_list) impure asm "c5 POP"; -(slice) enforce_and_remove_action_send_msg_prefix(slice body) impure asm "x{0ec3c86d} SDBEGINS"; +;;; transforms an ordinary or exotic cell into a Slice, as if it were an ordinary cell. A flag is returned indicating whether c is exotic. If that be the case, its type can later be deserialized from the first eight bits of s. +(slice, int) begin_parse_raw(cell c) asm "XCTOS"; -;; Extensible wallet contract v5 +cell verify_c5_actions(cell c5, int is_external) inline { + ;; XCTOS doesn't automatically load exotic cells (unlike CTOS `begin_parse`). + ;; we use it in `verify_c5_actions` because during action phase processing exotic cells in c5 won't be unfolded too. + ;; exotic cell starts with 0x02, 0x03 or 0x04 so it will not pass action_send_msg prefix check + (slice cs, _) = c5.begin_parse_raw(); -;; 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, int) address) impure asm "SWAP" "INC" "XOR"; ;; hash ^ (wc+1) + int count = 0; -;; Stores pre-computed list of actions (mostly `action_send_msg`) in the actions register. -() set_actions(cell action_list) impure asm "c5 POP"; + while (~ cs.slice_empty?()) { + ;; only `action_send_msg` is allowed; `action_set_code`, `action_reserve_currency` or `action_change_library` are not. + cs = cs.enforce_and_remove_action_send_msg_prefix(); -int count_leading_zeroes(slice cs) asm "SDCNTLEAD0"; -int count_trailing_zeroes(slice cs) asm "SDCNTTRAIL0"; -int count_trailing_ones(slice cs) asm "SDCNTTRAIL1"; + throw_unless(error::invalid_c5, cs.slice_bits() == 8); ;; send_mode + throw_unless(error::invalid_c5, cs.slice_refs() == 2); ;; next-action-ref and MessageRelaxed ref -;; (slice, slice) split(slice s, int bits, int refs) asm "SPLIT"; -;; (slice, slice, int) split?(slice s, int bits, int refs) asm "SPLIT" "NULLSWAPIFNOT"; + ;; enforce that send_mode has +2 bit (ignore errors) set for external message. + ;; if such send_mode is not set and sending fails at the action phase (for example due to insufficient balance) then the seqno will not be increased and the external message will be processed again and again. -slice get_last_bits(slice s, int n) asm "SDCUTLAST"; -slice remove_last_bits(slice s, int n) asm "SDSKIPLAST"; + ;; action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any) = OutAction; + ;; https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L380 + ;; load 7 bits and make sure that they end with 1 + throw_if(error::external_send_message_must_have_ignore_errors_send_mode, is_external & (count_trailing_zeroes(cs.preload_bits(7)) > 0)); + + (cs, _) = cs.preload_ref().begin_parse_raw(); + count += 1; + } + throw_unless(error::invalid_c5, count <= 255); + throw_unless(error::invalid_c5, cs.slice_refs() == 0); -cell verify_actions(cell c5) inline { - ;; Comment out code starting from here to disable checks (unsafe version) - ;; {- - slice c5s = c5.begin_parse(); - return_if(c5s.slice_empty?()); - do { - ;; only send_msg is allowed, set_code or reserve_currency are not - c5s = c5s.enforce_and_remove_action_send_msg_prefix(); - ;; enforce that send_mode has 2 bit set - ;; for that load 7 bits and make sure that they end with 1 - throw_if(37, count_trailing_zeroes(c5s.preload_bits(7))); - c5s = c5s.preload_ref().begin_parse(); - } until (c5s.slice_empty?()); - ;; -} return c5; } -;; Dispatches already authenticated request. -;; this function is explicitly included as an inline reference - not completely inlined -;; completely inlining it causes undesirable code split and noticeable gas increase in some paths -() dispatch_complex_request(slice cs) impure inline_ref { +() process_actions(slice cs, int is_external, int is_extension) impure inline_ref { + cell c5_actions = cs~load_maybe_ref(); + ifnot (cell_null?(c5_actions)) { + ;; Simply set the C5 register with all pre-computed actions after verification: + set_c5_actions(c5_actions.verify_c5_actions(is_external)); + } + if (cs~load_int(1) == 0) { ;; has_other_actions + return (); + } - ;; Recurse into extended actions until we reach standard actions - while (cs~load_int(1)) { - var is_add_ext? = cs~check_and_remove_add_extension_prefix(); - var is_del_ext? = is_add_ext? ? 0 : cs~check_and_remove_remove_extension_prefix(); + ;; Loop extended actions until we reach standard actions + while (true) { + int is_add_extension = cs~check_and_remove_add_extension_prefix(); + int is_remove_extension = is_add_extension ? 0 : cs~check_and_remove_remove_extension_prefix(); ;; Add/remove extensions - if (is_add_ext? | is_del_ext?) { - (int wc, int hash) = parse_std_addr(cs~load_msg_addr()); - int packed_addr = pack_address((wc, hash) ); + if (is_add_extension | is_remove_extension) { + (int address_wc, int address_hash) = parse_std_addr(cs~load_msg_addr()); + (int my_address_wc, _) = parse_std_addr(my_address()); - var ds = get_data().begin_parse(); - var data_bits = ds~load_bits(size::stored_seqno + size::stored_subwallet + size::public_key); - var stored_seqno = data_bits.preload_int(size::stored_seqno); - var extensions = ds.preload_dict(); + throw_unless(error::extension_wrong_workchain, my_address_wc == address_wc); + + slice data_slice = get_data().begin_parse(); + slice data_slice_before_extensions = data_slice~load_bits(size::bool + size::seqno + size::wallet_id + size::public_key); + cell extensions = data_slice.preload_dict(); ;; Add extension - if (is_add_ext?) { - (extensions, int success?) = extensions.udict_add_builder?(256, packed_addr, begin_cell().store_int(wc,8)); - throw_unless(39, success?); - } else - ;; Remove extension if (op == 0x5eaef4a4) - ;; It can be ONLY 0x1c40db9f OR 0x5eaef4a4 here. No need for second check. - { - (extensions, int success?) = extensions.udict_delete?(256, packed_addr); - throw_unless(40, success?); - throw_if(44, null?(extensions) & (stored_seqno < 0)); + if (is_add_extension) { + (extensions, int is_success) = extensions.udict_add_builder?(size::address_hash_size, address_hash, begin_cell().store_int(-1, 1)); + throw_unless( error::add_extension, is_success); + } else { ;; Remove extension + (extensions, int is_success) = extensions.udict_delete?(size::address_hash_size, address_hash); + throw_unless(error::remove_extension, is_success); + int is_signature_allowed = data_slice_before_extensions.preload_int(size::bool); + throw_if(error::remove_last_extension_when_signature_disabled, null?(extensions) & (~ is_signature_allowed)); } set_data(begin_cell() - .store_slice(data_bits) + .store_slice(data_slice_before_extensions) .store_dict(extensions) .end_cell()); - } - elseif (cs~check_and_remove_set_signature_auth_allowed_prefix()) { - var allow? = cs~load_int(1); - var ds = get_data().begin_parse(); - var stored_seqno = ds~load_int(size::stored_seqno); - var immutable_tail = ds; ;; stored_subwallet ~ public_key ~ extensions - if (allow?) { - ;; allow - throw_unless(43, stored_seqno < 0); - ;; Can't be disallowed with 0 because disallowing increments seqno - ;; -123 -> 123 -> 124 - stored_seqno = - stored_seqno; - stored_seqno = stored_seqno + 1; - } else { - ;; disallow - throw_unless(43, stored_seqno >= 0); - ds = ds.skip_bits(size::stored_subwallet + size::public_key); - var extensions_is_not_null = ds.preload_uint(1); - throw_unless(42, extensions_is_not_null); - ;; Corner case: 0 -> 1 -> -1 - ;; 123 -> 124 -> -124 - stored_seqno = stored_seqno + 1; - stored_seqno = - stored_seqno; + + } elseif (cs~check_and_remove_set_signature_allowed_prefix()) { + throw_unless(error::only_extension_can_change_signature_mode, is_extension); + int allow_signature = cs~load_int(1); + slice data_slice = get_data().begin_parse(); + int is_signature_allowed = data_slice~load_int(size::bool); + throw_if(error::this_signature_mode_already_set, is_signature_allowed == allow_signature); + is_signature_allowed = allow_signature; + + slice data_tail = data_slice; ;; seqno, wallet_id, public_key, extensions + ifnot (allow_signature) { ;; disallow + int is_extensions_not_empty = data_slice.skip_bits(size::seqno + size::wallet_id + size::public_key).preload_int(1); + throw_unless(error::disable_signature_when_extensions_is_empty, is_extensions_not_empty); } + set_data(begin_cell() - .store_int(stored_seqno, size::stored_seqno) - .store_slice(immutable_tail) ;; stored_subwallet ~ public_key ~ extensions + .store_int(is_signature_allowed, size::bool) + .store_slice(data_tail) ;; seqno, wallet_id, public_key, extensions .end_cell()); + } else { + throw(error::unsupported_action); } - ;; Uncomment to allow set_data (for unsafe version) - {- - elseif (cs~check_and_remove_set_data_prefix()) { - set_data(cs~load_ref()); - } - -} - else { - ;; need to throw on unsupported actions for correct flow and for testability - throw(41); ;; unsupported action + ifnot (cs.slice_refs()) { + return (); } 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 after verification: - set_actions(cs.preload_ref().verify_actions()); - return (); } ;; ------------------------------------------------------------------------------------------------ -;; Verifies signed request, prevents replays and proceeds with `dispatch_request`. -() process_signed_request_from_external_message(slice full_body) impure inline { - ;; The precise order of operations here is VERY important. Any other order results in unneccessary stack shuffles. - slice signature = full_body.get_last_bits(512); - slice signed = full_body.remove_last_bits(512); - - var cs = signed.skip_bits(32); - var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(size::subwallet_id), cs~load_uint(size::valid_until), cs~load_uint(size::msg_seqno)); - - var ds = get_data().begin_parse(); - var stored_seqno = ds~load_int(size::stored_seqno); - var immutable_tail = ds; ;; stored_subwallet ~ public_key ~ extensions - var stored_subwallet = ds~load_uint(size::stored_subwallet); - var public_key = ds.preload_uint(size::public_key); - - ;; TODO: Consider moving signed into separate ref, slice_hash consumes 500 gas just like cell creation! - ;; Only such checking order results in least amount of gas - throw_unless(35, check_signature(slice_hash(signed), signature, public_key)); - ;; If public key is disabled, stored_seqno is strictly less than zero: stored_seqno < 0 - ;; However, msg_seqno is uint, therefore it can be only greater or equal to zero: msg_seqno >= 0 - ;; Thus, if public key is disabled, these two domains NEVER intersect, and additional check is not needed - throw_unless(33, msg_seqno == stored_seqno); - throw_unless(34, subwallet_id == stored_subwallet); - 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_int(stored_seqno, size::stored_seqno) - .store_slice(immutable_tail) ;; stored_subwallet ~ public_key ~ extensions - .end_cell()); - - commit(); - - if (count_leading_zeroes(cs)) { ;; starts with bit 0 - return set_actions(cs.preload_ref().verify_actions()); +() process_signed_request(slice in_msg_body, int is_external, int is_extension) impure inline { + slice signature = in_msg_body.get_last_bits(size::signature); + slice signed_slice = in_msg_body.remove_last_bits(size::signature); + + slice cs = signed_slice.skip_bits(size::message_operation_prefix); ;; skip signed_internal or signed_external prefix + (int wallet_id, int valid_until, int seqno) = (cs~load_uint(size::wallet_id), cs~load_uint(size::valid_until), cs~load_uint(size::seqno)); + + slice data_slice = get_data().begin_parse(); + int is_signature_allowed = data_slice~load_int(size::bool); + int stored_seqno = data_slice~load_uint(size::seqno); + slice data_tail = data_slice; ;; wallet_id, public_key, extensions + int stored_wallet_id = data_slice~load_uint(size::wallet_id); + int public_key = data_slice~load_uint(size::public_key); + int is_extensions_not_empty = data_slice.preload_int(1); + + int is_signature_valid = check_signature(slice_hash(signed_slice), signature, public_key); + ifnot (is_signature_valid) { + if (is_external) { + throw(error::invalid_signature); + } else { + return (); + } } - ;; <<<<<<<<<<---------- Simple primary cases gas evaluation ends here ---------->>>>>>>>>> + throw_if(error::signature_disabled, (~ is_signature_allowed) & is_extensions_not_empty); + throw_unless(error::invalid_seqno, seqno == stored_seqno); + throw_unless(error::invalid_wallet_id, wallet_id == stored_wallet_id); + throw_if(error::expired, valid_until <= now()); - ;; inline_ref required because otherwise it will produce undesirable JMPREF - dispatch_complex_request(cs); -} - -() recv_external(slice body) impure inline { - slice full_body = body; - ;; 0x7369676E ("sign") external message authenticated by signature - body = enforce_and_remove_sign_prefix(body); - process_signed_request_from_external_message(full_body); - return(); -} - -;; ------------------------------------------------------------------------------------------------ - -() dispatch_extension_request(slice cs, var dummy1) impure inline { - if (count_leading_zeroes(cs)) { ;; starts with bit 0 - return set_actions(cs.preload_ref().verify_actions()); + if (is_external) { + accept_message(); } - ;; <<<<<<<<<<---------- Simple primary cases gas evaluation ends here ---------->>>>>>>>>> - ;; - dummy1~impure_touch(); ;; DROP merged to 2DROP! - dispatch_complex_request(cs); -} - -;; Same logic as above function but with return_* instead of throw_* and additional checks to prevent bounces -() process_signed_request_from_internal_message(slice full_body) impure inline { - ;; Additional check to make sure that there are enough bits for reading (+1 for actual actions flag) - return_if(full_body.slice_bits() < 32 + size::subwallet_id + size::valid_until + size::msg_seqno + 1 + 512); - - ;; The precise order of operations here is VERY important. Any other order results in unneccessary stack shuffles. - slice signature = full_body.get_last_bits(512); - slice signed = full_body.remove_last_bits(512); - - var cs = signed.skip_bits(32); - var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(size::subwallet_id), cs~load_uint(size::valid_until), cs~load_uint(size::msg_seqno)); - - var ds = get_data().begin_parse(); - var stored_seqno = ds~load_int(size::stored_seqno); - var immutable_tail = ds; ;; stored_subwallet ~ public_key ~ extensions - var stored_subwallet = ds~load_uint(size::stored_subwallet); - var public_key = ds.preload_uint(size::public_key); - - ;; Note on bouncing/nonbouncing behaviour: - ;; In principle, the wallet should not bounce incoming messages as to avoid - ;; returning deposits back to the sender due to opcode misinterpretation. - ;; However, specifically for "gasless" transactions (signed messages relayed by a 3rd party), - ;; there is a risk for the relaying party to be abused: their coins should be bounced back in case of a race condition or delays. - ;; We resolve this dilemma by silently failing at the signature check (therefore ordinary deposits with arbitrary opcodes never bounce), - ;; but failing with exception (therefore bouncing) after the signature check. - - ;; TODO: Consider moving signed into separate ref, slice_hash consumes 500 gas just like cell creation! - ;; Only such checking order results in least amount of gas - return_unless(check_signature(slice_hash(signed), signature, public_key)); - ;; If public key is disabled, stored_seqno is strictly less than zero: stored_seqno < 0 - ;; However, msg_seqno is uint, therefore it can be only greater or equal to zero: msg_seqno >= 0 - ;; Thus, if public key is disabled, these two domains NEVER intersect, and additional check is not needed - throw_unless(33, msg_seqno == stored_seqno); - throw_unless(34, subwallet_id == stored_subwallet); - throw_if(36, valid_until <= now()); ;; 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_int(stored_seqno, size::stored_seqno) - .store_slice(immutable_tail) ;; stored_subwallet ~ public_key ~ extensions + .store_int(true, size::bool) ;; is_signature_allowed + .store_uint(stored_seqno, size::seqno) + .store_slice(data_tail) ;; wallet_id, public_key, extensions .end_cell()); - commit(); - - if (count_leading_zeroes(cs)) { ;; starts with bit 0 - return set_actions(cs.preload_ref().verify_actions()); + if (is_external) { + commit(); } - ;; <<<<<<<<<<---------- Simple primary cases gas evaluation ends here ---------->>>>>>>>>> - ;; inline_ref required because otherwise it will produce undesirable JMPREF - dispatch_complex_request(cs); + process_actions(cs, is_external, is_extension); } -() recv_internal(cell full_msg, slice body) impure inline { - - ;; return right away if there are no references - ;; correct messages always have a ref, because any code paths ends with preload_ref - return_if(body.slice_refs_empty?()); - - ;; Any attempt to postpone msg_value deletion will result in s2 POP -> SWAP change. No use at all. - var full_msg_slice = full_msg.begin_parse(); - - var s_flags = full_msg_slice~load_bits(size::flags); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddressInt ... - - ;; If bounced flag (last bit) is set amount of trailing ones will be non-zero, else it will be zero. - return_if(count_trailing_ones(s_flags)); +() recv_external(slice in_msg_body) impure inline { + throw_unless(error::invalid_message_operation, in_msg_body.preload_uint(size::message_operation_prefix) == prefix::signed_external); + process_signed_request(in_msg_body, true, false); +} - ;; slicy_return_if_bounce(begin_cell().store_uint(3, 4).end_cell().begin_parse()); ;; TEST!!! +;; ------------------------------------------------------------------------------------------------ - ;; We accept two kinds of authenticated messages: - ;; - 0x6578746E "extn" authenticated by extension - ;; - 0x73696E74 "sint" internal message authenticated by signature +() recv_internal(cell in_msg_full, slice in_msg_body) impure inline { + if (in_msg_body.slice_bits() < size::message_operation_prefix) { + return (); + } + int op = in_msg_body.preload_uint(size::message_operation_prefix); + if ((op != prefix::extension_action) & (op != prefix::signed_internal)) { + return (); + } - (body, int is_extn?) = check_and_remove_extn_prefix(body); ;; 0x6578746E ("extn") + ;; bounded messages has 0xffffff prefix and skipped by op check - ;; IFJMPREF because unconditionally returns inside - if (is_extn?) { ;; "extn" authenticated by extension + if (op == prefix::extension_action) { + in_msg_body~skip_bits(size::message_operation_prefix); + slice in_msg_full_slice = in_msg_full.begin_parse(); + in_msg_full_slice~skip_bits(size::message_flags); ;; Authenticate extension by its address. - int packed_sender_addr = pack_address(parse_std_addr(full_msg_slice~load_msg_addr())); ;; no PLDMSGADDR exists + (int sender_address_wc, int sender_address_hash) = parse_std_addr(in_msg_full_slice~load_msg_addr()); + (int my_address_wc, _) = parse_std_addr(my_address()); - 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(size::stored_seqno + size::stored_subwallet + size::public_key).preload_dict(); + if (my_address_wc != sender_address_wc) { + return (); + } + + cell extensions = get_data().begin_parse() + .skip_bits(size::bool + size::seqno + size::wallet_id + size::public_key) + .preload_dict(); ;; 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). - var wc = extensions.udict_get_or_return(256, packed_sender_addr); ;; kindof ifnot (success?) { return(); } + (_, int extension_found) = extensions.udict_get?(size::address_hash_size, sender_address_hash); + ifnot (extension_found) { + return (); + } + + in_msg_body~skip_bits(size::query_id); ;; skip query_id - ;; auth_kind and wc are passed into dispatch_extension_request and later are dropped in batch with 3 BLKDROP - dispatch_extension_request(body, wc); ;; Special route for external address authenticated request + process_actions(in_msg_body, false, true); return (); } - slice full_body = body; - (_, int is_sint?) = check_and_remove_sint_prefix(body); ;; 0x73696E74 ("sint") - sign internal - return_unless(is_sint?); - - ;; Process the rest of the slice just like the signed request. - process_signed_request_from_internal_message(full_body); - return (); ;; Explicit returns escape function faster and const less gas (suddenly!) - + ;; Additional check to make sure that there are enough bits for reading before signature check + if (in_msg_body.slice_bits() < size::message_operation_prefix + size::wallet_id + size::valid_until + size::seqno + size::signature) { + return (); + } + process_signed_request(in_msg_body, false, false); } ;; ------------------------------------------------------------------------------------------------ ;; Get methods +int is_signature_allowed() method_id { + return get_data().begin_parse() + .preload_int(size::bool); +} + int seqno() method_id { - ;; Use absolute value to do not confuse apps with negative seqno if key is disabled - return abs(get_data().begin_parse().preload_int(size::stored_seqno)); + return get_data().begin_parse() + .skip_bits(size::bool) + .preload_uint(size::seqno); } -int get_wallet_id() method_id { - return get_data().begin_parse().skip_bits(size::stored_seqno).preload_uint(size::stored_subwallet); +int get_subwallet_id() method_id { + return get_data().begin_parse() + .skip_bits(size::bool + size::seqno) + .preload_uint(size::wallet_id); } int get_public_key() method_id { - var cs = get_data().begin_parse().skip_bits(size::stored_seqno + size::stored_subwallet); - return cs.preload_uint(size::public_key); + return get_data().begin_parse() + .skip_bits(size::bool + size::seqno + size::wallet_id) + .preload_uint(size::public_key); } -;; 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. +;; Returns raw dictionary (or null if empty) where keys are address hashes. Workchains of extensions are same with wallet smart contract workchain cell get_extensions() method_id { - var ds = get_data().begin_parse().skip_bits(size::stored_seqno + size::stored_subwallet + size::public_key); - return ds~load_dict(); -} - -int get_is_signature_auth_allowed() method_id { - return get_data().begin_parse().preload_int(size::stored_seqno) >= 0; -} + return get_data().begin_parse() + .skip_bits(size::bool + size::seqno + size::wallet_id + size::public_key) + .preload_dict(); +} \ No newline at end of file diff --git a/jest.config.ts b/jest.config.ts index 884cddc..9075ecd 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -4,6 +4,7 @@ const config: Config = { preset: 'ts-jest', testEnvironment: 'node', testPathIgnorePatterns: ['/node_modules/', '/dist/'], + testTimeout: 15000 }; export default config; diff --git a/package-lock.json b/package-lock.json index 7ee2edc..e27a735 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "WalletV5", "version": "0.0.1", "devDependencies": { - "@ton-community/blueprint": "^0.12.0", - "@ton-community/sandbox": "^0.11.0", - "@ton-community/test-utils": "^0.3.0", + "@ton/blueprint": "^0.21.0", + "@ton/core": "^0.56.3", + "@ton/crypto": "^3.2.0", + "@ton/sandbox": "^0.20.0", + "@ton/test-utils": "^0.4.2", "@types/jest": "^29.5.0", "@types/node": "^20.2.5", "@typescript-eslint/eslint-plugin": "^5.38.1", @@ -21,14 +23,12 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-unused-imports": "^2.0.0", - "jest": "^29.5.0", + "jest": "^29.7.0", "prettier": "^2.8.6", "ton": "~13.6.0", - "ton-core": "^0.51.0", - "ton-crypto": "^3.2.0", "ts-jest": "^29.0.5", "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.5.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -60,106 +60,44 @@ "dev": true }, "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.10", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", - "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.11.tgz", - "integrity": "sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-compilation-targets": "^7.22.10", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.11", - "@babel/parser": "^7.22.11", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.11", - "@babel/types": "^7.22.11", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", @@ -173,21 +111,15 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.10", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -195,14 +127,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", - "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -211,62 +143,66 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -285,79 +221,80 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz", - "integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.11", - "@babel/types": "^7.22.11" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -426,9 +363,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz", - "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -615,34 +552,34 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz", - "integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.11", - "@babel/types": "^7.22.11", - "debug": "^4.1.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -650,13 +587,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz", - "integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -822,6 +759,12 @@ "multiformats": "^9.5.4" } }, + "node_modules/@ipld/dag-pb/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -848,16 +791,16 @@ } }, "node_modules/@jest/console": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.4.tgz", - "integrity": "sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -865,15 +808,15 @@ } }, "node_modules/@jest/core": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.4.tgz", - "integrity": "sha512-U/vq5ccNTSVgYH7mHnodHmCffGWHJnz/E1BEWlLuK5pM4FZmGfBn/nrJGLjUsSmyx3otCeqc1T31F4y08AMDLg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "dependencies": { - "@jest/console": "^29.6.4", - "@jest/reporters": "^29.6.4", - "@jest/test-result": "^29.6.4", - "@jest/transform": "^29.6.4", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", @@ -881,21 +824,21 @@ "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.6.3", - "jest-config": "^29.6.4", - "jest-haste-map": "^29.6.4", - "jest-message-util": "^29.6.3", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.4", - "jest-resolve-dependencies": "^29.6.4", - "jest-runner": "^29.6.4", - "jest-runtime": "^29.6.4", - "jest-snapshot": "^29.6.4", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", - "jest-watcher": "^29.6.4", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -912,37 +855,37 @@ } }, "node_modules/@jest/environment": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.4.tgz", - "integrity": "sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.6.4", + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.3" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.4.tgz", - "integrity": "sha512-Warhsa7d23+3X5bLbrbYvaehcgX5TLYhI03JKoedTiI8uJU4IhqYBWF7OSSgUyz4IgLpUYPkK0AehA5/fRclAA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "dependencies": { - "expect": "^29.6.4", - "jest-snapshot": "^29.6.4" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.4.tgz", - "integrity": "sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "dependencies": { "jest-get-type": "^29.6.3" @@ -952,47 +895,47 @@ } }, "node_modules/@jest/fake-timers": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.4.tgz", - "integrity": "sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.6.3", - "jest-mock": "^29.6.3", - "jest-util": "^29.6.3" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.4.tgz", - "integrity": "sha512-wVIn5bdtjlChhXAzVXavcY/3PEjf4VqM174BM3eGL5kMxLiZD5CLnbmkEyA1Dwh9q8XjP6E8RwjBsY/iCWrWsA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.4", - "@jest/expect": "^29.6.4", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", - "jest-mock": "^29.6.3" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.4.tgz", - "integrity": "sha512-sxUjWxm7QdchdrD3NfWKrL8FBsortZeibSJv4XLjESOOjSUOkjQcb0ZHJwfhEGIvBvTluTzfG2yZWZhkrXJu8g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.6.4", - "@jest/test-result": "^29.6.4", - "@jest/transform": "^29.6.4", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", @@ -1006,9 +949,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", - "jest-worker": "^29.6.4", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1053,12 +996,12 @@ } }, "node_modules/@jest/test-result": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.4.tgz", - "integrity": "sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "@jest/console": "^29.6.4", + "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" @@ -1068,14 +1011,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.4.tgz", - "integrity": "sha512-E84M6LbpcRq3fT4ckfKs9ryVanwkaIB0Ws9bw3/yP4seRLg/VaCZ/LgW0MCq5wwk4/iP/qnilD41aj2fsw2RMg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.4", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.4", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1083,9 +1026,9 @@ } }, "node_modules/@jest/transform": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.4.tgz", - "integrity": "sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -1096,9 +1039,9 @@ "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.4", + "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1126,14 +1069,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -1149,9 +1092,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -1164,9 +1107,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1183,6 +1126,12 @@ "murmurhash3js-revisited": "^3.0.0" } }, + "node_modules/@multiformats/murmur3/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1292,9 +1241,9 @@ "dev": true }, "node_modules/@scarf/scarf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.1.1.tgz", - "integrity": "sha512-VGbKDbk1RFIaSmdVb0cNjjWJoRWRI/Weo23AjRCC2nryO0iAS8pzsToJfPVPtVs74WHw4L1UTADNdIYRLkirZQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.3.0.tgz", + "integrity": "sha512-lHKK8M5CTcpFj2hZDB3wIjb0KAbEOgDmiJGDv1WBRfQgRm/a8/XMEkG/N1iM01xgbUDsPQwi42D+dFo1XPAKew==", "dev": true, "hasInstallScript": true }, @@ -1305,9 +1254,9 @@ "dev": true }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "dependencies": { "type-detect": "4.0.8" @@ -1323,51 +1272,70 @@ } }, "node_modules/@tact-lang/compiler": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@tact-lang/compiler/-/compiler-1.1.3.tgz", - "integrity": "sha512-2UnHMW4S1+Rb5hUQAkNZWnQ8XJV4wRq+z3gAjchOzFiZEFNzE/46aIp7E1Jx+dFkfckdWXtGqHbhkRw6rc4CfQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@tact-lang/compiler/-/compiler-1.4.0.tgz", + "integrity": "sha512-MUZ8ulTrIs0sgs1tdNww7yan6ozMGNO7xR6S0yKZc57E0EN9o72vRqKdQW7k3iS3+MvltKPt6RVbNamtuVJ1tQ==", "dev": true, "dependencies": { "@ipld/dag-pb": "2.1.18", - "@tact-lang/opcode": "^0.0.13", - "arg": "^5.0.2", + "@tact-lang/opcode": "^0.0.14", + "@ton/core": "0.56.3", + "@ton/crypto": "^3.2.0", "blockstore-core": "1.0.5", "change-case": "^4.1.2", "ipfs-unixfs-importer": "9.0.10", + "meow": "^13.2.0", "mkdirp": "^2.1.3", - "multiformats": "9.9.0", - "ohm-js": "16.5.0", - "path-normalize": "^6.0.10", + "multiformats": "^13.1.0", + "ohm-js": "^17.1.0", + "path-normalize": "^6.0.13", "prando": "^6.0.1", - "qs": "^6.11.0", - "ton-core": ">=0.49.0", - "ton-crypto": "^3.2.0", - "zod": "^3.20.2" + "qs": "^6.12.1", + "zod": "^3.22.4" }, "bin": { "tact": "bin/tact" } }, "node_modules/@tact-lang/opcode": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@tact-lang/opcode/-/opcode-0.0.13.tgz", - "integrity": "sha512-4FGp1p3ahVrXr2QbyD2FqmnvXTMYapTlRJSPhj4O1L2yIq7dp0CkFL9EdKOCUfyirT0X5en78PHLfj0CuQGD7A==", + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@tact-lang/opcode/-/opcode-0.0.14.tgz", + "integrity": "sha512-8FKHK2jwvViRBReO2t40DCkHAP9KPTRWZof4kdsAUJFlyeWIC8SsRQSl9QkZxF+48WvjDduKNqN5Ltb80paufA==", "dev": true, "peerDependencies": { - "ton-core": ">=0.49.0", - "ton-crypto": "^3.2.0" + "@ton/core": ">=0.49.2", + "@ton/crypto": "^3.2.0" } }, - "node_modules/@ton-community/blueprint": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@ton-community/blueprint/-/blueprint-0.12.0.tgz", - "integrity": "sha512-QytcjOQCKtmaseEuEeuBGiKuQ649nwXGw4U3aGw7zz/SOd3SidMoTKAK3KE2VL84L8WRlw9HZx6wumJUkWPF7Q==", + "node_modules/@ton-community/func-js": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@ton-community/func-js/-/func-js-0.7.0.tgz", + "integrity": "sha512-VYJsv6Pqz6+qh3HlZWReBG5W9RXutAdIFYDqmblPSCXfjBhx/QjON/3WoppzUVrqQQdD0BVIh4PR+xRHRCBNhw==", + "dev": true, + "dependencies": { + "@ton-community/func-js-bin": "0.4.4-newops.1", + "arg": "^5.0.2" + }, + "bin": { + "func-js": "dist/cli.js" + } + }, + "node_modules/@ton-community/func-js-bin": { + "version": "0.4.4-newops.1", + "resolved": "https://registry.npmjs.org/@ton-community/func-js-bin/-/func-js-bin-0.4.4-newops.1.tgz", + "integrity": "sha512-TV4t6XhmItq4t+wv4pV30yEwb+YvdmsKo4Ig4B0zp4PLdI0r9iZHz4y5bBHcXmDQDRqulXzK6kTgfHvs2CIsaQ==", + "dev": true + }, + "node_modules/@ton/blueprint": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@ton/blueprint/-/blueprint-0.21.0.tgz", + "integrity": "sha512-87oHve+Xy+/IiyS1qNzs8JTGrxSI4kQrsNr7eERhZvC4qYmfAxpT0OBAJWx/qP+fcpzUOWR+vRiQqcJQQwwrLA==", "dev": true, "dependencies": { "@orbs-network/ton-access": "^2.3.3", - "@tact-lang/compiler": "^1.1.3", - "@ton-community/func-js": "^0.6.2", - "@tonconnect/sdk": "^2.1.3", + "@tact-lang/compiler": "^1.3.0", + "@ton-community/func-js": "^0.7.0", + "@tonconnect/sdk": "^2.2.0", "arg": "^5.0.2", "chalk": "^4.1.0", "dotenv": "^16.1.4", @@ -1380,52 +1348,65 @@ "blueprint": "dist/cli/cli.js" }, "peerDependencies": { - "ton": ">=13.4.1", - "ton-core": ">=0.48.0", - "ton-crypto": ">=3.2.0" + "@ton/core": ">=0.56.0", + "@ton/crypto": ">=3.2.0", + "@ton/ton": ">=13.11.0" } }, - "node_modules/@ton-community/func-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@ton-community/func-js/-/func-js-0.6.2.tgz", - "integrity": "sha512-5bewe8APG2TVgIPLUV5atQfAy+NtdjjGBfsWUeRdVUclzQ5H2wZ8aJsVNLiDBpKSNEKdOAP/1PownOFeodpQHg==", + "node_modules/@ton/core": { + "version": "0.56.3", + "resolved": "https://registry.npmjs.org/@ton/core/-/core-0.56.3.tgz", + "integrity": "sha512-HVkalfqw8zqLLPehtq0CNhu5KjVzc7IrbDwDHPjGoOSXmnqSobiWj8a5F+YuWnZnEbQKtrnMGNOOjVw4LG37rg==", "dev": true, "dependencies": { - "@ton-community/func-js-bin": "0.4.4", - "arg": "^5.0.2" + "symbol.inspect": "1.0.1" }, - "bin": { - "func-js": "dist/cli.js" + "peerDependencies": { + "@ton/crypto": ">=3.2.0" } }, - "node_modules/@ton-community/func-js-bin": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@ton-community/func-js-bin/-/func-js-bin-0.4.4.tgz", - "integrity": "sha512-zCSVXmh+rFMgouzTbWkSVDIt1Z5i36u9rws/Kuqn89/a0vhA1aEoJJ3oJypz0TjWKJQveU4doJsPlqu7UT2zkw==", - "dev": true + "node_modules/@ton/crypto": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@ton/crypto/-/crypto-3.2.0.tgz", + "integrity": "sha512-50RkwReEuV2FkxSZ8ht/x9+n0ZGtwRKGsJ0ay4I/HFhkYVG/awIIBQeH0W4j8d5lADdO5h01UtX8PJ8AjiejjA==", + "dev": true, + "dependencies": { + "@ton/crypto-primitives": "2.0.0", + "jssha": "3.2.0", + "tweetnacl": "1.0.3" + } + }, + "node_modules/@ton/crypto-primitives": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.0.0.tgz", + "integrity": "sha512-wttiNClmGbI6Dfy/8oyNnsIV0b/qYkCJz4Gn4eP62lJZzMtVQ94Ko7nikDX1EfYHkLI1xpOitWpW+8ZuG6XtDg==", + "dev": true, + "dependencies": { + "jssha": "3.2.0" + } }, - "node_modules/@ton-community/sandbox": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@ton-community/sandbox/-/sandbox-0.11.0.tgz", - "integrity": "sha512-3tlSprRBTSu9m0tJTC3cl4MXQep1vfNMPqk9+JAXSRJu9ToEvIUVpqO6MQNkbz9LkKDuOEBs5vyqT37DlKKcWw==", + "node_modules/@ton/sandbox": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@ton/sandbox/-/sandbox-0.20.0.tgz", + "integrity": "sha512-uci6DRDZGW1eu+hHgbVzf4lDTi29PV+5XKPC8ZyYUJaoOtulkHDtgyrfZ1H5QSOVOmUIjHDQhPwLsn1kU51yHw==", "dev": true, "peerDependencies": { - "ton-core": ">=0.48.0", - "ton-crypto": ">=3.2.0" + "@ton/core": ">=0.56.0", + "@ton/crypto": ">=3.2.0" } }, - "node_modules/@ton-community/test-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@ton-community/test-utils/-/test-utils-0.3.0.tgz", - "integrity": "sha512-eCw1c6a0TcKwiYEA4fmzPq+7dJtUx0UFYu+UEoRznIxEOcpEb8Ssjb9yMeiJEzbtUVMCkhEtpztdKt75ngDRWg==", + "node_modules/@ton/test-utils": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@ton/test-utils/-/test-utils-0.4.2.tgz", + "integrity": "sha512-fthY8Nrlmy8jnOl/vx6yjeKzzu62ZXMe7ej9Xg7rb4d3511V7dVQK+nw4YLSW5+dD/6WT03dFuNZXnuMYy5fHw==", "dev": true, "dependencies": { "node-inspect-extracted": "^2.0.0" }, "peerDependencies": { "@jest/globals": "*", - "chai": "*", - "ton-core": ">=0.36.1" + "@ton/core": ">=0.49.2", + "chai": "*" }, "peerDependenciesMeta": { "@jest/globals": { @@ -1436,6 +1417,36 @@ } } }, + "node_modules/@ton/ton": { + "version": "13.11.2", + "resolved": "https://registry.npmjs.org/@ton/ton/-/ton-13.11.2.tgz", + "integrity": "sha512-EPqW+ZTe0MmfqguJEIGMuAqTAFRKMEce95HlDx8h6CGn2y3jiMgV1/oO+WpDIOiX+1wnTu+xtajk8JTWr8nKRQ==", + "dev": true, + "peer": true, + "dependencies": { + "axios": "^1.6.7", + "dataloader": "^2.0.0", + "symbol.inspect": "1.0.1", + "teslabot": "^1.3.0", + "zod": "^3.21.4" + }, + "peerDependencies": { + "@ton/core": ">=0.56.0", + "@ton/crypto": ">=3.2.0" + } + }, + "node_modules/@ton/ton/node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dev": true, + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/@tonconnect/isomorphic-eventsource": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.1.tgz", @@ -1455,9 +1466,9 @@ } }, "node_modules/@tonconnect/protocol": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.2.5.tgz", - "integrity": "sha512-kR0E+CWZl6JrE/30283v+sRiAvEu21t1xOLFx6f/BxlCNLY2wki39+L32+iicX8gn/Ig99L1flr9TAI9QW9bnQ==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.2.6.tgz", + "integrity": "sha512-kyoDz5EqgsycYP+A+JbVsAUYHNT059BCrK+m0pqxykMODwpziuSAXfwAZmHcg8v7NB9VKYbdFY55xKeXOuEd0w==", "dev": true, "dependencies": { "tweetnacl": "^1.0.3", @@ -1541,18 +1552,18 @@ } }, "node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "dependencies": { "@types/node": "*" @@ -2188,12 +2199,12 @@ } }, "node_modules/babel-jest": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.4.tgz", - "integrity": "sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "dependencies": { - "@jest/transform": "^29.6.4", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", @@ -2347,6 +2358,12 @@ "multiformats": "^9.4.7" } }, + "node_modules/blockstore-core/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2370,9 +2387,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "funding": [ { @@ -2389,10 +2406,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -2453,13 +2470,19 @@ "dev": true }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2494,9 +2517,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001524", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", - "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", + "version": "1.0.30001638", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz", + "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", "dev": true, "funding": [ { @@ -2591,9 +2614,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, "node_modules/cli-cursor": { @@ -2609,9 +2632,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", - "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -2744,6 +2767,27 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2788,9 +2832,9 @@ } }, "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -2828,6 +2872,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -2915,21 +2976,21 @@ } }, "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "dev": true, "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/electron-to-chromium": { - "version": "1.4.503", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz", - "integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA==", + "version": "1.4.812", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.812.tgz", + "integrity": "sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==", "dev": true }, "node_modules/emittery": { @@ -3018,6 +3079,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -3059,9 +3141,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -3659,16 +3741,16 @@ } }, "node_modules/expect": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.4.tgz", - "integrity": "sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.6.4", + "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3" + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3831,9 +3913,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -3874,9 +3956,9 @@ } }, "node_modules/fp-ts": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.1.tgz", - "integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA==", + "version": "2.16.6", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.6.tgz", + "integrity": "sha512-v7w209VPj4L6pPn/ftFRJu31Oa8QagwcVw7BZmLCUWU4AQoc954rX9ogSIahDf67Pg+GjPbkW/Kn9XWnlWJG0g==", "dev": true }, "node_modules/fs.realpath": { @@ -3900,10 +3982,13 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.6", @@ -3957,15 +4042,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4159,12 +4248,12 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4209,6 +4298,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/header-case": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", @@ -4380,6 +4481,12 @@ "multiformats": "^9.0.4" } }, + "node_modules/interface-blockstore/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, "node_modules/interface-store": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-2.0.2.tgz", @@ -4401,9 +4508,9 @@ } }, "node_modules/io-ts": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.20.tgz", - "integrity": "sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA==", + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.21.tgz", + "integrity": "sha512-zz2Z69v9ZIC3mMLYWIeoUcwWD6f+O7yP92FMVVaXEOSZH1jnVBmET/urd/uoarD1WGBY4rCj8TAyMPzsGNzMFQ==", "dev": true, "peerDependencies": { "fp-ts": "^2.5.0" @@ -4463,6 +4570,12 @@ "npm": ">=7.0.0" } }, + "node_modules/ipfs-unixfs-importer/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -4794,14 +4907,14 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", - "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" }, @@ -4809,26 +4922,11 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -4836,12 +4934,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -4871,9 +4963,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -4929,15 +5021,15 @@ "dev": true }, "node_modules/jest": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.4.tgz", - "integrity": "sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "@jest/core": "^29.6.4", + "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.6.4" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -4955,13 +5047,13 @@ } }, "node_modules/jest-changed-files": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.6.3.tgz", - "integrity": "sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { "execa": "^5.0.0", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "p-limit": "^3.1.0" }, "engines": { @@ -4969,28 +5061,28 @@ } }, "node_modules/jest-circus": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.4.tgz", - "integrity": "sha512-YXNrRyntVUgDfZbjXWBMPslX1mQ8MrSG0oM/Y06j9EYubODIyHWP8hMUbjbZ19M3M+zamqEur7O80HODwACoJw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.4", - "@jest/expect": "^29.6.4", - "@jest/test-result": "^29.6.4", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.6.3", - "jest-matcher-utils": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-runtime": "^29.6.4", - "jest-snapshot": "^29.6.4", - "jest-util": "^29.6.3", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -5000,22 +5092,21 @@ } }, "node_modules/jest-cli": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.4.tgz", - "integrity": "sha512-+uMCQ7oizMmh8ZwRfZzKIEszFY9ksjjEQnTEMTaL7fYiL3Kw4XhqT9bYh+A4DQKUb67hZn2KbtEnDuHvcgK4pQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "@jest/core": "^29.6.4", - "@jest/test-result": "^29.6.4", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.6.4", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -5034,31 +5125,31 @@ } }, "node_modules/jest-config": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.4.tgz", - "integrity": "sha512-JWohr3i9m2cVpBumQFv2akMEnFEPVOh+9L2xIBJhJ0zOaci2ZXuKJj0tgMKQCBZAKA09H049IR4HVS/43Qb19A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.6.4", + "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", - "babel-jest": "^29.6.4", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.6.4", - "jest-environment-node": "^29.6.4", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.4", - "jest-runner": "^29.6.4", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -5079,24 +5170,24 @@ } }, "node_modules/jest-diff": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.4.tgz", - "integrity": "sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.6.3.tgz", - "integrity": "sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -5106,33 +5197,33 @@ } }, "node_modules/jest-each": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.3.tgz", - "integrity": "sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", - "jest-util": "^29.6.3", - "pretty-format": "^29.6.3" + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.4.tgz", - "integrity": "sha512-i7SbpH2dEIFGNmxGCpSc2w9cA4qVD+wfvg2ZnfQ7XVrKL0NA5uDVBIiGH8SR4F0dKEv/0qI5r+aDomDf04DpEQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.4", - "@jest/fake-timers": "^29.6.4", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.3", - "jest-util": "^29.6.3" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5148,9 +5239,9 @@ } }, "node_modules/jest-haste-map": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.4.tgz", - "integrity": "sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -5160,8 +5251,8 @@ "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", - "jest-util": "^29.6.3", - "jest-worker": "^29.6.4", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -5173,37 +5264,37 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz", - "integrity": "sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "dependencies": { "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz", - "integrity": "sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.6.4", + "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz", - "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", @@ -5212,7 +5303,7 @@ "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -5221,14 +5312,14 @@ } }, "node_modules/jest-mock": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.3.tgz", - "integrity": "sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.6.3" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5261,17 +5352,17 @@ } }, "node_modules/jest-resolve": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.4.tgz", - "integrity": "sha512-fPRq+0vcxsuGlG0O3gyoqGTAxasagOxEuyoxHeyxaZbc9QNek0AmJWSkhjlMG+mTsj+8knc/mWb3fXlRNVih7Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.4", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -5281,43 +5372,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.4.tgz", - "integrity": "sha512-7+6eAmr1ZBF3vOAJVsfLj1QdqeXG+WYhidfLHBRZqGN24MFRIiKG20ItpLw2qRAsW/D2ZUUmCNf6irUr/v6KHA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "dependencies": { "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.6.4" + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.4.tgz", - "integrity": "sha512-SDaLrMmtVlQYDuG0iSPYLycG8P9jLI+fRm8AF/xPKhYDB2g6xDWjXBrR5M8gEWsK6KVFlebpZ4QsrxdyIX1Jaw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "dependencies": { - "@jest/console": "^29.6.4", - "@jest/environment": "^29.6.4", - "@jest/test-result": "^29.6.4", - "@jest/transform": "^29.6.4", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.6.3", - "jest-environment-node": "^29.6.4", - "jest-haste-map": "^29.6.4", - "jest-leak-detector": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-resolve": "^29.6.4", - "jest-runtime": "^29.6.4", - "jest-util": "^29.6.3", - "jest-watcher": "^29.6.4", - "jest-worker": "^29.6.4", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -5326,17 +5417,17 @@ } }, "node_modules/jest-runtime": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.4.tgz", - "integrity": "sha512-s/QxMBLvmwLdchKEjcLfwzP7h+jsHvNEtxGP5P+Fl1FMaJX2jMiIqe4rJw4tFprzCwuSvVUo9bn0uj4gNRXsbA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.4", - "@jest/fake-timers": "^29.6.4", - "@jest/globals": "^29.6.4", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.6.4", - "@jest/transform": "^29.6.4", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", @@ -5344,13 +5435,13 @@ "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-mock": "^29.6.3", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.4", - "jest-snapshot": "^29.6.4", - "jest-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -5359,9 +5450,9 @@ } }, "node_modules/jest-snapshot": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.4.tgz", - "integrity": "sha512-VC1N8ED7+4uboUKGIDsbvNAZb6LakgIPgAF4RSpF13dN6YaMokfRqO+BaqK4zIh6X3JffgwbzuGqDEjHm/MrvA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -5369,20 +5460,20 @@ "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.6.4", - "@jest/transform": "^29.6.4", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.6.4", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.6.4", + "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "semver": "^7.5.3" }, "engines": { @@ -5423,9 +5514,9 @@ "dev": true }, "node_modules/jest-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz", - "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -5440,9 +5531,9 @@ } }, "node_modules/jest-validate": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.3.tgz", - "integrity": "sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -5450,7 +5541,7 @@ "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5469,18 +5560,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.4.tgz", - "integrity": "sha512-oqUWvx6+On04ShsT00Ir9T4/FvBeEh2M9PTubgITPxDa739p4hoQweWPRGyYeaojgT0xTpZKF0Y/rSY1UgMxvQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.4", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -5488,13 +5579,13 @@ } }, "node_modules/jest-worker": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.4.tgz", - "integrity": "sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -5724,26 +5815,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-dir/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -5751,12 +5827,6 @@ "node": ">=10" } }, - "node_modules/make-dir/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5772,6 +5842,18 @@ "tmpl": "1.0.5" } }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-options": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", @@ -5885,9 +5967,9 @@ "dev": true }, "node_modules/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.1.1.tgz", + "integrity": "sha512-JiptvwMmlxlzIlLLwhCi/srf/nk409UL0eUBr0kioRJq15hqqKyg68iftrBvhCRjR6Rw4fkNnSc4ZJXJDuta/Q==", "dev": true }, "node_modules/murmurhash3js-revisited": { @@ -5963,9 +6045,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, "node_modules/normalize-path": { @@ -6010,10 +6092,13 @@ "dev": true }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6106,9 +6191,9 @@ } }, "node_modules/ohm-js": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/ohm-js/-/ohm-js-16.5.0.tgz", - "integrity": "sha512-OXuB3g1cdP6vCyaGziLmihLkBrtfH9H9jmYp5nRqad093KVzkUSi062IVv5l+3SB/HIbMyDvBqhgR3Q3S+EEnw==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/ohm-js/-/ohm-js-17.1.0.tgz", + "integrity": "sha512-xc3B5dgAjTBQGHaH7B58M2Pmv6WvzrJ/3/7LeUzXNg0/sY3jQPdSd/S2SstppaleO77rifR1tyhdfFGNIwxf2Q==", "dev": true, "engines": { "node": ">=0.12.1" @@ -6361,9 +6446,9 @@ } }, "node_modules/path-normalize": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/path-normalize/-/path-normalize-6.0.12.tgz", - "integrity": "sha512-/lgDQDNQVtfOCKpQ9/Zr64/pT4OWmCiHDBtHCnJO/jkxXmnETI2Bes/E3y9yDbplYFMFvjRPSINfPvYxVrxEYA==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/path-normalize/-/path-normalize-6.0.13.tgz", + "integrity": "sha512-PfC1Pc+IEhI77UEN731pj2nMs9gHAV36IA6IW6VdXWjoQesf+jtO9hdMUqTRS6mwR0T5rmyUrQzd5vw0VwL1Lw==", "dev": true, "engines": { "node": ">=16.0.0" @@ -6385,9 +6470,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -6466,9 +6551,9 @@ } }, "node_modules/pretty-format": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", - "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -6530,6 +6615,13 @@ "pbts": "bin/pbts" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "peer": true + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -6540,9 +6632,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -6565,12 +6657,12 @@ } }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -6878,6 +6970,23 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6900,14 +7009,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7255,6 +7368,7 @@ "resolved": "https://registry.npmjs.org/ton-core/-/ton-core-0.51.0.tgz", "integrity": "sha512-FgejId/X1o7nNc5g8DBW1+9piwFolVU3e83Z8TudDCALik29YPhGOqLHSvcVYux3QV7SL0qCNaKgA3mV2nNfCA==", "dev": true, + "peer": true, "dependencies": { "symbol.inspect": "1.0.1" }, @@ -7518,9 +7632,9 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true }, "node_modules/tsutils": { @@ -7655,16 +7769,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/uint8arrays": { @@ -7676,6 +7790,12 @@ "multiformats": "^9.4.2" } }, + "node_modules/uint8arrays/node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -7692,9 +7812,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -7711,8 +7831,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -7767,25 +7887,19 @@ "dev": true }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -7811,9 +7925,9 @@ "dev": true }, "node_modules/whatwg-fetch": { - "version": "3.6.17", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz", - "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==", + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "dev": true }, "node_modules/whatwg-url": { @@ -7973,9 +8087,9 @@ } }, "node_modules/zod": { - "version": "3.22.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.2.tgz", - "integrity": "sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==", + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index a6a8954..3488bce 100644 --- a/package.json +++ b/package.json @@ -12,19 +12,13 @@ "scalpel": "bash ./scripts/scalpel.sh" }, "devDependencies": { - "@ton-community/blueprint": "^0.12.0", - "@ton-community/sandbox": "^0.11.0", - "@ton-community/test-utils": "^0.3.0", + "@ton/blueprint": "^0.21.0", + "@ton/core": "^0.56.3", + "@ton/crypto": "^3.2.0", + "@ton/sandbox": "^0.20.0", + "@ton/test-utils": "^0.4.2", "@types/jest": "^29.5.0", "@types/node": "^20.2.5", - "jest": "^29.5.0", - "prettier": "^2.8.6", - "ton": "~13.6.0", - "ton-core": "^0.51.0", - "ton-crypto": "^3.2.0", - "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "typescript": "^4.9.5", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", "eslint": "8.22.0", @@ -32,6 +26,12 @@ "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-unused-imports": "^2.0.0" + "eslint-plugin-unused-imports": "^2.0.0", + "jest": "^29.7.0", + "prettier": "^2.8.6", + "ton": "~13.6.0", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "^5.5.2" } } diff --git a/scripts/deployWalletV5.ts b/scripts/deployWalletV5.ts index 39f87e4..ce66fbb 100644 --- a/scripts/deployWalletV5.ts +++ b/scripts/deployWalletV5.ts @@ -15,6 +15,7 @@ export async function run(provider: NetworkProvider) { const walletV5 = provider.open( WalletV5.createFromConfig( { + signature_auth_disabled: false, seqno: 0, walletId: new WalletId({ networkGlobalId: -3 }).serialized, // testnet publicKey: keypair.publicKey, diff --git a/tests/WalletW5.spec.ts b/tests/WalletW5.spec.ts new file mode 100644 index 0000000..263fd85 --- /dev/null +++ b/tests/WalletW5.spec.ts @@ -0,0 +1,2870 @@ +import { compile } from '@ton/blueprint'; +import { + Address, + Cell, + Dictionary, + toNano, + Transaction, + internal as internal_relaxed, + beginCell, + SendMode, + Sender, + OutAction, + OutActionSendMsg, + contractAddress, + ExternalAddress, + storeOutAction +} from '@ton/core'; +import '@ton/test-utils'; +import { + Blockchain, + BlockchainSnapshot, + EmulationError, + SandboxContract, + SendMessageResult, + internal, + TreasuryContract +} from '@ton/sandbox'; +import { KeyPair, getSecureRandomBytes, keyPairFromSeed } from '@ton/crypto'; +import { Opcodes, walletV5ConfigToCell } from '../wrappers/wallet-v5'; +import { bufferToBigInt, getRandomInt, pickRandomNFrom } from './utils'; +import { findTransactionRequired, randomAddress } from '@ton/test-utils'; +import { estimateMessageImpact, getMsgPrices, MsgPrices, storageGeneric } from './gasUtils'; +import { ErrorsV5 } from '../wrappers/Errors'; +import { + WalletV5Test, + MessageOut, + WalletActions, + ExtendedAction, + message2action, + ExtensionAdd, + ExtensionRemove +} from '../wrappers/wallet-v5-test'; + +describe('Wallet v5 external tests', () => { + let blockchain: Blockchain; + let keys: KeyPair; + let wallet: SandboxContract; + let newWallet: SandboxContract; + let walletId: bigint; + const validOpCodes = [ + Opcodes.auth_signed, + Opcodes.auth_signed_internal, + Opcodes.auth_extension + ]; + const defaultExternalMode = SendMode.PAY_GAS_SEPARATELY | SendMode.IGNORE_ERRORS; + + let mockMessage: MessageOut; + let owner: SandboxContract; + let testWalletBc: SandboxContract; + let testWalletMc: SandboxContract; + let testExtensionBc: SandboxContract; + let testExtensionMc: SandboxContract; + + let initialState: BlockchainSnapshot; + let hasExtension: BlockchainSnapshot; + let hasMcWallet: BlockchainSnapshot; + + let msgPrices: MsgPrices; + let msgPricesMc: MsgPrices; + // let gasPrices: GasPrices; + + let code: Cell; + + let curTime: () => number; + let loadFrom: (snap: BlockchainSnapshot) => Promise; + let getWalletData: (from?: Address) => Promise; + let someMessages: (num: number) => OutActionSendMsg[]; + let someExtensions: ( + num: number, + action: 'add_extension' | 'remove_extension' + ) => ExtendedAction[]; + let assertMockMessage: (txs: Transaction[], from?: Address) => void; + let assertInternal: (txs: Transaction[], from: Address, exp: number) => void; + let shouldRejectWith: (p: Promise, code: number) => Promise; + let assertSendMessages: ( + exp: number, + wallet_id: bigint, + valid_until: number, + seqno: bigint | number, + messages: MessageOut[], + key: Buffer, + via?: Sender | ExtensionSender + ) => Promise; + + //type PartialBy = Omit & Partial>; + type TestArgs = { + walletId: bigint; + valid_until: number; + seqno: bigint | number; + actions: WalletActions; + key: Buffer; + prevState?: Cell; + extra?: any; + }; + type TestCase = (arg: TestArgs) => Promise; + type ExtensionSender = ( + arg: WalletActions + ) => Promise<{ op: number; res: SendMessageResult; is_inernal: boolean }>; + + /* Idea behind those wrappers is that we have common expectations of state + /* Everything common between Internal/External/Extension actions goes to wrapper. + /* Anything case specific goes to callbacks + */ + let testSendModes: ( + internal: boolean, + exp: number, + mask: SendMode, + modes: SendMode[], + customSender?: ExtensionSender + ) => Promise; + let extensionSender: ExtensionSender; + let testSendInit: (shouldSucceed: TestCase, validateNewWallet: TestCase) => Promise; + let testSetCode: (shouldSucceed: TestCase, validate: TestCase) => Promise; + let testAddExt: (shouldSucceed: TestCase, custom_addr?: Address) => Promise; + let testAddExtAlreadyIn: (shouldFail: TestCase) => Promise; + let testAddExtWrongChain: (shouldFail: TestCase, validateOnMc: TestCase) => Promise; + let testRemoveExt: (shouldSucceed: TestCase) => Promise; + let testAddRemoveSend: (shouldSucceed: TestCase) => Promise; + let testRemoveExtNonExistent: (shouldSucceed: TestCase) => Promise; + + beforeAll(async () => { + blockchain = await Blockchain.create(); + code = await compile('wallet_v5'); + keys = keyPairFromSeed(await getSecureRandomBytes(32)); + + owner = await blockchain.treasury('wallet_owner'); + testWalletBc = await blockchain.treasury('test_wallet', { workchain: 0 }); + testWalletMc = await blockchain.treasury('test_wallet', { workchain: -1 }); + + testExtensionBc = await blockchain.treasury('test_extension', { workchain: 0 }); + testExtensionMc = await blockchain.treasury('test_extension', { workchain: -1 }); + + mockMessage = { + message: internal_relaxed({ + to: testWalletBc.address, + value: toNano('1'), + body: beginCell().storeUint(0xdeadbeef, 32).endCell() + }), + mode: defaultExternalMode + }; + + msgPrices = getMsgPrices(blockchain.config, 0); + msgPricesMc = getMsgPrices(blockchain.config, -1); + + walletId = BigInt(getRandomInt(10, 1337)); + wallet = blockchain.openContract( + WalletV5Test.createFromConfig( + { + seqno: 0, + walletId, + signatureAllowed: true, + publicKey: keys.publicKey, + extensions: Dictionary.empty() + }, + code + ) + ); + + const deploy = await wallet.sendDeploy(owner.getSender(), toNano('100000')); + + expect(deploy.transactions).toHaveTransaction({ + on: wallet.address, + from: owner.address, + aborted: false, + deploy: true + }); + + initialState = blockchain.snapshot(); + + curTime = () => { + return blockchain.now ?? Math.floor(Date.now() / 1000); + }; + + loadFrom = async snap => { + if (snap == undefined) { + throw new Error("Snapshot doesn't exist yet. Check tests order"); + } + await blockchain.loadFrom(snap); + }; + getWalletData = async (address?: Address) => { + const contractAddress = address ?? wallet.address; + const smc = await blockchain.getContract(contractAddress); + if (!smc.account.account) throw 'Account not found'; + if (smc.account.account.storage.state.type != 'active') + throw 'Atempting to get data on inactive account'; + if (!smc.account.account.storage.state.state.data) throw 'Data is not present'; + return smc.account.account.storage.state.state.data; + }; + + someMessages = n => { + const messages: OutActionSendMsg[] = new Array(n); + for (let i = 0; i < n; i++) { + messages[i] = { + type: 'sendMsg', + mode: defaultExternalMode, + outMsg: internal_relaxed({ + to: testWalletBc.address, + value: toNano('1'), + body: beginCell().storeUint(i, 32).endCell() + }) + }; + } + return messages; + }; + someExtensions = (n, action) => { + const extensions: ExtendedAction[] = new Array(n); + + for (let i = 0; i < n; i++) { + extensions[i] = { + type: action, + address: randomAddress() + }; + } + + return extensions; + }; + + assertMockMessage = (txs, from) => { + const fromAddr = from ?? wallet.address; + expect(txs).toHaveTransaction({ + on: testWalletBc.address, + from: fromAddr, + value: toNano('1'), + body: beginCell().storeUint(0xdeadbeef, 32).endCell() + }); + }; + assertInternal = (txs, from, exp) => { + const expSuccess = exp == 0; + expect(txs).toHaveTransaction({ + on: wallet.address, + from, + success: expSuccess, + aborted: !expSuccess, + outMessagesCount: !expSuccess ? 1 : 0 + }); + }; + shouldRejectWith = async (p, code) => { + try { + const res = await p; + console.log((res as any).transactions[0].description); + throw new Error(`Should throw ${code}`); + } catch (e: unknown) { + if (e instanceof EmulationError) { + expect(e.exitCode !== undefined && e.exitCode == code).toBe(true); + } else { + throw e; + } + } + }; + + assertSendMessages = async (exp, wallet_id, valid_until, seqno, messages, key, via) => { + let res: SendMessageResult; + let op: number; + let isInternal: boolean; + + const smc = await blockchain.getContract(wallet.address); + let balanceBefore = BigInt(smc.balance); + + if (typeof via == 'function') { + const customRes = await via({ wallet: messages.map(message2action) }); + isInternal = customRes.is_inernal; + res = customRes.res; + op = customRes.op; + } else { + if (via) { + op = Opcodes.auth_signed_internal; + isInternal = true; + res = await wallet.sendMessagesInternal( + via, + wallet_id, + valid_until, + seqno, + key, + messages + ); + } else { + isInternal = false; + op = Opcodes.auth_signed; + res = await wallet.sendMessagesExternal( + wallet_id, + valid_until, + seqno, + key, + messages + ); + } + } + + if (exp == 0) { + const sendTx = findTransactionRequired(res.transactions, { + on: wallet.address, + op, + aborted: false, + outMessagesCount: messages.length + }); + // console.log(sendTx.description); + // console.log(sendTx.blockchainLogs); + + const storageFee = storageGeneric(sendTx).storageFeesCollected; + + balanceBefore -= storageFee; + + for (let i = 0; i < messages.length; i++) { + // console.log("Message:", i); + const msgOut = sendTx.outMessages.get(i)!; + if (msgOut.info.type == 'internal') { + const curPrices = msgOut.info.dest.workChain == 0 ? msgPrices : msgPricesMc; + const estMessage = estimateMessageImpact( + messages[i].message, + sendTx, + curPrices, + balanceBefore, + messages[i].mode, + i > 0 + ); + expect(res.transactions).toHaveTransaction({ + on: msgOut.info.dest, + from: wallet.address, + value: estMessage.expValue, + body: msgOut.body + }); + balanceBefore = estMessage.balanceAfter; + } + } + // console.log("Calculated balance:", balanceBefore); + // console.log("Real balance:", smc.balance); + expect(balanceBefore).toEqual(smc.balance); + } else { + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + outMessagesCount: isInternal ? 1 : 0, // On internal we should bounce + aborted: isInternal, + op, + exitCode: exp + }); + } + return res; + }; + testSendModes = async (internal, exp, mask, modes, sender) => { + let testMsgs: MessageOut[] = []; + let i = 0; + let seqNo = await wallet.getSeqno(); + const oldSeqno = seqNo; + const prevState = blockchain.snapshot(); + const testSender = sender ?? internal ? owner.getSender() : undefined; + /* + if(testSender == sender) { + console.log("Custom sender is working!"); + } + */ + try { + for (let mode of modes) { + // console.log("Testing mode:", mode); + const newMsg: MessageOut = { + message: internal_relaxed({ + to: testWalletBc.address, + value: toNano(1), + body: beginCell().storeUint(i++, 32).endCell() + }), + mode: mode | mask + }; + testMsgs.push(newMsg); + + // Test in single mode first + await assertSendMessages( + exp, + walletId, + curTime() + 1000, + seqNo, + [newMsg], + keys.secretKey, + testSender + ); + expect(await wallet.getSeqno()).toEqual(++seqNo); + } + + await blockchain.loadFrom(prevState); + + // Now all at once + await assertSendMessages( + exp, + walletId, + curTime() + 1000, + oldSeqno, + testMsgs, + keys.secretKey, + testSender + ); + expect(await wallet.getSeqno()).toEqual(oldSeqno + 1); + } finally { + await blockchain.loadFrom(prevState); + } + }; + extensionSender = async actions => { + const res = await wallet.sendExtensionActions(testExtensionBc.getSender(), actions); + return { + is_inernal: true, + res, + op: Opcodes.auth_extension + }; + }; + testSendInit = async (shouldWork, validateNewWallet) => { + let newWalletId: bigint; + let seqNo = await wallet.getSeqno(); + + do { + newWalletId = BigInt(getRandomInt(1000, 100000)); + } while (newWalletId == walletId); + + const newWalletData = walletV5ConfigToCell({ + walletId: newWalletId, + seqno: 0, + signatureAllowed: true, + publicKey: keys.publicKey, // Same key + extensions: Dictionary.empty() + }); + + // Deploying it to masterchain + const newAddress = contractAddress(-1, { code, data: newWalletData }); + + const testArgs: TestArgs = { + walletId, + valid_until: curTime() + 100, + seqno: seqNo, + actions: { + wallet: [ + { + type: 'sendMsg', + outMsg: internal_relaxed({ + to: newAddress, + value: toNano('100'), + init: { + code, + data: newWalletData + } + }), + mode: defaultExternalMode + } + ] + }, + key: keys.secretKey, + extra: { new_address: newAddress } + }; + + await shouldWork(testArgs); + + newWallet = blockchain.openContract(WalletV5Test.createFromAddress(newAddress)); + + // New wallet should be able to send message with current key + + await validateNewWallet({ + walletId: newWalletId, + valid_until: curTime() + 100, + seqno: 0, + actions: {}, // Won't be used by this handler anyway + key: keys.secretKey + }); + // res = await newWallet.sendMessagesExternal(newWalletId, curTime() + 100, 0, keys.secretKey, [mockMessage]); + + // Let's test getters while we can + expect((await newWallet.getWalletId()).subwalletNumber).toEqual(Number(newWalletId)); + expect(await newWallet.getPublicKey()).toEqual(bufferToBigInt(keys.publicKey)); + expect(await newWallet.getIsSignatureAuthAllowed()).toBe(-1); + + hasMcWallet = blockchain.snapshot(); + }; + testSetCode = async (testCb, validateCb) => { + let testMsgs: OutAction[] = new Array(254); + const newCode = beginCell().storeUint(getRandomInt(0, 1000), 32).endCell(); + let seqNo = await wallet.getSeqno(); + + const setCodeAction: OutAction = { + type: 'setCode', + newCode + }; + + testMsgs = someMessages(254); // Saving space for set_code + + const onlySetCode = [setCodeAction]; + const setCodeLast = [...testMsgs, setCodeAction]; + const setCodeFirst = [setCodeAction, ...testMsgs]; + const setCodeShuffle = [...testMsgs]; + + const setCodeIdx = getRandomInt(1, setCodeShuffle.length - 1); + // Just replace some random position with setCode + setCodeShuffle[setCodeIdx] = setCodeAction; + + const extraSetCode = [...setCodeShuffle]; + let newIdx = setCodeIdx; + + do { + newIdx = getRandomInt(1, setCodeShuffle.length - 1); + } while (newIdx == setCodeIdx); + // Insert another one, in case code removes first matched only + extraSetCode[newIdx] = setCodeAction; + + const prevState = await getWalletData(); + const defaultArgs = { + walletId, + seqno: seqNo, + valid_until: curTime() + 1000, + key: keys.secretKey, + prevState + }; + for (let actionSet of [ + onlySetCode, + setCodeLast, + setCodeFirst, + setCodeShuffle, + extraSetCode + ]) { + //const setCodeRequest = WalletV5Test.requestMessage(false, walletId, curTime() + 100, seqNo, {wallet: actionSet}, keys.secretKey); + const negTestArgs: TestArgs = { + ...defaultArgs, + seqno: seqNo, + actions: { wallet: actionSet } + }; + // const negTestArgs: TestArgs = {...defaultArgs, actions: {wallet: actionSet}}; + await testCb(negTestArgs); + seqNo = await wallet.getSeqno(); + } + + // Validate that it has nothing to do with message list + await validateCb({ ...defaultArgs, seqno: seqNo, actions: { wallet: testMsgs } }); + }; + testAddExt = async (checkTx, customAddr) => { + let seqNo = await wallet.getSeqno(); + + const extensionAddr = customAddr ?? testExtensionBc.address; + const testArgs: TestArgs = { + walletId, + valid_until: curTime() + 100, + seqno: seqNo, + actions: { + extended: [ + { + type: 'add_extension', + address: extensionAddr + } + ] + }, + key: keys.secretKey + }; + + await checkTx(testArgs); + + const installedExt = await wallet.getExtensionsArray(); + expect(installedExt.findIndex(a => a.equals(extensionAddr))).toBeGreaterThanOrEqual(0); + // expect(await wallet.getSeqno()).toEqual(seqNo + 1); + }; + testAddExtAlreadyIn = async checkTx => { + await loadFrom(hasExtension); + + const installedBefore = await wallet.getExtensionsArray(); + let seqNo = await wallet.getSeqno(); + + const testArgs: TestArgs = { + walletId, + valid_until: curTime() + 100, + seqno: seqNo, + actions: { + extended: [ + { + type: 'add_extension', + address: testExtensionBc.address + } + ] + }, + key: keys.secretKey + }; + + await checkTx(testArgs); + + const installedAfter = await wallet.getExtensionsArray(); + expect(installedBefore.length).toEqual(installedAfter.length); + + for (let i = 0; i < installedBefore.length; i++) { + expect(installedBefore[i].equals(installedAfter[i])).toBe(true); + } + }; + testAddExtWrongChain = async (shouldReject, validate) => { + const prevState = blockchain.snapshot(); + let seqNo = await wallet.getSeqno(); + const installedBefore = await wallet.getExtensionsArray(); + + let testArgs: TestArgs = { + walletId, + valid_until: curTime() + 100, + seqno: seqNo, + actions: { + extended: [ + { + type: 'add_extension', + address: testExtensionMc.address + } + ] + }, + key: keys.secretKey + }; + + await shouldReject(testArgs); + + const installedAfter = await wallet.getExtensionsArray(); + expect(installedBefore.length).toEqual(installedAfter.length); + + for (let i = 0; i < installedBefore.length; i++) { + expect(installedBefore[i].equals(installedAfter[i])).toBe(true); + } + // But it should work for the wallet in basechain + + const newSeqNo = await newWallet.getSeqno(); + const newId = BigInt((await newWallet.getWalletId()).subwalletNumber); + + testArgs = { + walletId: newId, + valid_until: curTime() + 100, + seqno: newSeqNo, + actions: { + extended: [ + { + type: 'add_extension', + address: testExtensionMc.address + } + ] + }, + key: keys.secretKey + }; + + let installedExt = await newWallet.getExtensionsArray(); + expect(installedExt.findIndex(a => a.equals(testExtensionMc.address))).toBe(-1); + + await validate(testArgs); + + installedExt = await newWallet.getExtensionsArray(); + expect( + installedExt.findIndex(a => a.equals(testExtensionMc.address)) + ).toBeGreaterThanOrEqual(0); + // expect(await wallet.getSeqno()).toEqual(seqNo + 1); + + await loadFrom(prevState); + }; + testRemoveExt = async shouldRemove => { + await loadFrom(hasExtension); + let seqNo = await wallet.getSeqno(); + let installedExt = await wallet.getExtensionsArray(); + expect(installedExt[0].equals(testExtensionBc.address)).toBe(true); + + const testArgs: TestArgs = { + walletId, + valid_until: curTime() + 100, + seqno: seqNo, + actions: { + extended: [ + { + type: 'remove_extension', + address: testExtensionBc.address + } + ] + }, + key: keys.secretKey + }; + + await shouldRemove(testArgs); + + installedExt = await wallet.getExtensionsArray(); + expect(installedExt.findIndex(a => a.equals(testExtensionBc.address))).toBe(-1); + }; + testRemoveExtNonExistent = async shouldFail => { + await loadFrom(hasExtension); + let seqNo = await wallet.getSeqno(); + const differentExt = await blockchain.treasury('totally different extension'); + const installedBefore = await wallet.getExtensionsArray(); + + expect(installedBefore.length).toBe(1); + expect(installedBefore[0].equals(testExtensionBc.address)).toBe(true); + + const testArgs: TestArgs = { + walletId, + valid_until: curTime() + 100, + seqno: seqNo, + actions: { + extended: [ + { + type: 'remove_extension', + address: differentExt.address + } + ] + }, + key: keys.secretKey + }; + await shouldFail(testArgs); + + const extAfter = await wallet.getExtensionsArray(); + expect(extAfter.length).toBe(1); + // expect(await wallet.getSeqno()).toEqual(seqNo + 1); + expect( + extAfter.findIndex(e => e.equals(testExtensionBc.address)) + ).toBeGreaterThanOrEqual(0); + }; + testAddRemoveSend = async shouldWork => { + const prevState = blockchain.snapshot(); + const seqNo = await wallet.getSeqno(); + + const extBefore = await wallet.getExtensionsArray(); + const testMessages = someMessages(255); // Full pack + const testExtensions = someExtensions(100, 'add_extension'); + + // Let's pick some of those for removal + const removeExt = pickRandomNFrom(5, testExtensions).map(e => { + const res: ExtensionRemove = { + type: 'remove_extension', + address: (e as ExtensionAdd).address + }; + return res; + }); + // console.log("Remove extensions:", removeExt); + const shouldStay = (testExtensions as ExtensionAdd[]) + .filter(e => removeExt.find(r => r.address.equals(e.address)) == undefined) + .map(e => e.address); + shouldStay.push(...extBefore); + testExtensions.push(...removeExt); + + const testArgs: TestArgs = { + walletId, + valid_until: curTime() + 100, + seqno: seqNo, + actions: { + wallet: testMessages, + extended: testExtensions + }, + key: keys.secretKey + }; + + await shouldWork(testArgs); + + const extAfter = await wallet.getExtensionsArray(); + + expect(extAfter.length).toEqual(shouldStay.length); + for (let i = 0; i < shouldStay.length; i++) { + const testAddr = shouldStay[i]; + expect(extAfter.findIndex(addr => addr.equals(testAddr))).toBeGreaterThanOrEqual(0); + expect(removeExt.findIndex(e => e.address.equals(testAddr))).toBe(-1); + } + // expect(await wallet.getSeqno()).toEqual(seqNo + 1); + await loadFrom(prevState); + }; + }); + describe('Basic', () => { + it('should deploy', async () => {}); + it('should be able to receive basic transfer', async () => { + const testWallet = await blockchain.treasury('test_wallet'); + const assertSimple = async (body?: Cell) => { + const res = await testWallet.send({ + to: wallet.address, + value: toNano(getRandomInt(1, 100)), + body, + sendMode: SendMode.PAY_GAS_SEPARATELY + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testWallet.address, + aborted: false, + outMessagesCount: 0 + }); + }; + + await assertSimple(); + await assertSimple( + beginCell().storeUint(0, 32).storeStringTail('Hey, bruh!').endCell() + ); + + const validSet = new Set(validOpCodes); + let testOp: number; + + do { + testOp = getRandomInt(1, (1 << 32) - 1); + } while (validSet.has(testOp)); + + await assertSimple( + beginCell().storeUint(testOp, 32).storeUint(curTime(), 64).endCell() + ); + }); + }); + describe('Actions', () => { + describe('External', () => { + it('should be able to send message to arbitrary address', async () => { + const msgValue = toNano(getRandomInt(1, 10)); + const randomBody = beginCell().storeUint(curTime(), 64).endCell(); + const seqNo = BigInt(await wallet.getSeqno()); + + await assertSendMessages( + 0, + walletId, + curTime() + 1000, + seqNo, + [ + { + message: internal_relaxed({ + to: testWalletBc.address, + value: msgValue, + body: randomBody + }), + mode: defaultExternalMode + }, + { + message: internal_relaxed({ + to: testWalletMc.address, + value: msgValue, + body: randomBody + }), + mode: defaultExternalMode + } + ], + keys.secretKey + ); + + const seqnoAfter = BigInt(await wallet.getSeqno()); + expect(seqnoAfter).toEqual(seqNo + 1n); + }); + it('should reject message with wrong signature', async () => { + const seqNo = await wallet.getSeqno(); + const badKeys = keyPairFromSeed(await getSecureRandomBytes(32)); + + await shouldRejectWith( + wallet.sendMessagesExternal( + walletId, + curTime() + 1000, + seqNo, + badKeys.secretKey, + [mockMessage] + ), + ErrorsV5.invalid_signature + ); + expect(await wallet.getSeqno()).toEqual(seqNo); + + const res = await wallet.sendMessagesExternal( + walletId, + curTime() + 1000, + seqNo, + keys.secretKey, + [mockMessage] + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + aborted: false, + outMessagesCount: 1 + }); + }); + it('should reject external message with prefix other than signed_external', async () => { + // All of the valid ops except acceptable, plus one random + const nonExternalOps = [ + ...validOpCodes.filter(op => op != Opcodes.auth_signed), + 0xdeadbeef + ]; + const seqNo = await wallet.getSeqno(); + const validMsg = WalletV5Test.requestMessage( + false, + walletId, + curTime() + 1000, + BigInt(seqNo), + {} + ); + const msgTail = validMsg.beginParse().skip(32); // skip op; + + for (let op of nonExternalOps) { + const newMsg = WalletV5Test.signRequestMessage( + beginCell().storeUint(op, 32).storeSlice(msgTail).endCell(), + keys.secretKey + ); + await shouldRejectWith( + wallet.sendExternalSignedMessage(newMsg), + ErrorsV5.invalid_message_operation + ); + // Should not change seqno + expect(await wallet.getSeqno()).toEqual(seqNo); + } + + // Validate that original message works + const res = await wallet.sendExternalSignedMessage( + WalletV5Test.signRequestMessage(validMsg, keys.secretKey) + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + aborted: false + }); + expect(await wallet.getSeqno()).toEqual(seqNo + 1); + }); + it('should be able to send up to 255 messages', async () => { + let testMsgs: MessageOut[] = new Array(255); + const seqNo = BigInt(await wallet.getSeqno()); + + for (let i = 0; i < 255; i++) { + testMsgs[i] = { + message: internal_relaxed({ + to: testWalletBc.address, + value: toNano('1'), + body: beginCell().storeUint(i, 32).endCell() + }), + mode: defaultExternalMode + }; + } + + await assertSendMessages( + 0, + walletId, + curTime() + 1000, + seqNo, + testMsgs, + keys.secretKey + ); + }); + it('should be able to send messages with different send modes', async () => { + await testSendModes(false, 0, SendMode.IGNORE_ERRORS, [ + SendMode.NONE, + SendMode.PAY_GAS_SEPARATELY, + SendMode.CARRY_ALL_REMAINING_BALANCE + ]); + }); + it('should reject send modes without IGNORE_ERRORS', async () => { + await testSendModes( + false, + ErrorsV5.external_send_message_must_have_ignore_errors_send_mode, + 0, + [ + SendMode.NONE, + SendMode.PAY_GAS_SEPARATELY, + SendMode.CARRY_ALL_REMAINING_BALANCE, + SendMode.CARRY_ALL_REMAINING_BALANCE | SendMode.DESTROY_ACCOUNT_IF_ZERO + ] + ); + }); + it('should be able to send message with init state', async () => { + await testSendInit( + async args => { + if (!Address.isAddress(args.extra.new_address)) { + throw new TypeError('Callback requires wallet address'); + } + const reqMsg = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(reqMsg); + expect(res.transactions).toHaveTransaction({ + on: args.extra.new_address, + aborted: false, + deploy: true + }); + }, + async args => { + // New wallet should be able to send from new wallet via external + const res = await newWallet.sendMessagesExternal( + args.walletId, + args.valid_until, + args.seqno, + args.key, + [mockMessage] + ); + assertMockMessage(res.transactions, newWallet.address); + } + ); + }); + it('should be able to send external message', async () => { + const seqNo = await wallet.getSeqno(); + const testPayload = BigInt(getRandomInt(0, 100000)); + const testBody = beginCell().storeUint(testPayload, 32).endCell(); + + const res = await wallet.sendMessagesExternal( + walletId, + curTime() + 100, + seqNo, + keys.secretKey, + [ + { + message: { + info: { + type: 'external-out', + createdAt: 0, + createdLt: 0n, + dest: new ExternalAddress(testPayload, 32), + src: null + }, + body: testBody + }, + mode: defaultExternalMode + } + ] + ); + + const txSuccess = findTransactionRequired(res.transactions, { + on: wallet.address, + op: Opcodes.auth_signed, + aborted: false + }); + + expect(txSuccess.externals.length).toBe(1); + + const extOut = txSuccess.externals[0]; + + expect(extOut.info.dest!.value).toBe(testPayload); + expect(extOut.body).toEqualCell(testBody); + }); + it('should reject message with invalid seqno', async () => { + const seqNo = await wallet.getSeqno(); + expect(seqNo).toBeGreaterThan(2); // For better test + const testDelta = getRandomInt(2, seqNo); + + for (let testSeq of [seqNo - 1, seqNo + 1, seqNo + testDelta, seqNo - testDelta]) { + await shouldRejectWith( + wallet.sendMessagesExternal( + walletId, + curTime() + 100, + testSeq, + keys.secretKey, + [mockMessage] + ), + ErrorsV5.invalid_seqno + ); + expect(await wallet.getSeqno()).toEqual(seqNo); + } + }); + it('should reject message with invalid subwallet', async () => { + const seqNo = await wallet.getSeqno(); + const testDelta = BigInt(getRandomInt(2, Number(walletId))); + + for (let testId of [ + walletId - 1n, + walletId + 1n, + walletId - testDelta, + walletId + testDelta + ]) { + await shouldRejectWith( + wallet.sendMessagesExternal( + testId, + curTime() + 100, + seqNo, + keys.secretKey, + [mockMessage] + ), + ErrorsV5.invalid_wallet_id + ); + expect(await wallet.getSeqno()).toEqual(seqNo); + } + }); + it('should reject expired message', async () => { + blockchain.now = curTime(); // Stop ticking + const seqNo = await wallet.getSeqno(); + const testDelta = getRandomInt(1, 10000); + + // We're treating current time as expired. Should we? + for (let testUntil of [blockchain.now, blockchain.now - testDelta]) { + await shouldRejectWith( + wallet.sendMessagesExternal(walletId, testUntil, seqNo, keys.secretKey, [ + mockMessage + ]), + ErrorsV5.expired + ); + expect(await wallet.getSeqno()).toEqual(seqNo); + } + + const res = await wallet.sendMessagesExternal( + walletId, + blockchain.now + 1, + seqNo, + keys.secretKey, + [mockMessage] + ); + + assertMockMessage(res.transactions); + expect(await wallet.getSeqno()).toEqual(seqNo + 1); + }); + it('should reject set_code action', async () => { + await testSetCode( + async args => { + if (args.actions == undefined || args.key == undefined) { + throw new Error('Actions and keys are required'); + } + const setCodeRequest = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(setCodeRequest); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + outMessagesCount: 0, + success: true, // Because of commit call + exitCode: 9 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + }, + async args => { + if (args.actions == undefined || args.key == undefined) { + throw new Error('Actions and keys are required'); + } + const sendJustMessages = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(sendJustMessages); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + aborted: false, + outMessagesCount: 254, + exitCode: 0 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + } + ); + }); + it('should be able to add extension', async () => { + await testAddExt(async args => { + const reqMsg = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(reqMsg); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + outMessagesCount: 0, + exitCode: 0 // Because of commit we can't rely on compute phase status + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + }); + + hasExtension = blockchain.snapshot(); + }); + it('should not be able to install already installed extendsion', async () => { + await testAddExtAlreadyIn(async args => { + const reqMsg = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(reqMsg); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + outMessagesCount: 0, + exitCode: ErrorsV5.add_extension + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + }); + }); + it('should not be able to install extension from different chain', async () => { + await testAddExtWrongChain( + async args => { + const reqMsg = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(reqMsg); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + outMessagesCount: 0, + exitCode: ErrorsV5.extension_wrong_workchain + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + }, + async args => { + const reqMsg = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await newWallet.sendExternalSignedMessage(reqMsg); + expect(res.transactions).toHaveTransaction({ + on: newWallet.address, + op: Opcodes.auth_signed, + outMessagesCount: 0, + exitCode: 0 // We're good now + }); + expect(await newWallet.getSeqno()).toEqual(Number(args.seqno) + 1); + } + ); + }); + it('should be able to remove extension', async () => { + await testRemoveExt(async args => { + const reqMsg = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(reqMsg); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + outMessagesCount: 0, + exitCode: 0 // Because of commit we can't rely on compute phase status + }); + }); + }); + it('should throw on removing non-existent extension', async () => { + await testRemoveExtNonExistent(async args => { + const reqMsg = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(reqMsg); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + outMessagesCount: 0, + exitCode: ErrorsV5.remove_extension + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + }); + }); + // Doesn't make much sense, since inderectly tested in too many places + it.skip('empty action list should increase seqno', async () => { + const seqNo = await wallet.getSeqno(); + const testMsg = WalletV5Test.requestMessage( + false, + walletId, + curTime() + 100, + seqNo, + {}, + keys.secretKey + ); + const res = await wallet.sendExternalSignedMessage(testMsg); + + expect(await wallet.getSeqno()).toEqual(seqNo + 1); + }); + it('should be able to add/remove extensions and send messages in one go', async () => { + await testAddRemoveSend(async args => { + const reqMsg = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(reqMsg); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + aborted: false, + outMessagesCount: 255 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + }); + }); + }); + describe('Internal', () => { + it('should be able to send message to arbitrary address', async () => { + const msgValue = toNano(getRandomInt(1, 10)); + const randomBody = beginCell().storeUint(curTime(), 64).endCell(); + const seqNo = BigInt(await wallet.getSeqno()); + + const res = await assertSendMessages( + 0, + walletId, + curTime() + 1000, + seqNo, + [ + { + message: internal_relaxed({ + to: testWalletBc.address, + value: msgValue, + body: randomBody + }), + mode: SendMode.PAY_GAS_SEPARATELY + }, + { + message: internal_relaxed({ + to: testWalletMc.address, + value: msgValue, + body: randomBody + }), + mode: SendMode.PAY_GAS_SEPARATELY + } + ], + keys.secretKey, + owner.getSender() + ); + + const seqnoAfter = BigInt(await wallet.getSeqno()); + expect(seqnoAfter).toEqual(seqNo + 1n); + }); + it('should ignore message with wrong signature', async () => { + const seqNo = await wallet.getSeqno(); + const badKeys = keyPairFromSeed(await getSecureRandomBytes(32)); + const stateBefore = await getWalletData(); + + const msgActions = [message2action(mockMessage)]; + let testMsg = WalletV5Test.requestMessage( + true, + walletId, + curTime() + 100, + seqNo, + { wallet: msgActions }, + badKeys.secretKey + ); + let res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: testMsg + }); + assertInternal(res.transactions, owner.address, 0); + expect(await getWalletData()).toEqualCell(stateBefore); + + testMsg = WalletV5Test.requestMessage( + true, + walletId, + curTime() + 100, + seqNo, + { wallet: msgActions }, + keys.secretKey + ); + res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: testMsg + }); + assertMockMessage(res.transactions); + expect(await wallet.getSeqno()).toEqual(seqNo + 1); + }); + + it('should bounce message with invalid subwallet', async () => { + const seqNo = await wallet.getSeqno(); + const testDelta = BigInt(getRandomInt(2, Number(walletId))); + const stateBefore = await getWalletData(); + + for (let testId of [ + walletId - 1n, + walletId + 1n, + walletId - testDelta, + walletId + testDelta + ]) { + const res = await wallet.sendMessagesInternal( + owner.getSender(), + testId, + curTime() + 100, + seqNo, + keys.secretKey, + [mockMessage] + ); + assertInternal(res.transactions, owner.address, ErrorsV5.invalid_wallet_id); + expect(await getWalletData()).toEqualCell(stateBefore); + } + }); + it('should bounce expired message', async () => { + blockchain.now = curTime(); // Stop ticking + const seqNo = await wallet.getSeqno(); + const testDelta = getRandomInt(1, 10000); + const stateBefore = await getWalletData(); + + // We're treating current time as expired. Should we? + for (let testUntil of [blockchain.now, blockchain.now - testDelta]) { + const res = await wallet.sendMessagesInternal( + owner.getSender(), + walletId, + testUntil, + seqNo, + keys.secretKey, + [mockMessage] + ); + assertInternal(res.transactions, owner.address, ErrorsV5.expired); + expect(await getWalletData()).toEqualCell(stateBefore); + } + + const res = await wallet.sendMessagesExternal( + walletId, + blockchain.now + 1, + seqNo, + keys.secretKey, + [mockMessage] + ); + assertMockMessage(res.transactions); + expect(await wallet.getSeqno()).toEqual(seqNo + 1); + }); + it('should reject set_code action', async () => { + await testSetCode( + async args => { + const setCodeRequest = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: setCodeRequest + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed_internal, + outMessagesCount: 1, // bounce + aborted: true, + success: false, // No commit anymore + exitCode: 9 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno)); // On internal seqno is not commited + }, + async args => { + if (args.actions == undefined || args.key == undefined) { + throw new Error('Actions and keys are required'); + } + const sendJustMessages = WalletV5Test.requestMessage( + false, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExternalSignedMessage(sendJustMessages); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + aborted: false, + outMessagesCount: 254, + exitCode: 0 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + } + ); + }); + it('should bounce message with invalid seqno', async () => { + const seqNo = await wallet.getSeqno(); + expect(seqNo).toBeGreaterThan(2); // For better test + const testDelta = getRandomInt(2, seqNo); + const stateBefore = await getWalletData(); + + for (let testSeq of [seqNo - 1, seqNo + 1, seqNo + testDelta, seqNo - testDelta]) { + const res = await wallet.sendMessagesInternal( + owner.getSender(), + walletId, + curTime() + 100, + testSeq, + keys.secretKey, + [mockMessage] + ); + assertInternal(res.transactions, owner.address, ErrorsV5.invalid_seqno); + expect(await getWalletData()).toEqualCell(stateBefore); + } + }); + it('should ignore internal message with prefix other than signed_internal', async () => { + // All of the valid ops except acceptable, plus one random + const nonExternalOps = [ + ...validOpCodes.filter(op => op != Opcodes.auth_signed_internal), + 0xdeadbeef + ]; + const seqNo = await wallet.getSeqno(); + // Not yet signed + const validMsg = WalletV5Test.requestMessage( + true, + walletId, + curTime() + 1000, + BigInt(seqNo), + {} + ); + const msgTail = validMsg.beginParse().skip(32); // skip op; + + for (let op of nonExternalOps) { + const newMsg = WalletV5Test.signRequestMessage( + beginCell().storeUint(op, 32).storeSlice(msgTail).endCell(), + keys.secretKey + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: newMsg + }); + assertInternal(res.transactions, owner.address, 0); // return no bounce + // Should not change seqno + expect(await wallet.getSeqno()).toEqual(seqNo); + } + + // Validate that original message works + const successMsg = WalletV5Test.signRequestMessage(validMsg, keys.secretKey); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: successMsg + }); + assertInternal(res.transactions, owner.address, 0); + expect(await wallet.getSeqno()).toEqual(seqNo + 1); + }); + it('should ignore internal message with correct prefix, but incorrect length', async () => { + const seqNo = await wallet.getSeqno(); + // So we have message with bad wallet id + const badMsg = WalletV5Test.requestMessage( + true, + walletId - 1n, + curTime() + 1000, + BigInt(seqNo), + {}, + keys.secretKey + ); + + // Now we have it's truncated version + const msgTrunc = beginCell() + .storeBits(badMsg.beginParse().loadBits(badMsg.bits.length - 10)) + .endCell(); // off by one + + let res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: msgTrunc + }); + // Now, because it's truncated it gets ignored + assertInternal(res.transactions, owner.address, 0); + + res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: badMsg + }); + assertInternal(res.transactions, owner.address, ErrorsV5.invalid_wallet_id); + // If we send it as is, the subwallet exception will trigger + }); + it('should be able to send up to 255 messages', async () => { + let testMsgs: MessageOut[] = new Array(255); + const seqNo = BigInt(await wallet.getSeqno()); + + for (let i = 0; i < 255; i++) { + testMsgs[i] = { + message: internal_relaxed({ + to: testWalletBc.address, + value: toNano('1'), + body: beginCell().storeUint(i, 32).endCell() + }), + mode: defaultExternalMode + }; + } + await assertSendMessages( + 0, + walletId, + curTime() + 1000, + seqNo, + testMsgs, + keys.secretKey + ); + }); + it('should be able to send message with init state', async () => { + await testSendInit( + async args => { + if (!Address.isAddress(args.extra.new_address)) { + throw new TypeError('Callback requires wallet address'); + } + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: reqMsg + }); + + expect(res.transactions).toHaveTransaction({ + on: args.extra.new_address, + aborted: false, + deploy: true + }); + }, + async args => { + // New wallet should be able to send from new wallet via external + const res = await newWallet.sendMessagesInternal( + owner.getSender(), + args.walletId, + args.valid_until, + args.seqno, + args.key, + [mockMessage], + toNano('1') + ); + assertMockMessage(res.transactions, newWallet.address); + } + ); + }); + it('should be able to send external message', async () => { + const seqNo = await wallet.getSeqno(); + const testPayload = BigInt(getRandomInt(0, 100000)); + const testBody = beginCell().storeUint(testPayload, 32).endCell(); + + const res = await wallet.sendMessagesInternal( + owner.getSender(), + walletId, + curTime() + 100, + seqNo, + keys.secretKey, + [ + { + message: { + info: { + type: 'external-out', + createdAt: 0, + createdLt: 0n, + dest: new ExternalAddress(testPayload, 32), + src: null + }, + body: testBody + }, + mode: defaultExternalMode + } + ] + ); + + const txSuccess = findTransactionRequired(res.transactions, { + on: wallet.address, + from: owner.address, + op: Opcodes.auth_signed_internal, + aborted: false + }); + + expect(txSuccess.externals.length).toBe(1); + + const extOut = txSuccess.externals[0]; + + expect(extOut.info.dest!.value).toBe(testPayload); + expect(extOut.body).toEqualCell(testBody); + }); + + it('should be able to send messages with various send modes', async () => { + // Internal should work with + await testSendModes(true, 0, SendMode.IGNORE_ERRORS, [ + SendMode.NONE, + SendMode.PAY_GAS_SEPARATELY, + SendMode.CARRY_ALL_REMAINING_INCOMING_VALUE, + SendMode.CARRY_ALL_REMAINING_BALANCE + ]); + // And without IGNORE_ERRORS + await testSendModes(true, 0, 0, [ + SendMode.NONE, + SendMode.PAY_GAS_SEPARATELY, + SendMode.CARRY_ALL_REMAINING_INCOMING_VALUE, + SendMode.CARRY_ALL_REMAINING_BALANCE + ]); + }); + it('should bounce on set_code action', async () => { + await testSetCode( + async args => { + if ( + args.actions == undefined || + args.key == undefined || + args.prevState == undefined + ) { + throw new Error('Actions keys and state are required'); + } + const setCodeRequest = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: setCodeRequest + }); + assertInternal(res.transactions, owner.address, 9); + expect(await getWalletData()).toEqualCell(args.prevState); + }, + async args => { + if ( + args.actions == undefined || + args.key == undefined || + args.prevState == undefined + ) { + throw new Error('Actions keys and state are required'); + } + const sendJustMessages = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: sendJustMessages + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed_internal, + aborted: false, + outMessagesCount: 254, + exitCode: 0 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + } + ); + }); + it('should be able to add extension', async () => { + await loadFrom(initialState); + await testAddExt(async args => { + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: reqMsg + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: owner.address, + op: Opcodes.auth_signed_internal, + outMessagesCount: 0, + aborted: false, + exitCode: 0 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + }); + hasExtension = blockchain.snapshot(); + }); + it('should not be able to install already installed extendsion', async () => { + await testAddExtAlreadyIn(async args => { + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: reqMsg + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: owner.address, + op: Opcodes.auth_signed_internal, + aborted: true, + outMessagesCount: 1, + exitCode: ErrorsV5.add_extension + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno)); + }); + }); + it('should not be able to install extension from different chain', async () => { + await loadFrom(hasMcWallet); + await testAddExtWrongChain( + async args => { + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: reqMsg + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: owner.address, + op: Opcodes.auth_signed_internal, + outMessagesCount: 1, + aborted: true, + exitCode: ErrorsV5.extension_wrong_workchain + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno)); + }, + async args => { + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await newWallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: reqMsg + }); + expect(res.transactions).toHaveTransaction({ + on: newWallet.address, + from: owner.address, + op: Opcodes.auth_signed_internal, + outMessagesCount: 0, + aborted: false + }); + expect(await newWallet.getSeqno()).toEqual(Number(args.seqno) + 1); + } + ); + }); + it('should be able to remove extension', async () => { + await testRemoveExt(async args => { + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: reqMsg + }); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: owner.address, + op: Opcodes.auth_signed_internal, + outMessagesCount: 0, + aborted: false + }); + }); + }); + it('should throw on removing non-existent extension', async () => { + await testRemoveExtNonExistent(async args => { + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: reqMsg + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed_internal, + outMessagesCount: 1, + aborted: true, + exitCode: ErrorsV5.remove_extension + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno)); + }); + }); + it('should be able to add/remove extensions and send messages in one go', async () => { + await testAddRemoveSend(async args => { + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: reqMsg + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed_internal, + aborted: false, + outMessagesCount: 255 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno) + 1); + }); + }); + // describe('Bounce', () => { + // it('should ignore bounced mesages', async () => { + // await loadFrom(hasExtension); + // const seqNo = await wallet.getSeqno(); + // const mockActions: WalletActions = { wallet: [message2action(mockMessage)] }; + // + // // Note that in reality bounce gets prefixed by 0xFFFFFFFF + // // With current code, that would mean message would be ignored + // // due to op check + // // However we still could test as if TVM doesn't add prefix to bounce somehow + // const intReq = WalletV5Test.requestMessage( + // true, + // walletId, + // curTime() + 1000, + // seqNo, + // mockActions, + // keys.secretKey + // ); + // const extReq = WalletV5Test.extensionMessage(mockActions); + // // ihr_disable and bounce flags combinations + // let flagFuzz = [ + // [false, false], + // [true, false], + // [false, true], + // [true, true] + // ]; + // + // const stateBefore = await getWalletData(); + // + // for (let flags of flagFuzz) { + // let res = await blockchain.sendMessage( + // internal({ + // from: owner.address, + // to: wallet.address, + // body: intReq, + // value: toNano('1'), + // bounced: true, + // ihrDisabled: flags[0], + // bounce: flags[1] + // }) + // ); + // expect(res.transactions).toHaveTransaction({ + // on: wallet.address, + // op: Opcodes.auth_signed_internal, + // aborted: false, + // outMessagesCount: 0 + // }); + // expect(await getWalletData()).toEqualCell(stateBefore); + // + // res = await blockchain.sendMessage( + // internal({ + // from: testExtensionBc.address, + // to: wallet.address, + // body: extReq, + // value: toNano('1'), + // bounced: true, + // ihrDisabled: flags[0], + // bounce: flags[1] + // }) + // ); + // expect(res.transactions).toHaveTransaction({ + // on: wallet.address, + // op: Opcodes.auth_extension, + // aborted: false, + // outMessagesCount: 0 + // }); + // expect(await getWalletData()).toEqualCell(stateBefore); + // } + // + // // Let's proove that bounce flag is the reason + // const resInt = await blockchain.sendMessage( + // internal({ + // from: owner.address, + // to: wallet.address, + // body: intReq, + // value: toNano('1'), + // ihrDisabled: true, + // bounce: true + // }) + // ); + // assertMockMessage(resInt.transactions); + // + // const resExt = await blockchain.sendMessage( + // internal({ + // from: testExtensionBc.address, + // to: wallet.address, + // body: extReq, + // value: toNano('1'), + // ihrDisabled: false, + // bounce: false + // }) + // ); + // assertMockMessage(resExt.transactions); + // }); + // }); + }); + describe('Extension', () => { + let actionFuzz: WalletActions[]; + beforeAll(async () => { + actionFuzz = [ + { wallet: [message2action(mockMessage)] }, + { wallet: someMessages(10) }, + { extended: someExtensions(5, 'add_extension') }, + { wallet: someMessages(5), extended: someExtensions(5, 'add_extension') }, + { extended: [{ type: 'remove_extension', address: testExtensionBc.address }] }, + { + wallet: someMessages(5), + extended: [{ type: 'remove_extension', address: testExtensionBc.address }] + }, + { extended: [{ type: 'sig_auth', allowed: false }] }, + { wallet: someMessages(5), extended: [{ type: 'sig_auth', allowed: false }] } + ]; + + await loadFrom(hasExtension); + }); + + it('should be able to send message to arbitrary address', async () => { + const msgValue = toNano(getRandomInt(1, 10)); + const randomBody = beginCell().storeUint(curTime(), 64).endCell(); + + await assertSendMessages( + 0, + walletId, + curTime() + 1000, + 0, + [ + { + message: internal_relaxed({ + to: testWalletBc.address, + value: msgValue, + body: randomBody + }), + mode: SendMode.PAY_GAS_SEPARATELY + }, + { + message: internal_relaxed({ + to: testWalletMc.address, + value: msgValue, + body: randomBody + }), + mode: SendMode.PAY_GAS_SEPARATELY + } + ], + keys.secretKey, + extensionSender + ); + }); + it('extension action is only allowed from installed extension address', async () => { + const differentExt = await blockchain.treasury('Not installed'); + + const stateBefore = await getWalletData(); + + for (let testSender of [owner, differentExt]) { + for (let actions of actionFuzz) { + const res = await wallet.sendExtensionActions( + testSender.getSender(), + actions + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testSender.address, + op: Opcodes.auth_extension, + aborted: false, + outMessagesCount: 0 + }); + expect(await getWalletData()).toEqualCell(stateBefore); + } + } + + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + actionFuzz[0] + ); + + assertMockMessage(res.transactions); + expect(await getWalletData()).toEqualCell(stateBefore); + }); + it('extension request with same hash from different workchain should be ignored', async () => { + // Those should completely equal by hash + expect(testExtensionBc.address.hash.equals(testExtensionMc.address.hash)).toBe( + true + ); + + // Extension with such has is installed + const curExt = await wallet.getExtensionsArray(); + expect( + curExt.findIndex(e => e.hash.equals(testExtensionMc.address.hash)) + ).toBeGreaterThanOrEqual(0); + + const stateBefore = await getWalletData(); + + for (let actions of actionFuzz) { + const res = await wallet.sendExtensionActions( + testExtensionMc.getSender(), + actions + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionMc.address, + op: Opcodes.auth_extension, + aborted: false, + outMessagesCount: 0 + }); + expect(await getWalletData()).toEqualCell(stateBefore); + } + }); + it('should be able to send up to 255 messages', async () => { + let testMsgs: MessageOut[] = new Array(255); + const seqNo = BigInt(await wallet.getSeqno()); + + for (let i = 0; i < 255; i++) { + testMsgs[i] = { + message: internal_relaxed({ + to: testWalletBc.address, + value: toNano('1'), + body: beginCell().storeUint(i, 32).endCell() + }), + mode: defaultExternalMode + }; + } + await assertSendMessages( + 0, + walletId, + curTime() + 1000, + seqNo, + testMsgs, + keys.secretKey, + extensionSender + ); + }); + it('should be able to send messages with various send modes', async () => { + let modeSet = [ + SendMode.NONE, + SendMode.PAY_GAS_SEPARATELY, + SendMode.CARRY_ALL_REMAINING_INCOMING_VALUE, + SendMode.CARRY_ALL_REMAINING_BALANCE + ]; + await testSendModes(true, 0, SendMode.IGNORE_ERRORS, modeSet, extensionSender); + // And without IGNORE_ERRORS + await testSendModes(true, 0, 0, modeSet, extensionSender); + }); + it('should be able to send message with init state', async () => { + await testSendInit( + async args => { + if (!Address.isAddress(args.extra.new_address)) { + throw new TypeError('Callback requires wallet address'); + } + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions + ); + + expect(res.transactions).toHaveTransaction({ + on: args.extra.new_address, + aborted: false, + deploy: true + }); + }, + async args => { + // Couldn't think of any better + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + { + wallet: [message2action(mockMessage)] + }, + args.key + ); + + const testMsg = internal_relaxed({ + to: newWallet.address, + value: toNano('2'), + body: reqMsg + }); + + const res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + wallet: [ + { + type: 'sendMsg', + mode: SendMode.PAY_GAS_SEPARATELY, + outMsg: testMsg + } + ] + }); + + // So tx chain ext->basechain wallet->mc wallet->mock message + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: false, + outMessagesCount: 1 + }); + expect(res.transactions).toHaveTransaction({ + on: newWallet.address, + from: wallet.address, + op: Opcodes.auth_signed_internal, + aborted: false, + outMessagesCount: 1 + }); + // Finally mock messages goes live + assertMockMessage(res.transactions, newWallet.address); + } + ); + }); + it('should be able to send external message', async () => { + const testPayload = BigInt(getRandomInt(0, 100000)); + const testBody = beginCell().storeUint(testPayload, 32).endCell(); + + const res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + wallet: [ + { + type: 'sendMsg', + mode: SendMode.NONE, + outMsg: { + info: { + type: 'external-out', + createdAt: 0, + createdLt: 0n, + dest: new ExternalAddress(testPayload, 32), + src: null + }, + body: testBody + } + } + ] + }); + + const txSuccess = findTransactionRequired(res.transactions, { + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: false + }); + + expect(txSuccess.externals.length).toBe(1); + + const extOut = txSuccess.externals[0]; + + expect(extOut.info.dest!.value).toBe(testPayload); + expect(extOut.body).toEqualCell(testBody); + }); + it('should bounce set_code action', async () => { + await testSetCode( + async args => { + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_extension, + outMessagesCount: 1, // bounce + aborted: true, + success: false, // No commit anymore + exitCode: 9 + }); + expect(await wallet.getSeqno()).toEqual(Number(args.seqno)); // On internal seqno is not commited + }, + async args => { + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: false, + outMessagesCount: 254, + exitCode: 0 + }); + } + ); + }); + it('should be able to add extension', async () => { + const randomExtAddres = randomAddress(); + await testAddExt(async args => { + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + outMessagesCount: 0, + aborted: false, + exitCode: 0 + }); + }, randomExtAddres); + }); + it('should not be able to install already installed extendsion', async () => { + await testAddExtAlreadyIn(async args => { + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: true, + outMessagesCount: 1, + exitCode: ErrorsV5.add_extension + }); + }); + }); + it('should not be able to install extension from different chain', async () => { + await loadFrom(hasMcWallet); + await testAddExtWrongChain( + async args => { + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions, + toNano('1') + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + outMessagesCount: 1, + aborted: true, + exitCode: ErrorsV5.extension_wrong_workchain + }); + }, + async args => { + const reqMsg = WalletV5Test.requestMessage( + true, + args.walletId, + args.valid_until, + args.seqno, + args.actions, + args.key + ); + const testMsg = internal_relaxed({ + to: newWallet.address, + value: toNano('2'), + body: reqMsg + }); + + const res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + wallet: [ + { + type: 'sendMsg', + mode: SendMode.PAY_GAS_SEPARATELY, + outMsg: testMsg + } + ] + }); + // So via extension we've sent signed add_extension message + // through our wallet to the masterchain wallet + // And it should end up being installed + expect(res.transactions).toHaveTransaction({ + on: newWallet.address, + from: wallet.address, + op: Opcodes.auth_signed_internal, + outMessagesCount: 0, + aborted: false + }); + } + ); + }); + it('should be able to remove extension', async () => { + await testRemoveExt(async args => { + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions, + toNano('1') + ); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + outMessagesCount: 0, + aborted: false + }); + }); + }); + it('should throw on removing non-existent extension', async () => { + await testRemoveExtNonExistent(async args => { + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + outMessagesCount: 1, + aborted: true, + exitCode: ErrorsV5.remove_extension + }); + }); + }); + it('should be able to add/remove extensions and send messages in one go', async () => { + await testAddRemoveSend(async args => { + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + args.actions, + toNano('1') + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: false, + outMessagesCount: 255 + }); + }); + }); + }); + describe('Malformed action list', () => { + it('action list exceeding 255 elements should be rejected', async () => { + await loadFrom(hasExtension); + let seqNo = await wallet.getSeqno(); + let tooMuch = someMessages(256); + const extReq = WalletV5Test.requestMessage( + false, + walletId, + curTime() + 100, + seqNo, + { wallet: tooMuch }, + keys.secretKey + ); + + let res = await wallet.sendExternalSignedMessage(extReq); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + outMessagesCount: 0, + exitCode: ErrorsV5.invalid_c5 + }); + expect(await wallet.getSeqno()).toEqual(++seqNo); + + const intReq = WalletV5Test.requestMessage( + true, + walletId, + curTime() + 100, + seqNo, + { wallet: tooMuch }, + keys.secretKey + ); + res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: intReq + }); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed_internal, + aborted: true, + outMessagesCount: 1, + exitCode: ErrorsV5.invalid_c5 + }); + + res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + { wallet: tooMuch }, + toNano('1') + ); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_extension, + aborted: true, + outMessagesCount: 1, + exitCode: ErrorsV5.invalid_c5 + }); + }); + it('should reject action list with extra data/refs', async () => { + let seqNo = await wallet.getSeqno(); + const testActionRaw = beginCell().store( + storeOutAction({ + type: 'sendMsg', + mode: SendMode.PAY_GAS_SEPARATELY, + outMsg: mockMessage.message + }) + ); + + const ds = testActionRaw.asSlice(); + ds.loadRef(); // Drop one + const noRef = beginCell().storeSlice(ds).endCell(); + const excessiveData = beginCell() + .storeSlice(testActionRaw.asSlice()) + .storeBit(true) + .endCell(); + const truncated = beginCell() + .storeBits(testActionRaw.asSlice().loadBits(testActionRaw.bits - 1)) + .endCell(); + const extraRef = beginCell() + .storeSlice(testActionRaw.asSlice()) + .storeRef(beginCell().storeUint(0x0ec3c86d, 32).endCell()) + .endCell(); + + const origActions = beginCell() + .storeRef(beginCell().endCell()) + .storeSlice(testActionRaw.asSlice()) + .endCell(); + + for (let payload of [excessiveData, truncated, extraRef, noRef]) { + const actionList = beginCell() + .storeRef(beginCell().endCell()) + .storeSlice(payload.asSlice()) + .endCell(); + + const intReq = WalletV5Test.requestMessage( + true, + walletId, + curTime() + 100, + seqNo, + { wallet: actionList }, + keys.secretKey + ); + let res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: intReq + }); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed_internal, + aborted: true, + outMessagesCount: 1, + exitCode: ErrorsV5.invalid_c5 + }); + + const extReq = WalletV5Test.requestMessage( + false, + walletId, + curTime() + 100, + seqNo, + { wallet: actionList }, + keys.secretKey + ); + res = await wallet.sendExternalSignedMessage(extReq); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_signed, + aborted: false, + outMessagesCount: 0, + exitCode: ErrorsV5.invalid_c5 + }); + expect(await wallet.getSeqno()).toEqual(++seqNo); + + res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + { wallet: actionList }, + toNano('1') + ); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + op: Opcodes.auth_extension, + aborted: true, + outMessagesCount: 1, + exitCode: ErrorsV5.invalid_c5 + }); + } + + const intReq = WalletV5Test.requestMessage( + true, + walletId, + curTime() + 100, + seqNo, + { wallet: origActions }, + keys.secretKey + ); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: intReq + }); + assertMockMessage(res.transactions); + }); + }); + }); + describe('Signature auth', () => { + type OwnerArguments = { walletId: bigint; seqno: number | bigint; key: Buffer }; + let signatureDisabled: BlockchainSnapshot; + let signatureEnabled: BlockchainSnapshot; + let multipleExtensions: BlockchainSnapshot; + /* + let testRemoveExtension: (exp: number, + reqType:RequestType, + extension: Address, + via: Sender, commonArgs: OwnerArguments) => Promise; + */ + beforeAll(async () => { + await loadFrom(hasExtension); + }); + + it('extension should be able to set signature mode', async () => { + const seqNo = await wallet.getSeqno(); + const allowedBefore = await wallet.getIsSignatureAuthAllowed(); + expect(allowedBefore).toBe(-1); + signatureEnabled = blockchain.snapshot(); + + let res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + extended: [ + { + type: 'sig_auth', + allowed: false + } + ] + }); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: false + }); + expect(await wallet.getIsSignatureAuthAllowed()).toBe(0); + expect(await wallet.getSeqno()).toEqual(seqNo); + signatureDisabled = blockchain.snapshot(); + + res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + extended: [ + { + type: 'sig_auth', + allowed: true + } + ] + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: false + }); + expect(await wallet.getIsSignatureAuthAllowed()).toBe(-1); + expect(await wallet.getSeqno()).toEqual(seqNo); + signatureEnabled = blockchain.snapshot(); // Usefull? + }); + it('should reject atempt to change sig auth via internal/external request', async () => { + let seqNo = await wallet.getSeqno(); + + const disableSigAuth: ExtendedAction = { + type: 'sig_auth', + allowed: false + }; + const enableSigAuth: ExtendedAction = { + type: 'sig_auth', + allowed: true + }; + + const mockExtensions = someExtensions(100, 'add_extension'); + const randIdx = getRandomInt(0, mockExtensions.length - 2); + + /* + const msgInt = WalletV5Test.requestMessage(true, walletId, curTime() + 100, seqNo, testWalletAction, keys.secretKey); + const msgExt = WalletV5Test.requestMessage(false, walletId, curTime() + 100, seqNo, testWalletAction, keys.secretKey); + */ + + //const res = await wallet.sendExtensionActions(testExtensionBc.getSender(), testWalletActions); + + // const fromInt = async (message) => await wallet.sendInternalSignedMessage(owner.getSender(), {value: toNano('1'), body: message}); + // const fromExt = async (message) => await wallet.sendExternalSignedMessage(message); + + for (let action of [disableSigAuth, enableSigAuth]) { + const actionSingle = [action]; + const actionFirst = [action, ...mockExtensions]; + const actionLast = [...mockExtensions, action]; + const actionRandom = [...mockExtensions]; + actionRandom[randIdx] = action; + + for (let actionSet of [actionSingle, actionFirst, actionLast, actionRandom]) { + const msgInt = WalletV5Test.requestMessage( + true, + walletId, + curTime() + 100, + seqNo, + { extended: actionSet }, + keys.secretKey + ); + const msgExt = WalletV5Test.requestMessage( + false, + walletId, + curTime() + 100, + seqNo, + { extended: actionSet }, + keys.secretKey + ); + // Meh, kinda much + for (let testMsg of [msgInt, msgExt]) { + if (testMsg == msgInt) { + const stateBefore = await getWalletData(); + const res = await wallet.sendInternalSignedMessage(owner.getSender(), { + value: toNano('1'), + body: msgInt + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + aborted: true, + exitCode: ErrorsV5.only_extension_can_change_signature_mode + }); + expect(await getWalletData()).toEqualCell(stateBefore); + } else { + const res = await wallet.sendExternalSignedMessage(msgExt); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + aborted: false, + exitCode: ErrorsV5.only_extension_can_change_signature_mode + }); + expect(await wallet.getSeqno()).toEqual(++seqNo); + } + } + } + } + }); + it('should reject sig auth if mode is already set', async () => { + let i = 0; + for (let testState of [signatureDisabled, signatureEnabled]) { + await loadFrom(testState); + let stateBefore = await getWalletData(); + let res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + extended: [ + { + type: 'sig_auth', + allowed: Boolean(i++) + } + ] + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: true, + exitCode: ErrorsV5.this_signature_mode_already_set + }); + expect(await getWalletData()).toEqualCell(stateBefore); + } + }); + it('should not accept signed external when signature auth is disabled and extension present', async () => { + await loadFrom(signatureDisabled); + const seqNo = await wallet.getSeqno(); + await shouldRejectWith( + wallet.sendMessagesExternal(walletId, curTime() + 100, seqNo, keys.secretKey, [ + mockMessage + ]), + ErrorsV5.signature_disabled + ); + expect(await wallet.getSeqno()).toEqual(seqNo); + }); + it('should not accept signed internal when signature auth is disabled and exension is present', async () => { + await loadFrom(signatureDisabled); + const seqNo = await wallet.getSeqno(); + await assertSendMessages( + ErrorsV5.signature_disabled, + walletId, + curTime() + 100, + seqNo, + [mockMessage], + keys.secretKey, + owner.getSender() + ); + }); + it('extension should be able to add another extension when sig auth is disabled', async () => { + await loadFrom(signatureDisabled); + const testExtAddr = randomAddress(); + const stateBefore = await getWalletData(); + const extBefore = await wallet.getExtensionsArray(); + expect(extBefore.length).toBe(1); + + const res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + extended: [{ type: 'add_extension', address: testExtAddr }] + }); + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: false + }); + + const extAfter = await wallet.getExtensionsArray(); + expect(extAfter.length).toBe(2); + expect(extAfter.findIndex(a => a.equals(testExtAddr))).toBeGreaterThanOrEqual(0); + + multipleExtensions = blockchain.snapshot(); + }); + it('should not allow to remove last extension when sig auth is disabled', async () => { + await loadFrom(signatureDisabled); + + const stateBefore = await getWalletData(); + const extBefore = await wallet.getExtensionsArray(); + expect(extBefore.length).toBe(1); + + let res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + extended: [ + { + type: 'remove_extension', + address: extBefore[0] + } + ] + }); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: true, + exitCode: ErrorsV5.remove_last_extension_when_signature_disabled + }); + expect(await getWalletData()).toEqualCell(stateBefore); + }); + it('should remove extension if sig auth disabled and at lease one left', async () => { + await loadFrom(multipleExtensions); + + const extBefore = await wallet.getExtensionsArray(); + expect(extBefore.length).toBeGreaterThan(1); + + const pickExt = extBefore[getRandomInt(0, extBefore.length - 1)]; + + const res = await wallet.sendExtensionActions(testExtensionBc.getSender(), { + extended: [{ type: 'remove_extension', address: pickExt }] + }); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + from: testExtensionBc.address, + op: Opcodes.auth_extension, + aborted: false + }); + + const extAfter = await wallet.getExtensionsArray(); + expect(extAfter.length).toBe(extBefore.length - 1); + expect(extAfter.findIndex(a => a.equals(pickExt))).toBe(-1); + }); + it('should not allow to remove last extension and then disable sig auth', async () => { + await loadFrom(signatureEnabled); + const stateBefore = await getWalletData(); + const testWalletActions: WalletActions = { + extended: [ + { type: 'remove_extension', address: testExtensionBc.address }, + { type: 'sig_auth', allowed: false } + ] + }; + + const res = await wallet.sendExtensionActions( + testExtensionBc.getSender(), + testWalletActions + ); + + expect(res.transactions).toHaveTransaction({ + on: wallet.address, + aborted: true, + exitCode: ErrorsV5.disable_signature_when_extensions_is_empty + }); + + expect(await getWalletData()).toEqualCell(stateBefore); + }); + }); +}); diff --git a/tests/actions.ts b/tests/actions.ts index 6212e60..cacba0b 100644 --- a/tests/actions.ts +++ b/tests/actions.ts @@ -1,4 +1,4 @@ -import { Address, beginCell, Cell, MessageRelaxed, SendMode, storeMessageRelaxed } from 'ton-core'; +import { Address, beginCell, Cell, MessageRelaxed, SendMode, storeMessageRelaxed } from '@ton/core'; import { isTestOnlyExtendedAction, TestOnlyExtendedAction, TestOnlyOutAction } from './test-only-actions'; export class ActionSendMsg { @@ -18,31 +18,31 @@ export class ActionSendMsg { } export class ActionAddExtension { - public static readonly tag = 0x1c40db9f; + public static readonly tag = 0x02; public readonly tag = ActionAddExtension.tag; constructor(public readonly address: Address) {} public serialize(): Cell { - return beginCell().storeUint(this.tag, 32).storeAddress(this.address).endCell(); + return beginCell().storeUint(this.tag, 8).storeAddress(this.address).endCell(); } } export class ActionRemoveExtension { - public static readonly tag = 0x5eaef4a4; + public static readonly tag = 0x03; public readonly tag = ActionRemoveExtension.tag; constructor(public readonly address: Address) {} public serialize(): Cell { - return beginCell().storeUint(this.tag, 32).storeAddress(this.address).endCell(); + return beginCell().storeUint(this.tag, 8).storeAddress(this.address).endCell(); } } export class ActionSetSignatureAuthAllowed { - public static readonly tag = 0x20cbb95a; + public static readonly tag = 0x04; public readonly tag = ActionSetSignatureAuthAllowed.tag; @@ -50,7 +50,7 @@ export class ActionSetSignatureAuthAllowed { public serialize(): Cell { return beginCell() - .storeUint(this.tag, 32) + .storeUint(this.tag, 8) .storeUint(this.allowed ? 1 : 0, 1) .endCell(); } @@ -89,21 +89,47 @@ function packActionsListOut(actions: (OutAction | ExtendedAction)[]): Cell { .endCell(); } -function packActionsListExtended(actions: (OutAction | ExtendedAction)[]): Cell { - const [action, ...rest] = actions; - - if (!action || !isExtendedAction(action)) { - return beginCell() - .storeUint(0, 1) - .storeRef(packActionsListOut(actions.slice().reverse())) // tvm handles actions from c5 in reversed order - .endCell(); +function packExtendedActions(extendedActions: ExtendedAction[]): Cell { + const first = extendedActions[0]; + const rest = extendedActions.slice(1); + let builder = beginCell() + .storeSlice(first.serialize().beginParse()); + if (rest.length > 0) { + builder = builder.storeRef(packExtendedActions(extendedActions.slice(1))); } + return builder.endCell(); +} - return beginCell() - .storeUint(1, 1) - .storeSlice(action.serialize().beginParse()) - .storeRef(packActionsListExtended(rest)) - .endCell(); +function packActionsListExtended(actions: (OutAction | ExtendedAction)[]): Cell { + const extendedActions: ExtendedAction[] = []; + const outActions: OutAction[] = []; + actions.forEach(action => { + if (isExtendedAction(action)) { + extendedActions.push(action); + } else { + outActions.push(action); + } + }); + + let builder = beginCell(); + if (outActions.length === 0) { + builder = builder.storeUint(0, 1); + } else { + builder = builder.storeMaybeRef(packActionsListOut(outActions.slice().reverse())); + } + if (extendedActions.length === 0) { + builder = builder.storeUint(0, 1); + } else { + const first = extendedActions[0]; + const rest = extendedActions.slice(1); + builder = builder + .storeUint(1, 1) + .storeSlice(first.serialize().beginParse()); + if (rest.length > 0) { + builder = builder.storeRef(packExtendedActions(rest)); + } + } + return builder.endCell(); } export function packActionsList(actions: (OutAction | ExtendedAction)[]): Cell { diff --git a/tests/gasUtils.ts b/tests/gasUtils.ts new file mode 100644 index 0000000..f322cea --- /dev/null +++ b/tests/gasUtils.ts @@ -0,0 +1,415 @@ +import { Cell, Slice, toNano, beginCell, Address, Dictionary, Message, DictionaryValue, Transaction, BitString, SendMode, MessageRelaxed, CommonMessageInfoInternal, storeMessage, storeMessageRelaxed } from '@ton/core'; +import { internal } from '@ton/sandbox'; +import { randomAddress } from './utils'; + +export type GasPrices = { + flat_gas_limit: bigint, + flat_gas_price: bigint, + gas_price: bigint; +}; +export type StorageValue = { + utime_sice: number, + bit_price_ps: bigint, + cell_price_ps: bigint, + mc_bit_price_ps: bigint, + mc_cell_price_ps: bigint +}; + + +export type MsgPrices = ReturnType; +export type FullFees = ReturnType; + +export class StorageStats { + bits: bigint; + cells: bigint; + + constructor(bits?: number | bigint, cells?: number | bigint) { + this.bits = bits !== undefined ? BigInt(bits) : 0n; + this.cells = cells !== undefined ? BigInt(cells) : 0n; + } + add(...stats: StorageStats[]) { + let cells = this.cells, bits = this.bits; + for (let stat of stats) { + bits += stat.bits; + cells += stat.cells; + } + return new StorageStats(bits, cells); + } + sub(...stats: StorageStats[]) { + let cells = this.cells, bits = this.bits; + for (let stat of stats) { + bits -= stat.bits; + cells -= stat.cells; + } + return new StorageStats(bits, cells); + } + addBits(bits: number | bigint) { + return new StorageStats(this.bits + BigInt(bits), this.cells); + } + subBits(bits: number | bigint) { + return new StorageStats(this.bits - BigInt(bits), this.cells); + } + addCells(cells: number | bigint) { + return new StorageStats(this.bits, this.cells + BigInt(cells)); + } + subCells(cells: number | bigint) { + return new StorageStats(this.bits, this.cells - BigInt(cells)); + } + + toString() : string { + return JSON.stringify({ + bits: this.bits.toString(), + cells: this.cells.toString() + }); + } +} + +export function computedGeneric(transaction: T) { + if(transaction.description.type !== "generic") + throw("Expected generic transactionaction"); + if(transaction.description.computePhase.type !== "vm") + throw("Compute phase expected") + return transaction.description.computePhase; +} + +export function storageGeneric(transaction: T) { + if(transaction.description.type !== "generic") + throw("Expected generic transactionaction"); + const storagePhase = transaction.description.storagePhase; + if(storagePhase === null || storagePhase === undefined) + throw("Storage phase expected") + return storagePhase; +} + +function shr16ceil(src: bigint) { + let rem = src % BigInt(65536); + let res = src / 65536n; // >> BigInt(16); + if (rem != BigInt(0)) { + res += BigInt(1); + } + return res; +} + +export function collectCellStats(cell: Cell, visited:Array, skipRoot: boolean = false): StorageStats { + let bits = skipRoot ? 0n : BigInt(cell.bits.length); + let cells = skipRoot ? 0n : 1n; + let hash = cell.hash().toString(); + if (visited.includes(hash)) { + // We should not account for current cell data if visited + return new StorageStats(); + } + else { + visited.push(hash); + } + for (let ref of cell.refs) { + let r = collectCellStats(ref, visited); + cells += r.cells; + bits += r.bits; + } + return new StorageStats(bits, cells); +} + +export function getGasPrices(configRaw: Cell, workchain: 0 | -1): GasPrices { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const ds = config.get(21 + workchain)!.beginParse(); + if(ds.loadUint(8) !== 0xd1) { + throw new Error("Invalid flat gas prices tag!"); + } + + const flat_gas_limit = ds.loadUintBig(64); + const flat_gas_price = ds.loadUintBig(64); + + if(ds.loadUint(8) !== 0xde) { + throw new Error("Invalid gas prices tag!"); + } + return { + flat_gas_limit, + flat_gas_price, + gas_price: ds.preloadUintBig(64) + }; +} + +export function setGasPrice(configRaw: Cell, prices: GasPrices, workchain: 0 | -1) : Cell { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const idx = 21 + workchain; + const ds = config.get(idx)!; + const tail = ds.beginParse().skip(8 + 64 + 64 + 8 + 64); + + const newPrices = beginCell().storeUint(0xd1, 8) + .storeUint(prices.flat_gas_limit, 64) + .storeUint(prices.flat_gas_price, 64) + .storeUint(0xde, 8) + .storeUint(prices.gas_price, 64) + .storeSlice(tail) + .endCell(); + config.set(idx, newPrices); + + return beginCell().storeDictDirect(config).endCell(); +} + +export const storageValue : DictionaryValue = { + serialize: (src, builder) => { + builder.storeUint(0xcc, 8) + .storeUint(src.utime_sice, 32) + .storeUint(src.bit_price_ps, 64) + .storeUint(src.cell_price_ps, 64) + .storeUint(src.mc_bit_price_ps, 64) + .storeUint(src.mc_cell_price_ps, 64) + }, + parse: (src) => { + return { + utime_sice: src.skip(8).loadUint(32), + bit_price_ps: src.loadUintBig(64), + cell_price_ps: src.loadUintBig(64), + mc_bit_price_ps: src.loadUintBig(64), + mc_cell_price_ps: src.loadUintBig(64) + }; + } + }; + +export function getStoragePrices(configRaw: Cell) { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const storageData = Dictionary.loadDirect(Dictionary.Keys.Uint(32),storageValue, config.get(18)!); + const values = storageData.values(); + + return values[values.length - 1]; +} +export function calcStorageFee(prices: StorageValue, stats: StorageStats, duration: bigint) { + return shr16ceil((stats.bits * prices.bit_price_ps + stats.cells * prices.cell_price_ps) * duration) +} +export function setStoragePrices(configRaw: Cell, prices: StorageValue) { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const storageData = Dictionary.loadDirect(Dictionary.Keys.Uint(32),storageValue, config.get(18)!); + storageData.set(storageData.values().length - 1, prices); + config.set(18, beginCell().storeDictDirect(storageData).endCell()); + return beginCell().storeDictDirect(config).endCell(); +} + +export function computeGasFee(prices: GasPrices, gas: bigint): bigint { + if(gas <= prices.flat_gas_limit) { + return prices.flat_gas_price; + } + return prices.flat_gas_price + prices.gas_price * (gas - prices.flat_gas_limit) / 65536n +} + +export function computeDefaultForwardFee(msgPrices: MsgPrices) { + return msgPrices.lumpPrice - ((msgPrices.lumpPrice * msgPrices.firstFrac) >> BigInt(16)); +} + +export function computeCellForwardFees(msgPrices: MsgPrices, msg: Cell) { + let storageStats = collectCellStats(msg, [], true); + return computeFwdFees(msgPrices, storageStats.cells, storageStats.bits); +} +export function computeMessageForwardFees(msgPrices: MsgPrices, msg: Message) { + // let msg = loadMessageRelaxed(cell.beginParse()); + let storageStats = new StorageStats(); + + if( msg.info.type !== "internal") { + throw Error("Helper intended for internal messages"); + } + const defaultFwd = computeDefaultForwardFee(msgPrices); + // If message forward fee matches default than msg cell is flat + if(msg.info.forwardFee == defaultFwd) { + return {fees: {total: msgPrices.lumpPrice, res : defaultFwd, remaining: defaultFwd}, stats: storageStats}; + } + let visited : Array = []; + // Init + if (msg.init) { + let addBits = 5n; // Minimal additional bits + let refCount = 0; + if(msg.init.splitDepth) { + addBits += 5n; + } + if(msg.init.libraries) { + refCount++; + storageStats = storageStats.add(collectCellStats(beginCell().storeDictDirect(msg.init.libraries).endCell(), visited, true)); + } + if(msg.init.code) { + refCount++; + storageStats = storageStats.add(collectCellStats(msg.init.code, visited)) + } + if(msg.init.data) { + refCount++; + storageStats = storageStats.add(collectCellStats(msg.init.data, visited)); + } + if(refCount >= 2) { //https://github.com/ton-blockchain/ton/blob/51baec48a02e5ba0106b0565410d2c2fd4665157/crypto/block/transaction.cpp#L2079 + storageStats.cells++; + storageStats.bits += addBits; + } + } + const lumpBits = BigInt(msg.body.bits.length); + const bodyStats = collectCellStats(msg.body,visited, true); + storageStats = storageStats.add(bodyStats); + + // NOTE: Extra currencies are ignored for now + let fees = computeFwdFeesVerbose(msgPrices, BigInt(storageStats.cells), BigInt(storageStats.bits)); + // Meeh + if(fees.remaining < msg.info.forwardFee) { + // console.log(`Remaining ${fees.remaining} < ${msg.info.forwardFee} lump bits:${lumpBits}`); + storageStats = storageStats.addCells(1).addBits(lumpBits); + fees = computeFwdFeesVerbose(msgPrices, storageStats.cells, storageStats.bits); + } + if(fees.remaining != msg.info.forwardFee) { + console.log("Result fees:", fees); + console.log(msg); + console.log(fees.remaining); + throw(new Error("Something went wrong in fee calcuation!")); + } + return {fees, stats: storageStats}; +} + +export const configParseMsgPrices = (sc: Slice) => { + + let magic = sc.loadUint(8); + + if(magic != 0xea) { + throw Error("Invalid message prices magic number!"); + } + return { + lumpPrice:sc.loadUintBig(64), + bitPrice: sc.loadUintBig(64), + cellPrice: sc.loadUintBig(64), + ihrPriceFactor: sc.loadUintBig(32), + firstFrac: sc.loadUintBig(16), + nextFrac: sc.loadUintBig(16) + }; +} + +export const setMsgPrices = (configRaw: Cell, prices: MsgPrices, workchain: 0 | -1) => { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const priceCell = beginCell().storeUint(0xea, 8) + .storeUint(prices.lumpPrice, 64) + .storeUint(prices.bitPrice, 64) + .storeUint(prices.cellPrice, 64) + .storeUint(prices.ihrPriceFactor, 32) + .storeUint(prices.firstFrac, 16) + .storeUint(prices.nextFrac, 16) + .endCell(); + config.set(25 + workchain, priceCell); + + return beginCell().storeDictDirect(config).endCell(); +} + +export const getMsgPrices = (configRaw: Cell, workchain: 0 | -1 ) => { + + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const prices = config.get(25 + workchain); + + if(prices === undefined) { + throw Error("No prices defined in config"); + } + + return configParseMsgPrices(prices.beginParse()); +} + +export function computeFwdFees(msgPrices: MsgPrices, cells: bigint, bits: bigint) { + return msgPrices.lumpPrice + (shr16ceil((msgPrices.bitPrice * bits) + + (msgPrices.cellPrice * cells)) + ); +} + +export function computeFwdFeesVerbose(msgPrices: MsgPrices, cells: bigint | number, bits: bigint | number) { + const fees = computeFwdFees(msgPrices, BigInt(cells), BigInt(bits)); + + const res = (fees * msgPrices.firstFrac) >> 16n; + return { + total: fees, + res, + remaining: fees - res + } +} + +export const setPrecompiledGas = (configRaw: Cell, code_hash: Buffer, gas_usage: number) => { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const entry = beginCell().storeUint(0xb0, 8) + .storeUint(gas_usage, 64) + .endCell().beginParse(); + let dict = Dictionary.empty(Dictionary.Keys.Buffer(32), Dictionary.Values.BitString(8 + 64)); + dict.set(code_hash, entry.loadBits(8 + 64)); + const param = beginCell().storeUint(0xc0, 8).storeBit(1).storeRef(beginCell().storeDictDirect(dict).endCell()).endCell(); + + config.set(45, param); + + return beginCell().storeDictDirect(config).endCell(); +}; + +export const estimateMessageImpact = (message: MessageRelaxed, sendTx: T, msgPrices: MsgPrices, balanceBefore: bigint, mode: SendMode, computed: boolean) => { + + if(message.info.type !== 'internal') { + throw new TypeError("External message is not supported!"); + } + + const computePhase = computedGeneric(sendTx); + + let inValue = 0n; + let inMessage = sendTx.inMessage; + let feesPaid = false; + + if(inMessage) { + if(inMessage.info.type == 'internal') { + inValue = inMessage.info.value.coins; + } + else if(inMessage.info.type == 'external-in') { + // Negative because of import cost + inValue -= computeCellForwardFees(msgPrices, beginCell().store(storeMessage(inMessage)).endCell()); + } + else { + throw new TypeError("external-out can't be incomming message!"); + } + } + + const msgPacked = beginCell().store(storeMessageRelaxed(message)).endCell(); + + const fees = computeCellForwardFees(msgPrices, msgPacked); + + let expOut = message.info.value.coins; + + let balanceAfter = balanceBefore - expOut; + // Usually means it's not the first action, so gas has already been deducted and credit added + if(!computed) { + balanceAfter += inValue - computePhase.gasFees; + } + + if(!(mode & SendMode.PAY_GAS_SEPARATELY)) { + expOut -= fees; + feesPaid = true; + } + else { + balanceAfter -= fees; + } + /* + else if(mode & SendMode.PAY_GAS_SEPARATELY) { + if(!(mode & SendMode.CARRY_ALL_REMAINING_BALANCE) || (mode & SendMode.CARRY_ALL_REMAINING_INCOMING_VALUE)) { + balanceAfter -= fees; + } + } + */ + if(mode & SendMode.CARRY_ALL_REMAINING_BALANCE) { + expOut = balanceAfter - fees + message.info.value.coins; + balanceAfter = 0n; + } + if(mode & SendMode.CARRY_ALL_REMAINING_INCOMING_VALUE) { + if(mode & SendMode.CARRY_ALL_REMAINING_BALANCE) { + throw new TypeError("Mode 64 and 128 is not compatible"); + } + if(!inMessage) { + throw new Error("Mode 64 doesn't work without incomming message"); + } + if(inMessage.info.type != 'internal') { + throw new Error("Mode 64 doesn't work with external incomming message"); + } + + expOut = inValue - computePhase.gasFees + message.info.value.coins - fees; + balanceAfter -= inValue - computePhase.gasFees; + /* + if(!feesPaid) { + expOut -= fees; + } + */ + } + return {expValue: expOut, balanceAfter}; +} diff --git a/tests/test-only-actions.ts b/tests/test-only-actions.ts index 7517351..00ca35d 100644 --- a/tests/test-only-actions.ts +++ b/tests/test-only-actions.ts @@ -7,7 +7,7 @@ import { SendMode, storeCurrencyCollection, storeMessageRelaxed -} from 'ton-core'; +} from '@ton/core'; import { ExtendedAction, OutAction @@ -79,4 +79,4 @@ export function isTestOnlyExtendedAction(action: OutAction | ExtendedAction): ac return ( action.tag === ActionSetData.tag ); -} \ No newline at end of file +} diff --git a/tests/utils.ts b/tests/utils.ts index 8257d4b..8a32688 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,12 +1,11 @@ -import { Address, beginCell, Cell, CurrencyCollection, MessageRelaxed, StateInit } from 'ton-core'; +import { Address, beginCell, Cell, CurrencyCollection, MessageRelaxed, StateInit } from '@ton/core'; export function bufferToBigInt(buffer: Buffer): bigint { return BigInt('0x' + buffer.toString('hex')); } export function packAddress(address: Address) { - const wcPlus = address.workChain + 1; - return bufferToBigInt(address.hash) ^ BigInt(wcPlus); + return bufferToBigInt(address.hash); } export function validUntil(ttlMs = 1000 * 60 * 3) { @@ -38,6 +37,69 @@ export function createMsgInternal(params: { }; } +export const randomAddress = (wc: number = 0) => { + const buf = Buffer.alloc(32); + for (let i = 0; i < buf.length; i++) { + buf[i] = Math.floor(Math.random() * 256); + } + return new Address(wc, buf); +}; + +export const differentAddress = (old: Address) => { + let newAddr: Address; + do { + newAddr = randomAddress(old.workChain); + } while(newAddr.equals(old)); + + return newAddr; +} + +const getRandom = (min:number, max:number) => { + return Math.random() * (max - min) + min; +} + +export const getRandomInt = (min: number, max: number) => { + return Math.round(getRandom(min, max)); +} + +export const pickRandomN = (min: number, max: number, count: number): number[] => { + if(count > max - min) { + throw new Error("Element count can't be larger than range"); + } + + let uniqSet: Set = new Set(); + let foundCount = 0; + // I know it' inefficient + do { + const atempt = getRandomInt(min, max) + if(!uniqSet.has(atempt)) { + foundCount++; + uniqSet.add(atempt); + } + } while(foundCount < count); + + return [...uniqSet]; +} + +export const pickRandomNFrom = (count: number, from: T[]): T[] => { + let resultPick: T[] = new Array(count); + const pickIdxs = pickRandomN(0, from.length - 1, count); + + for(let i = 0; i < pickIdxs.length; i++) { + resultPick[i] = from[pickIdxs[i]]; + } + + return resultPick; +} +export const testArgs = (...args: unknown[]) => { + for(let arg of args) { + if(arg === undefined || arg === null) { + throw TypeError("Required argument is missing!"); + } + } +} + + export async function disableConsoleError(callback: () => Promise): Promise { const errorsHandler = console.error; console.error = () => {}; diff --git a/tests/wallet-v5-extensions.spec.ts b/tests/wallet-v5-extensions.spec.ts index ae91fdd..6822105 100644 --- a/tests/wallet-v5-extensions.spec.ts +++ b/tests/wallet-v5-extensions.spec.ts @@ -1,8 +1,8 @@ -import {Blockchain, BlockchainTransaction, SandboxContract} from '@ton-community/sandbox'; -import { Address, beginCell, Cell, Dictionary, Sender, SendMode, toNano } from 'ton-core'; +import {Blockchain, BlockchainTransaction, SandboxContract} from '@ton/sandbox'; +import { Address, beginCell, Cell, Dictionary, Sender, SendMode, toNano } from '@ton/core'; import { Opcodes, WalletId, WalletV5 } from '../wrappers/wallet-v5'; -import '@ton-community/test-utils'; -import { compile } from '@ton-community/blueprint'; +import '@ton/test-utils'; +import { compile } from '@ton/blueprint'; import { getSecureRandomBytes, KeyPair, keyPairFromSeed, sign } from 'ton-crypto'; import { bufferToBigInt, createMsgInternal, packAddress, validUntil } from './utils'; import { @@ -11,8 +11,8 @@ import { ActionSendMsg, ActionSetSignatureAuthAllowed, packActionsList } from './actions'; -import { TransactionDescriptionGeneric } from 'ton-core/src/types/TransactionDescription'; -import { TransactionComputeVm } from 'ton-core/src/types/TransactionComputePhase'; +import { TransactionDescriptionGeneric } from '@ton/core/src/types/TransactionDescription'; +import { TransactionComputeVm } from '@ton/core/src/types/TransactionComputePhase'; import { buildBlockchainLibraries, LibraryDeployer } from '../wrappers/library-deployer'; import { default as config } from './config'; @@ -45,7 +45,7 @@ describe('Wallet V5 extensions auth', () => { function createBody(actionsList: Cell) { const payload = beginCell() .storeUint(Opcodes.auth_signed_internal, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -68,6 +68,7 @@ describe('Wallet V5 extensions auth', () => { walletV5 = blockchain.openContract( WalletV5.createFromConfig( { + signatureAllowed: true, seqno: 0, walletId: WALLET_ID.serialized, publicKey: keypair.publicKey, @@ -196,17 +197,17 @@ describe('Wallet V5 extensions auth', () => { const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict.size).toEqual(2); expect(extensionsDict.get(packAddress(sender.address!))).toEqual( - BigInt(sender.address!.workChain) + -1n ); expect(extensionsDict.get(packAddress(testOtherExtension))).toEqual( - BigInt(testOtherExtension.workChain) + -1n ); }); @@ -229,15 +230,15 @@ describe('Wallet V5 extensions auth', () => { const extensionsDict1 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict1.size).toEqual(2); expect(extensionsDict1.get(packAddress(sender.address!))).toEqual( - BigInt(sender.address!.workChain) + -1n ); expect(extensionsDict1.get(packAddress(otherExtension))).toEqual( - BigInt(otherExtension.workChain) + -1n ); const actions2 = packActionsList([new ActionRemoveExtension(otherExtension)]); @@ -251,12 +252,12 @@ describe('Wallet V5 extensions auth', () => { const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict.size).toEqual(1); expect(extensionsDict.get(packAddress(sender.address!))).toEqual( - BigInt(sender.address!.workChain) + -1n ); expect(extensionsDict.get(packAddress(otherExtension))).toEqual(undefined); }); @@ -278,7 +279,7 @@ describe('Wallet V5 extensions auth', () => { const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict.size).toEqual(0); @@ -325,11 +326,15 @@ describe('Wallet V5 extensions auth', () => { await walletV5.sendInternalSignedMessage(sender, { value: toNano(0.1), body: createBody(packActionsList([ - new ActionAddExtension(sender.address!), - new ActionSetSignatureAuthAllowed(false) + new ActionAddExtension(sender.address!) ])) }); + const receipt0 = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: packActionsList([new ActionSetSignatureAuthAllowed(false)]) + }); + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const forwardValue = toNano(0.001); const receiverBalanceBefore = (await blockchain.getContract(testReceiver)).balance; @@ -361,11 +366,17 @@ describe('Wallet V5 extensions auth', () => { await walletV5.sendInternalSignedMessage(sender, { value: toNano(0.1), body: createBody(packActionsList([ - new ActionAddExtension(sender.address!), - new ActionSetSignatureAuthAllowed(false) + new ActionAddExtension(sender.address!) ])) }); + await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: packActionsList([ + new ActionSetSignatureAuthAllowed(false) + ]) + }); + const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); expect(isSignatureAuthAllowed).toEqual(0); @@ -391,10 +402,7 @@ describe('Wallet V5 extensions auth', () => { expect(isSignatureAuthAllowed1).toEqual(-1); const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 2); - - // Allowing or disallowing signature auth increments seqno, need to re-read - seqno = contract_seqno; + expect(contract_seqno).toEqual(seqno); const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const forwardValue = toNano(0.001); @@ -480,10 +488,7 @@ describe('Wallet V5 extensions auth', () => { expect(isSignatureAuthAllowed1).toEqual(-1); const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 2); - - // Allowing or disallowing signature auth increments seqno, need to re-read - seqno = contract_seqno; + expect(contract_seqno).toEqual(seqno); const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const forwardValue = toNano(0.001); diff --git a/tests/wallet-v5-external.spec.ts b/tests/wallet-v5-external.spec.ts index 3d17d1d..9316123 100644 --- a/tests/wallet-v5-external.spec.ts +++ b/tests/wallet-v5-external.spec.ts @@ -1,9 +1,10 @@ -import {Blockchain, BlockchainTransaction, SandboxContract} from '@ton-community/sandbox'; -import { Address, beginCell, Cell, Dictionary, internal, Sender, SendMode, toNano } from 'ton-core'; +import {Blockchain, BlockchainTransaction, SandboxContract} from '@ton/sandbox'; +import { Address, beginCell, Cell, Dictionary, internal, Sender, SendMode, toNano } from '@ton/core'; import { Opcodes, WalletId, WalletV5 } from '../wrappers/wallet-v5'; -import '@ton-community/test-utils'; -import { compile } from '@ton-community/blueprint'; -import { getSecureRandomBytes, KeyPair, keyPairFromSeed, sign } from 'ton-crypto'; +import '@ton/test-utils'; +import { compile } from '@ton/blueprint'; +import { getSecureRandomBytes, KeyPair, keyPairFromSeed, sign } from '@ton/crypto'; + import { bufferToBigInt, createMsgInternal, @@ -58,6 +59,7 @@ describe('Wallet V5 sign auth external', () => { const _walletV5 = blockchain.openContract( WalletV5.createFromConfig( { + signatureAllowed: true, seqno: params?.seqno ?? 0, walletId: params?.walletId ?? WALLET_ID.serialized, publicKey: params?.publicKey ?? _keypair.publicKey, @@ -77,7 +79,7 @@ describe('Wallet V5 sign auth external', () => { function createBody(actionsList: Cell) { const payload = beginCell() .storeUint(Opcodes.auth_signed, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -100,6 +102,7 @@ describe('Wallet V5 sign auth external', () => { walletV5 = blockchain.openContract( WalletV5.createFromConfig( { + signatureAllowed: true, seqno: 0, walletId: WALLET_ID.serialized, publicKey: keypair.publicKey, @@ -145,13 +148,13 @@ describe('Wallet V5 sign auth external', () => { .endCell(); const actionsList = beginCell() - .storeUint(0, 1) - .storeRef( + .storeMaybeRef( beginCell() - .storeRef(beginCell().endCell()) + .storeRef(beginCell().endCell()) // empty child - end of action list .storeSlice(sendTxactionAction.beginParse()) .endCell() ) + .storeUint(0, 1) // no other_actions .endCell(); if (config.microscope) @@ -188,13 +191,13 @@ describe('Wallet V5 sign auth external', () => { const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const addExtensionAction = beginCell() - .storeUint(Opcodes.action_extended_add_extension, 32) + .storeUint(Opcodes.action_extended_add_extension, 8) .storeAddress(testExtension) .endCell(); const actionsList = beginCell() - .storeUint(1, 1) - .storeRef(beginCell().storeUint(0, 1).storeRef(beginCell().endCell()).endCell()) + .storeUint(0, 1) // no c5 actions + .storeUint(1, 1) // have other actions .storeSlice(addExtensionAction.beginParse()) .endCell(); @@ -206,14 +209,14 @@ describe('Wallet V5 sign auth external', () => { const extensions = await walletV5.getExtensions(); const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), extensions ); expect(extensionsDict.size).toEqual(1); - const storedWC = extensionsDict.get(packAddress(testExtension)); - expect(storedWC).toEqual(BigInt(testExtension.workChain)); + const dictValue = extensionsDict.get(packAddress(testExtension)); + expect(dictValue).toEqual(-1n); }); it('Send single transfers to a deployed wallet', async () => { @@ -293,7 +296,7 @@ describe('Wallet V5 sign auth external', () => { }); it('Add two extensions and do a transfer', async () => { - const testExtension1 = Address.parse('Ef82pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX28X'); + const testExtension1 = Address.parse('EQA2pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX5Bf'); const testExtension2 = Address.parse('EQCgYDKqfTh7zVj9BQwOIPs4SuOhM7wnIjb6bdtM2AJf_Z9G'); const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); @@ -326,7 +329,7 @@ describe('Wallet V5 sign auth external', () => { const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); @@ -334,10 +337,10 @@ describe('Wallet V5 sign auth external', () => { accountForGas(receipt.transactions); expect(extensionsDict.get(packAddress(testExtension1))).toEqual( - BigInt(testExtension1.workChain) + -1n ); expect(extensionsDict.get(packAddress(testExtension2))).toEqual( - BigInt(testExtension2.workChain) + -1n ); }); @@ -348,19 +351,19 @@ describe('Wallet V5 sign auth external', () => { const receipt1 = await walletV5.sendExternalSignedMessage(createBody(actionsList1)); const extensionsDict1 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict1.size).toEqual(1); expect(extensionsDict1.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); const actionsList2 = packActionsList([new ActionRemoveExtension(testExtension)]); const receipt2 = await walletV5.sendExternalSignedMessage(createBody(actionsList2)); const extensionsDict2 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); @@ -384,7 +387,7 @@ describe('Wallet V5 sign auth external', () => { (receipt.transactions[0].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(41); + ).toEqual(141); }); it('Should fail SetCode action', async () => { @@ -411,12 +414,12 @@ describe('Wallet V5 sign auth external', () => { accountForGas(receipt1.transactions); const extensionsDict1 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict1.size).toEqual(1); expect(extensionsDict1.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); const actionsList2 = packActionsList([new ActionAddExtension(testExtension)]); @@ -427,16 +430,16 @@ describe('Wallet V5 sign auth external', () => { (receipt.transactions[0].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(39); + ).toEqual(139); const extensionsDict2 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict2.size).toEqual(1); expect(extensionsDict2.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); }); @@ -451,11 +454,11 @@ describe('Wallet V5 sign auth external', () => { (receipt.transactions[0].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(40); + ).toEqual(140); const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict.size).toEqual(0); @@ -473,7 +476,7 @@ describe('Wallet V5 sign auth external', () => { const vu = validUntil(); const payload = beginCell() - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(vu, 32) .storeUint(seqno, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -481,7 +484,7 @@ describe('Wallet V5 sign auth external', () => { const fakePayload = beginCell() .storeUint(Opcodes.auth_signed, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(vu, 32) .storeUint(seqno + 1, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -512,7 +515,7 @@ describe('Wallet V5 sign auth external', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -545,7 +548,7 @@ describe('Wallet V5 sign auth external', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno + 1, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -576,7 +579,7 @@ describe('Wallet V5 sign auth external', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(Math.round(Date.now() / 1000) - 600, 32) .storeUint(seqno, 32) .storeSlice(actionsList.beginParse()) @@ -607,7 +610,7 @@ describe('Wallet V5 sign auth external', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed, 32) - .storeUint(new WalletId({ ...WALLET_ID, subwalletNumber: 1 }).serialized, 80) + .storeUint(new WalletId({ ...WALLET_ID, subwalletNumber: 1 }).serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) .storeSlice(actionsList.beginParse()) @@ -638,7 +641,7 @@ describe('Wallet V5 sign auth external', () => { const payload = beginCell() // auth_signed_internal used instead of auth_signed .storeUint(Opcodes.auth_signed_internal, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) .storeSlice(actionsList.beginParse()) @@ -691,251 +694,248 @@ describe('Wallet V5 sign auth external', () => { expect(walletBalanceBefore).toEqual(walletBalanceAfter); }); - it('Should fail disallowing signature auth with no exts', async () => { - const actionsList = packActionsList([ - new ActionSetSignatureAuthAllowed(false) - ]); - const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); - - expect( - ( - (receipt.transactions[0].description as TransactionDescriptionGeneric) - .computePhase as TransactionComputeVm - ).exitCode - ).toEqual(42); - - const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); - expect(isSignatureAuthAllowed).toEqual(-1); - }); - - it('Should fail allowing signature auth when allowed', async () => { - const actionsList = packActionsList([ - new ActionSetSignatureAuthAllowed(true) - ]); - const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); - - expect( - ( - (receipt.transactions[0].description as TransactionDescriptionGeneric) - .computePhase as TransactionComputeVm - ).exitCode - ).toEqual(43); - - const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); - expect(isSignatureAuthAllowed).toEqual(-1); - }); - - it('Should add ext and disallow signature auth', async () => { - const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - + it('only_extension_can_change_signature_mode', async () => { const actionsList = packActionsList([ - new ActionAddExtension(testExtension), new ActionSetSignatureAuthAllowed(false) ]); const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); - accountForGas(receipt.transactions); - - expect( - ( - (receipt.transactions[0].description as TransactionDescriptionGeneric) - .computePhase as TransactionComputeVm - ).exitCode - ).toEqual(0); - - const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); - expect(isSignatureAuthAllowed).toEqual(0); - - const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 1); - }); - - it('Should add ext and disallow signature auth in separate txs', async () => { - const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - - const actionsList = packActionsList([ - new ActionAddExtension(testExtension) - ]); - const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); - accountForGas(receipt.transactions); expect( ( (receipt.transactions[0].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(0); - - const extensionsDict = Dictionary.loadDirect( - Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), - await walletV5.getExtensions() - ); - - expect(extensionsDict.size).toEqual(1); - - expect(extensionsDict.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) - ); + ).toEqual(146); // only_extension_can_change_signature_mode const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); expect(isSignatureAuthAllowed).toEqual(-1); - - const actionsList2 = packActionsList([ - new ActionSetSignatureAuthAllowed(false) - ]); - const receipt2 = await walletV5.sendExternalSignedMessage(createBody(actionsList2)); - accountForGas(receipt2.transactions); - - expect( - ( - (receipt2.transactions[0].description as TransactionDescriptionGeneric) - .computePhase as TransactionComputeVm - ).exitCode - ).toEqual(0); - - const isSignatureAuthAllowed2 = await walletV5.getIsSignatureAuthAllowed(); - expect(isSignatureAuthAllowed2).toEqual(0); - - const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 1); }); - it('Should add ext, disallow sign, allow sign, remove ext in one tx; send in other', async () => { - const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - - const actionsList = packActionsList([ - new ActionAddExtension(testExtension), - new ActionSetSignatureAuthAllowed(false), - new ActionSetSignatureAuthAllowed(true), - new ActionRemoveExtension(testExtension) - ]); - const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); - accountForGas(receipt.transactions); - - expect( - ( - (receipt.transactions[0].description as TransactionDescriptionGeneric) - .computePhase as TransactionComputeVm - ).exitCode - ).toEqual(0); - - const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); - expect(isSignatureAuthAllowed).toEqual(-1); - - const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 2); - - // Allowing or disallowing signature auth increments seqno, need to re-read - seqno = contract_seqno; - - const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - const forwardValue = toNano(0.001); - - const receiverBalanceBefore = (await blockchain.getContract(testReceiver)).balance; - - const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); - - const actionsList2 = packActionsList([ - new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg) - ]); - - const receipt2 = await walletV5.sendExternalSignedMessage(createBody(actionsList2)); - - expect(receipt2.transactions.length).toEqual(2); - accountForGas(receipt2.transactions); - - expect(receipt2.transactions).toHaveTransaction({ - from: walletV5.address, - to: testReceiver, - value: forwardValue - }); - - const fee = receipt2.transactions[1].totalFees.coins; - const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; - expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); - }); - - it('Should fail removing last extension with signature auth disabled', async () => { - const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - - const actionsList = packActionsList([ - new ActionAddExtension(testExtension), - new ActionSetSignatureAuthAllowed(false), - new ActionRemoveExtension(testExtension) - ]); - const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); - accountForGas(receipt.transactions); - - expect( - ( - (receipt.transactions[0].description as TransactionDescriptionGeneric) - .computePhase as TransactionComputeVm - ).exitCode - ).toEqual(44); - - const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); - expect(isSignatureAuthAllowed).toEqual(-1); - }); - - it('Should fail disallowing signature auth twice in tx', async () => { - const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - - const actionsList = packActionsList([ - new ActionAddExtension(testExtension), - new ActionSetSignatureAuthAllowed(false), - new ActionSetSignatureAuthAllowed(false) - ]); - const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); - accountForGas(receipt.transactions); - - expect( - ( - (receipt.transactions[0].description as TransactionDescriptionGeneric) - .computePhase as TransactionComputeVm - ).exitCode - ).toEqual(43); - - const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); - expect(isSignatureAuthAllowed).toEqual(-1); // throw when handling, packet is dropped - }); - - it('Should add ext, disallow sig auth; fail different signed tx', async () => { - const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - - const actionsList = packActionsList([ - new ActionAddExtension(testExtension), - new ActionSetSignatureAuthAllowed(false) - ]); - const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); - accountForGas(receipt.transactions); - - expect( - ( - (receipt.transactions[0].description as TransactionDescriptionGeneric) - .computePhase as TransactionComputeVm - ).exitCode - ).toEqual(0); - - const extensionsDict = Dictionary.loadDirect( - Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), - await walletV5.getExtensions() - ); - - expect(extensionsDict.size).toEqual(1); - - expect(extensionsDict.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) - ); - - const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); - expect(isSignatureAuthAllowed).toEqual(0); - - const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 1); - - await disableConsoleError(() => - expect(walletV5.sendExternalSignedMessage(createBody(packActionsList([])))).rejects.toThrow() - ); - }); + // it('Should fail allowing signature auth when allowed', async () => { + // const actionsList = packActionsList([ + // new ActionSetSignatureAuthAllowed(true) + // ]); + // const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + // + // expect( + // ( + // (receipt.transactions[0].description as TransactionDescriptionGeneric) + // .computePhase as TransactionComputeVm + // ).exitCode + // ).toEqual(43); + // + // const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + // expect(isSignatureAuthAllowed).toEqual(-1); + // }); + + // it('Should add ext and disallow signature auth', async () => { + // const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + // + // const actionsList = packActionsList([ + // new ActionAddExtension(testExtension), + // new ActionSetSignatureAuthAllowed(false) + // ]); + // const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + // accountForGas(receipt.transactions); + // + // expect( + // ( + // (receipt.transactions[0].description as TransactionDescriptionGeneric) + // .computePhase as TransactionComputeVm + // ).exitCode + // ).toEqual(0); + // + // const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + // expect(isSignatureAuthAllowed).toEqual(0); + // + // const contract_seqno = await walletV5.getSeqno(); + // expect(contract_seqno).toEqual(seqno); + // }); + + // it('Should add ext and disallow signature auth in separate txs', async () => { + // const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + // + // const actionsList = packActionsList([ + // new ActionAddExtension(testExtension) + // ]); + // const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + // accountForGas(receipt.transactions); + // + // expect( + // ( + // (receipt.transactions[0].description as TransactionDescriptionGeneric) + // .computePhase as TransactionComputeVm + // ).exitCode + // ).toEqual(0); + // + // const extensionsDict = Dictionary.loadDirect( + // Dictionary.Keys.BigUint(256), + // Dictionary.Values.BigInt(1), + // await walletV5.getExtensions() + // ); + // + // expect(extensionsDict.size).toEqual(1); + // + // expect(extensionsDict.get(packAddress(testExtension))).toEqual( + // -1n + // ); + // + // const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + // expect(isSignatureAuthAllowed).toEqual(-1); + // + // const actionsList2 = packActionsList([ + // new ActionSetSignatureAuthAllowed(false) + // ]); + // const receipt2 = await walletV5.sendExternalSignedMessage(createBody(actionsList2)); + // accountForGas(receipt2.transactions); + // + // expect( + // ( + // (receipt2.transactions[0].description as TransactionDescriptionGeneric) + // .computePhase as TransactionComputeVm + // ).exitCode + // ).toEqual(0); + // + // const isSignatureAuthAllowed2 = await walletV5.getIsSignatureAuthAllowed(); + // expect(isSignatureAuthAllowed2).toEqual(0); + // + // const contract_seqno = await walletV5.getSeqno(); + // expect(contract_seqno).toEqual(seqno); + // }); + // + // it('Should add ext, disallow sign, allow sign, remove ext in one tx; send in other', async () => { + // const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + // + // const actionsList = packActionsList([ + // new ActionAddExtension(testExtension), + // new ActionSetSignatureAuthAllowed(false), + // new ActionSetSignatureAuthAllowed(true), + // new ActionRemoveExtension(testExtension) + // ]); + // const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + // accountForGas(receipt.transactions); + // + // expect( + // ( + // (receipt.transactions[0].description as TransactionDescriptionGeneric) + // .computePhase as TransactionComputeVm + // ).exitCode + // ).toEqual(0); + // + // const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + // expect(isSignatureAuthAllowed).toEqual(-1); + // + // const contract_seqno = await walletV5.getSeqno(); + // expect(contract_seqno).toEqual(seqno); + // + // const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + // const forwardValue = toNano(0.001); + // + // const receiverBalanceBefore = (await blockchain.getContract(testReceiver)).balance; + // + // const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); + // + // const actionsList2 = packActionsList([ + // new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg) + // ]); + // + // const receipt2 = await walletV5.sendExternalSignedMessage(createBody(actionsList2)); + // + // expect(receipt2.transactions.length).toEqual(2); + // accountForGas(receipt2.transactions); + // + // expect(receipt2.transactions).toHaveTransaction({ + // from: walletV5.address, + // to: testReceiver, + // value: forwardValue + // }); + // + // const fee = receipt2.transactions[1].totalFees.coins; + // const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + // expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + // }); + // + // it('Should fail removing last extension with signature auth disabled', async () => { + // const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + // + // const actionsList = packActionsList([ + // new ActionAddExtension(testExtension), + // new ActionSetSignatureAuthAllowed(false), + // new ActionRemoveExtension(testExtension) + // ]); + // const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + // accountForGas(receipt.transactions); + // + // expect( + // ( + // (receipt.transactions[0].description as TransactionDescriptionGeneric) + // .computePhase as TransactionComputeVm + // ).exitCode + // ).toEqual(44); + // + // const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + // expect(isSignatureAuthAllowed).toEqual(-1); + // }); + // + // it('Should fail disallowing signature auth twice in tx', async () => { + // const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + // + // const actionsList = packActionsList([ + // new ActionAddExtension(testExtension), + // new ActionSetSignatureAuthAllowed(false), + // new ActionSetSignatureAuthAllowed(false) + // ]); + // const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + // accountForGas(receipt.transactions); + // + // expect( + // ( + // (receipt.transactions[0].description as TransactionDescriptionGeneric) + // .computePhase as TransactionComputeVm + // ).exitCode + // ).toEqual(43); + // + // const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + // expect(isSignatureAuthAllowed).toEqual(-1); // throw when handling, packet is dropped + // }); + // + // it('Should add ext, disallow sig auth; fail different signed tx', async () => { + // const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + // + // const actionsList = packActionsList([ + // new ActionAddExtension(testExtension), + // new ActionSetSignatureAuthAllowed(false) + // ]); + // const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + // accountForGas(receipt.transactions); + // + // expect( + // ( + // (receipt.transactions[0].description as TransactionDescriptionGeneric) + // .computePhase as TransactionComputeVm + // ).exitCode + // ).toEqual(0); + // + // const extensionsDict = Dictionary.loadDirect( + // Dictionary.Keys.BigUint(256), + // Dictionary.Values.BigInt(1), + // await walletV5.getExtensions() + // ); + // + // expect(extensionsDict.size).toEqual(1); + // + // expect(extensionsDict.get(packAddress(testExtension))).toEqual( + // -1n + // ); + // + // const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + // expect(isSignatureAuthAllowed).toEqual(0); + // + // const contract_seqno = await walletV5.getSeqno(); + // expect(contract_seqno).toEqual(seqno); + // + // await disableConsoleError(() => + // expect(walletV5.sendExternalSignedMessage(createBody(packActionsList([])))).rejects.toThrow() + // ); + // }); }); diff --git a/tests/wallet-v5-get.spec.ts b/tests/wallet-v5-get.spec.ts index c3fac3a..1283249 100644 --- a/tests/wallet-v5-get.spec.ts +++ b/tests/wallet-v5-get.spec.ts @@ -1,8 +1,8 @@ -import { Blockchain, SandboxContract } from '@ton-community/sandbox'; -import { Address, beginCell, Cell, Dictionary, Sender, toNano } from 'ton-core'; +import { Blockchain, SandboxContract } from '@ton/sandbox'; +import { Address, beginCell, Cell, Dictionary, Sender, toNano } from '@ton/core'; import { WalletId, WalletV5 } from '../wrappers/wallet-v5'; -import '@ton-community/test-utils'; -import { compile } from '@ton-community/blueprint'; +import '@ton/test-utils'; +import { compile } from '@ton/blueprint'; import { getSecureRandomBytes, KeyPair, keyPairFromSeed } from 'ton-crypto'; import { bufferToBigInt, packAddress } from './utils'; import { buildBlockchainLibraries, LibraryDeployer } from '../wrappers/library-deployer'; @@ -31,6 +31,7 @@ describe('Wallet V5 get methods', () => { walletV5 = blockchain.openContract( WalletV5.createFromConfig( { + signatureAllowed: true, seqno: params?.seqno ?? 0, walletId: params?.walletId ?? WALLET_ID.serialized, publicKey: params?.publicKey ?? keypair.publicKey, @@ -78,7 +79,7 @@ describe('Wallet V5 get methods', () => { }); await deploy({ walletId: expectedWalletId.serialized }); const actualWalletId = await walletV5.getWalletId(); - expect(expectedWalletId.serialized).toEqual(actualWalletId.serialized); + expect(expectedWalletId.subwalletNumber).toEqual(actualWalletId.subwalletNumber); }); it('Get subwallet number', async () => { @@ -107,41 +108,46 @@ describe('Wallet V5 get methods', () => { it('Get extensions dict', async () => { const plugin1 = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - const plugin2 = Address.parse('Ef82pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX28X'); + const plugin2 = Address.parse('EQA2pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX5Bf'); const extensions: Dictionary = Dictionary.empty( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8) + Dictionary.Values.BigInt(1) ); - extensions.set(packAddress(plugin1), BigInt(plugin1.workChain)); - extensions.set(packAddress(plugin2), BigInt(plugin2.workChain)); + extensions.set(packAddress(plugin1), -1n); + extensions.set(packAddress(plugin2), -1n); await deploy({ extensions }); const actual = await walletV5.getExtensions(); const expected = beginCell() - .storeDictDirect(extensions, Dictionary.Keys.BigUint(256), Dictionary.Values.BigInt(8)) + .storeDictDirect(extensions, Dictionary.Keys.BigUint(256), Dictionary.Values.BigInt(1)) .endCell(); expect(actual?.equals(expected)).toBeTruthy(); }); it('Get extensions array', async () => { - const plugin1 = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); - const plugin2 = Address.parse('Ef82pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX28X'); + const plugin1 = Address.parse( + '0:0000F5851B4A185F5F63C0D0CD0412F5ACA353F577DA18FF47C936F99DBD0000' + ); + const plugin2 = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const plugin3 = Address.parse('EQA2pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX5Bf'); const extensions: Dictionary = Dictionary.empty( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8) + Dictionary.Values.BigInt(1) ); - extensions.set(packAddress(plugin1), BigInt(plugin1.workChain)); - extensions.set(packAddress(plugin2), BigInt(plugin2.workChain)); + extensions.set(packAddress(plugin1), -1n); + extensions.set(packAddress(plugin2), -1n); + extensions.set(packAddress(plugin3), -1n); await deploy({ extensions }); const actual = await walletV5.getExtensionsArray(); - expect(actual.length).toBe(2); + expect(actual.length).toBe(3); expect(actual[0].equals(plugin1)).toBeTruthy(); expect(actual[1].equals(plugin2)).toBeTruthy(); + expect(actual[2].equals(plugin3)).toBeTruthy(); }); it('Get empty extensions array', async () => { diff --git a/tests/wallet-v5-internal.spec.ts b/tests/wallet-v5-internal.spec.ts index 61f79f0..963535f 100644 --- a/tests/wallet-v5-internal.spec.ts +++ b/tests/wallet-v5-internal.spec.ts @@ -1,8 +1,8 @@ -import {Blockchain, BlockchainTransaction, SandboxContract} from '@ton-community/sandbox'; -import { Address, beginCell, Cell, Dictionary, Sender, SendMode, toNano } from 'ton-core'; +import {Blockchain, BlockchainTransaction, SandboxContract} from '@ton/sandbox'; +import { Address, beginCell, Cell, Dictionary, Sender, SendMode, toNano } from '@ton/core'; import { Opcodes, WalletId, WalletV5 } from '../wrappers/wallet-v5'; -import '@ton-community/test-utils'; -import { compile } from '@ton-community/blueprint'; +import '@ton/test-utils'; +import { compile } from '@ton/blueprint'; import { getSecureRandomBytes, KeyPair, keyPairFromSeed, sign } from 'ton-crypto'; import { bufferToBigInt, createMsgInternal, disableConsoleError, packAddress, validUntil } from './utils'; import { @@ -11,8 +11,8 @@ import { ActionSendMsg, ActionSetSignatureAuthAllowed, packActionsList } from './actions'; -import { TransactionDescriptionGeneric } from 'ton-core/src/types/TransactionDescription'; -import { TransactionComputeVm } from 'ton-core/src/types/TransactionComputePhase'; +import { TransactionDescriptionGeneric } from '@ton/core/src/types/TransactionDescription'; +import { TransactionComputeVm } from '@ton/core/src/types/TransactionComputePhase'; import { buildBlockchainLibraries, LibraryDeployer } from '../wrappers/library-deployer'; import { default as config } from './config'; import { ActionSetCode, ActionSetData } from './test-only-actions'; @@ -51,6 +51,7 @@ describe('Wallet V5 sign auth internal', () => { const _walletV5 = blockchain.openContract( WalletV5.createFromConfig( { + signatureAllowed: true, seqno: params?.seqno ?? 0, walletId: params?.walletId ?? WALLET_ID.serialized, publicKey: params?.publicKey ?? _keypair.publicKey, @@ -70,7 +71,7 @@ describe('Wallet V5 sign auth internal', () => { function createBody(actionsList: Cell) { const payload = beginCell() .storeUint(Opcodes.auth_signed_internal, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -93,6 +94,7 @@ describe('Wallet V5 sign auth internal', () => { walletV5 = blockchain.openContract( WalletV5.createFromConfig( { + signatureAllowed: true, seqno: 0, walletId: WALLET_ID.serialized, publicKey: keypair.publicKey, @@ -138,13 +140,13 @@ describe('Wallet V5 sign auth internal', () => { .endCell(); const actionsList = beginCell() - .storeUint(0, 1) - .storeRef( + .storeMaybeRef( beginCell() .storeRef(beginCell().endCell()) .storeSlice(sendTxactionAction.beginParse()) .endCell() ) + .storeUint(0, 1) // no other actions .endCell(); if (config.microscope) @@ -184,13 +186,13 @@ describe('Wallet V5 sign auth internal', () => { const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const addExtensionAction = beginCell() - .storeUint(Opcodes.action_extended_add_extension, 32) + .storeUint(Opcodes.action_extended_add_extension, 8) .storeAddress(testExtension) .endCell(); const actionsList = beginCell() + .storeUint(0, 1) // no c5 actions .storeUint(1, 1) - .storeRef(beginCell().storeUint(0, 1).storeRef(beginCell().endCell()).endCell()) .storeSlice(addExtensionAction.beginParse()) .endCell(); @@ -205,14 +207,14 @@ describe('Wallet V5 sign auth internal', () => { const extensions = await walletV5.getExtensions(); const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), extensions ); expect(extensionsDict.size).toEqual(1); const storedWC = extensionsDict.get(packAddress(testExtension)); - expect(storedWC).toEqual(BigInt(testExtension.workChain)); + expect(storedWC).toEqual(-1n); }); it('Send two transfers', async () => { @@ -266,7 +268,7 @@ describe('Wallet V5 sign auth internal', () => { }); it('Add two extensions and do a transfer', async () => { - const testExtension1 = Address.parse('Ef82pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX28X'); + const testExtension1 = Address.parse('EQA2pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX5Bf'); const testExtension2 = Address.parse('EQCgYDKqfTh7zVj9BQwOIPs4SuOhM7wnIjb6bdtM2AJf_Z9G'); const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); @@ -302,17 +304,17 @@ describe('Wallet V5 sign auth internal', () => { const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict.size).toEqual(2); expect(extensionsDict.get(packAddress(testExtension1))).toEqual( - BigInt(testExtension1.workChain) + -1n ); expect(extensionsDict.get(packAddress(testExtension2))).toEqual( - BigInt(testExtension2.workChain) + -1n ); }); @@ -326,12 +328,12 @@ describe('Wallet V5 sign auth internal', () => { }); const extensionsDict1 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict1.size).toEqual(1); expect(extensionsDict1.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); const actionsList2 = packActionsList([new ActionRemoveExtension(testExtension)]); @@ -341,7 +343,7 @@ describe('Wallet V5 sign auth internal', () => { }); const extensionsDict2 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); @@ -368,7 +370,7 @@ describe('Wallet V5 sign auth internal', () => { (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(41); + ).toEqual(141); }); it('Should fail SetCode action', async () => { @@ -400,12 +402,12 @@ describe('Wallet V5 sign auth internal', () => { }); const extensionsDict1 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict1.size).toEqual(1); expect(extensionsDict1.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); const actionsList2 = packActionsList([new ActionAddExtension(testExtension)]); @@ -419,16 +421,16 @@ describe('Wallet V5 sign auth internal', () => { (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(39); + ).toEqual(139); const extensionsDict2 = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict2.size).toEqual(1); expect(extensionsDict2.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); }); @@ -446,11 +448,11 @@ describe('Wallet V5 sign auth internal', () => { (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(40); + ).toEqual(140); const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); expect(extensionsDict.size).toEqual(0); @@ -469,14 +471,14 @@ describe('Wallet V5 sign auth internal', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed_internal, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(vu, 32) .storeUint(seqno, 32) // seqno .storeSlice(actionsList.beginParse()) .endCell(); const fakePayload = beginCell() - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(vu, 32) .storeUint(seqno + 1, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -521,7 +523,7 @@ describe('Wallet V5 sign auth internal', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed_internal, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -575,7 +577,7 @@ describe('Wallet V5 sign auth internal', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed_internal, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno + 1, 32) // seqno .storeSlice(actionsList.beginParse()) @@ -597,7 +599,7 @@ describe('Wallet V5 sign auth internal', () => { (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(33); + ).toEqual(133); expect(receipt.transactions).not.toHaveTransaction({ from: walletV5.address, @@ -620,7 +622,7 @@ describe('Wallet V5 sign auth internal', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed_internal, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(Math.round(Date.now() / 1000) - 600, 32) .storeUint(seqno, 32) .storeSlice(actionsList.beginParse()) @@ -642,7 +644,7 @@ describe('Wallet V5 sign auth internal', () => { (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(36); + ).toEqual(136); expect(receipt.transactions).not.toHaveTransaction({ from: walletV5.address, @@ -665,7 +667,7 @@ describe('Wallet V5 sign auth internal', () => { const payload = beginCell() .storeUint(Opcodes.auth_signed_internal, 32) - .storeUint(new WalletId({ ...WALLET_ID, subwalletNumber: 1 }).serialized, 80) + .storeUint(new WalletId({ ...WALLET_ID, subwalletNumber: 1 }).serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) .storeSlice(actionsList.beginParse()) @@ -687,7 +689,7 @@ describe('Wallet V5 sign auth internal', () => { (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(34); + ).toEqual(134); expect(receipt.transactions).not.toHaveTransaction({ from: walletV5.address, @@ -710,7 +712,7 @@ describe('Wallet V5 sign auth internal', () => { const payload = beginCell() // auth_signed used instead of auth_signed_internal .storeUint(Opcodes.auth_signed, 32) - .storeUint(WALLET_ID.serialized, 80) + .storeUint(WALLET_ID.serialized, 32) .storeUint(validUntil(), 32) .storeUint(seqno, 32) .storeSlice(actionsList.beginParse()) @@ -854,53 +856,76 @@ describe('Wallet V5 sign auth internal', () => { it('Should fail disallowing signature auth with no exts', async () => { const actionsList = packActionsList([ - new ActionSetSignatureAuthAllowed(false) + new ActionAddExtension(sender.address!) ]); - const receipt = await walletV5.sendInternal(sender, { + await walletV5.sendInternal(sender, { sendMode: SendMode.PAY_GAS_SEPARATELY, value: toNano(0.1), body: createBody(actionsList) }); - expect(receipt.transactions.length).toEqual(2); + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: packActionsList([ + new ActionRemoveExtension(sender.address!), + new ActionSetSignatureAuthAllowed(false) + ]) + }); + + expect(receipt.transactions.length).toEqual(3); // sender_wallet -> wallet_v5 -> bounced expect( ( (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(42); + ).toEqual(142); const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); expect(isSignatureAuthAllowed).toEqual(-1); }); it('Should fail allowing signature auth when allowed', async () => { + await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(packActionsList([ + new ActionAddExtension(sender.address!) + ])) + }); + const actionsList = packActionsList([ new ActionSetSignatureAuthAllowed(true) ]); - const receipt = await walletV5.sendInternal(sender, { - sendMode: SendMode.PAY_GAS_SEPARATELY, - value: toNano(0.1), - body: createBody(actionsList) + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList }); - expect(receipt.transactions.length).toEqual(2); + expect(receipt.transactions.length).toEqual(3); // sender_wallet -> wallet_v5 -> bounced expect( ( (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(43); + ).toEqual(143); const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); expect(isSignatureAuthAllowed).toEqual(-1); }); it('Should add ext and disallow signature auth', async () => { + await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(packActionsList([ + new ActionAddExtension(sender.address!) + ])) + }); + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const actionsList = packActionsList([ @@ -908,10 +933,9 @@ describe('Wallet V5 sign auth internal', () => { new ActionSetSignatureAuthAllowed(false) ]); - const receipt = await walletV5.sendInternal(sender, { - sendMode: SendMode.PAY_GAS_SEPARATELY, - value: toNano(0.1), - body: createBody(actionsList) + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList }); expect(receipt.transactions.length).toEqual(2); @@ -920,14 +944,14 @@ describe('Wallet V5 sign auth internal', () => { const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); - expect(extensionsDict.size).toEqual(1); + expect(extensionsDict.size).toEqual(2); expect(extensionsDict.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); expect( @@ -941,20 +965,27 @@ describe('Wallet V5 sign auth internal', () => { expect(isSignatureAuthAllowed).toEqual(0); const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 1); + expect(contract_seqno).toEqual(seqno); }); it('Should add ext and disallow signature auth in separate txs', async () => { + await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(packActionsList([ + new ActionAddExtension(sender.address!) + ])) + }); + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const actionsList = packActionsList([ new ActionAddExtension(testExtension) ]); - const receipt = await walletV5.sendInternal(sender, { - sendMode: SendMode.PAY_GAS_SEPARATELY, - value: toNano(0.1), - body: createBody(actionsList) + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList }); expect(receipt.transactions.length).toEqual(2); @@ -963,14 +994,14 @@ describe('Wallet V5 sign auth internal', () => { const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); - expect(extensionsDict.size).toEqual(1); + expect(extensionsDict.size).toEqual(2); expect(extensionsDict.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); expect( @@ -987,10 +1018,9 @@ describe('Wallet V5 sign auth internal', () => { new ActionSetSignatureAuthAllowed(false) ]); - const receipt2 = await walletV5.sendInternal(sender, { - sendMode: SendMode.PAY_GAS_SEPARATELY, - value: toNano(0.1), - body: createBody(actionsList2) + const receipt2 = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList2 }); expect(receipt2.transactions.length).toEqual(2); @@ -1008,10 +1038,18 @@ describe('Wallet V5 sign auth internal', () => { expect(isSignatureAuthAllowed2).toEqual(0); const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 1); + expect(contract_seqno).toEqual(seqno); }); it('Should add ext, disallow sign, allow sign, remove ext in one tx; send in other', async () => { + await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(packActionsList([ + new ActionAddExtension(sender.address!) + ])) + }); + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const actionsList = packActionsList([ @@ -1020,11 +1058,9 @@ describe('Wallet V5 sign auth internal', () => { new ActionSetSignatureAuthAllowed(true), new ActionRemoveExtension(testExtension), ]); - - const receipt = await walletV5.sendInternal(sender, { - sendMode: SendMode.PAY_GAS_SEPARATELY, - value: toNano(0.1), - body: createBody(actionsList) + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList }); expect(receipt.transactions.length).toEqual(2); @@ -1041,10 +1077,7 @@ describe('Wallet V5 sign auth internal', () => { expect(isSignatureAuthAllowed).toEqual(-1); const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 2); - - // Allowing or disallowing signature auth increments seqno, need to re-read - seqno = contract_seqno; + expect(contract_seqno).toEqual(seqno); const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const forwardValue = toNano(0.001); @@ -1078,21 +1111,29 @@ describe('Wallet V5 sign auth internal', () => { }); it('Should fail removing last extension with signature auth disabled', async () => { + await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(packActionsList([ + new ActionAddExtension(sender.address!) + ])) + }); + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const actionsList = packActionsList([ new ActionAddExtension(testExtension), new ActionSetSignatureAuthAllowed(false), - new ActionRemoveExtension(testExtension) + new ActionRemoveExtension(testExtension), + new ActionRemoveExtension(sender.address!) ]); - const receipt = await walletV5.sendInternal(sender, { - sendMode: SendMode.PAY_GAS_SEPARATELY, - value: toNano(0.1), - body: createBody(actionsList) + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList }); - expect(receipt.transactions.length).toEqual(2); + expect(receipt.transactions.length).toEqual(3); // sender_wallet -> wallet_v5 -> bounced accountForGas(receipt.transactions); expect( @@ -1100,13 +1141,21 @@ describe('Wallet V5 sign auth internal', () => { (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(44); + ).toEqual(144); const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); expect(isSignatureAuthAllowed).toEqual(-1); }); it('Should fail disallowing signature auth twice in tx', async () => { + await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(packActionsList([ + new ActionAddExtension(sender.address!) + ])) + }); + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const actionsList = packActionsList([ @@ -1115,13 +1164,12 @@ describe('Wallet V5 sign auth internal', () => { new ActionSetSignatureAuthAllowed(false) ]); - const receipt = await walletV5.sendInternal(sender, { - sendMode: SendMode.PAY_GAS_SEPARATELY, - value: toNano(0.1), - body: createBody(actionsList) + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList }); - expect(receipt.transactions.length).toEqual(2); + expect(receipt.transactions.length).toEqual(3); // sender_wallet -> wallet_v5 -> bounced accountForGas(receipt.transactions); expect( @@ -1129,13 +1177,21 @@ describe('Wallet V5 sign auth internal', () => { (receipt.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(43); + ).toEqual(143); const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); expect(isSignatureAuthAllowed).toEqual(-1); // throw when handling, packet is dropped }); it('Should add ext, disallow sig auth; fail different signed tx', async () => { + await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(packActionsList([ + new ActionAddExtension(sender.address!) + ])) + }); + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const actionsList = packActionsList([ @@ -1143,10 +1199,9 @@ describe('Wallet V5 sign auth internal', () => { new ActionSetSignatureAuthAllowed(false) ]); - const receipt = await walletV5.sendInternal(sender, { - sendMode: SendMode.PAY_GAS_SEPARATELY, - value: toNano(0.1), - body: createBody(actionsList) + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList }); expect(receipt.transactions.length).toEqual(2); @@ -1154,14 +1209,14 @@ describe('Wallet V5 sign auth internal', () => { const extensionsDict = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), await walletV5.getExtensions() ); - expect(extensionsDict.size).toEqual(1); + expect(extensionsDict.size).toEqual(2); expect(extensionsDict.get(packAddress(testExtension))).toEqual( - BigInt(testExtension.workChain) + -1n ); expect( @@ -1175,7 +1230,7 @@ describe('Wallet V5 sign auth internal', () => { expect(isSignatureAuthAllowed).toEqual(0); const contract_seqno = await walletV5.getSeqno(); - expect(contract_seqno).toEqual(seqno + 1); + expect(contract_seqno).toEqual(seqno); const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); const forwardValue = toNano(0.001); @@ -1194,7 +1249,7 @@ describe('Wallet V5 sign auth internal', () => { (receipt2.transactions[1].description as TransactionDescriptionGeneric) .computePhase as TransactionComputeVm ).exitCode - ).toEqual(33); + ).toEqual(132); expect(receipt2.transactions).not.toHaveTransaction({ from: walletV5.address, diff --git a/types.tlb b/types.tlb index 0ac3940..6390684 100644 --- a/types.tlb +++ b/types.tlb @@ -27,4 +27,4 @@ actions$_ {m:#} {n:#} actions:(ActionList n m) = InnerRequest; // Contract state wallet_id$_ global_id:int32 wc:int8 version:(## 8) subwallet_number:(## 32) = WalletID; -contract_state$_ seqno:int33 wallet_id:WalletID public_key:(## 256) extensions_dict:(HashmapE 256 int8) = ContractState; +contract_state$_ signature_auth_disabled:(## 1) seqno:# wallet_id:WalletID public_key:(## 256) extensions_dict:(HashmapE 256 int8) = ContractState; diff --git a/wrappers/Errors.ts b/wrappers/Errors.ts new file mode 100644 index 0000000..b1c3b93 --- /dev/null +++ b/wrappers/Errors.ts @@ -0,0 +1,18 @@ +export abstract class ErrorsV5 { + static readonly signature_disabled = 132; + static readonly invalid_seqno = 133; + static readonly invalid_wallet_id = 134; + static readonly invalid_signature = 135; + static readonly expired = 136; + static readonly external_send_message_must_have_ignore_errors_send_mode = 137; + static readonly invalid_message_operation = 138; + static readonly add_extension = 139; + static readonly remove_extension = 140; + static readonly unsupported_action = 141; + static readonly disable_signature_when_extensions_is_empty = 142; + static readonly this_signature_mode_already_set = 143; + static readonly remove_last_extension_when_signature_disabled = 144; + static readonly extension_wrong_workchain = 145; + static readonly only_extension_can_change_signature_mode = 146; + static readonly invalid_c5 = 147; +} diff --git a/wrappers/library-deployer.ts b/wrappers/library-deployer.ts index efec67e..cb3c15c 100644 --- a/wrappers/library-deployer.ts +++ b/wrappers/library-deployer.ts @@ -11,8 +11,8 @@ import { Sender, SendMode, SimpleLibrary -} from 'ton-core'; -import { SimpleLibraryValue } from 'ton-core/dist/types/SimpleLibrary'; +} from '@ton/core'; +import { SimpleLibraryValue } from '@ton/core/dist/types/SimpleLibrary'; export type LibraryDeployerConfig = { libraryCode: Cell; diff --git a/wrappers/wallet-v5-test.ts b/wrappers/wallet-v5-test.ts new file mode 100644 index 0000000..2d5c09e --- /dev/null +++ b/wrappers/wallet-v5-test.ts @@ -0,0 +1,190 @@ +import { Cell, beginCell, Sender, ContractProvider, SendMode, MessageRelaxed, Address, toNano, contractAddress, OutAction, OutActionSendMsg, Builder, storeOutList } from '@ton/core'; +import { WalletV5, WalletV5Config, walletV5ConfigToCell, Opcodes } from './wallet-v5'; +import { sign } from '@ton/crypto'; + +export type WalletActions = { + wallet?: OutAction[] | Cell, + extended?: ExtendedAction[] | Cell +} + +export type ExtensionAdd = { + type: 'add_extension', + address: Address +} +export type ExtensionRemove = { + type: 'remove_extension', + address: Address +} + +export type SetSignatureAuth = { + type: 'sig_auth', + allowed: boolean +} + +export type ExtendedAction = ExtensionAdd | ExtensionRemove | SetSignatureAuth; + +export type MessageOut = { + message: MessageRelaxed, + mode: SendMode +}; + +function storeWalletActions(actions: WalletActions) { + // store compatable + return (builder: Builder) => { + let hasExtendedActions = false; + if(actions.wallet) { + let actionCell: Cell | null = null; + if(actions.wallet instanceof Cell) { + actionCell = actions.wallet; + } + else if(actions.wallet.length > 0) { + actionCell = beginCell().store(storeOutList(actions.wallet)).endCell(); + } + builder.storeMaybeRef(actionCell); + } + else { + builder.storeBit(false); + } + if(actions.extended) { + if(actions.extended instanceof Cell) { + builder.storeBit(true); + builder.storeSlice(actions.extended.asSlice()); + } + else if(actions.extended.length > 0) { + builder.storeBit(true); + builder.store(storeExtendedActions(actions.extended)); + } + else { + builder.storeBit(false); + } + } + else { + builder.storeBit(false); + } + } +} + +function storeExtensionAction(action: ExtendedAction) { + return (builder: Builder) => { + if(action.type == 'add_extension') { + builder.storeUint(2, 8).storeAddress(action.address); + } + else if(action.type == 'remove_extension') { + builder.storeUint(3, 8).storeAddress(action.address); + } + else { + builder.storeUint(4, 8).storeBit(action.allowed); + } + } +} + +export function storeExtendedActions(actions: ExtendedAction[]) { + const cell = actions.reverse().reduce((curCell, action) => { + const ds = beginCell().store(storeExtensionAction(action)); + if(curCell.bits.length > 0) { + ds.storeRef(curCell); + } + return ds.endCell(); + }, beginCell().endCell()); + + return (builder: Builder) => builder.storeSlice(cell.beginParse()); +} + +export function message2action(msg: MessageOut) : OutActionSendMsg { + return { + type: 'sendMsg', + mode: msg.mode, + outMsg: msg.message + } +} + + +export class WalletV5Test extends WalletV5 { + constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) { + super(address, init); + } + static createFromAddress(address: Address) { + return new WalletV5Test(address); + } + + static createFromConfig(config: WalletV5Config, code: Cell, workchain = 0) { + const data = walletV5ConfigToCell(config); + const init = { code, data }; + return new WalletV5Test(contractAddress(workchain, init), init); + } + static requestMessage(internal: boolean, wallet_id: bigint, valid_until: number, seqno: bigint | number, actions: WalletActions, key?: Buffer) { + const op = internal ? Opcodes.auth_signed_internal : Opcodes.auth_signed; + const msgBody = beginCell().storeUint(op, 32) + .storeUint(wallet_id, 32) + .storeUint(valid_until, 32) + .storeUint(seqno, 32) + .store(storeWalletActions(actions)) + .endCell(); + return key ? WalletV5Test.signRequestMessage(msgBody, key) : msgBody; + } + + static signRequestMessage(msg: Cell, key: Buffer) { + const signature = sign(msg.hash(), key); + + return beginCell().storeSlice(msg.asSlice()).storeBuffer(signature).endCell(); + } + async sendMessagesExternal(provider: ContractProvider, + wallet_id: bigint, + valid_until: number, + seqno: bigint | number, + key: Buffer, messages: MessageOut[]) { + const actions: OutActionSendMsg[] = messages.map(message2action); + + await provider.external( + WalletV5Test.requestMessage(false, wallet_id, valid_until, seqno, {wallet: actions}, key) + ); + } + + static extensionMessage(actions: WalletActions, query_id: bigint | number = 0) { + return beginCell() + .storeUint(Opcodes.auth_extension, 32) + .storeUint(query_id, 64) + .store(storeWalletActions(actions)) + .endCell(); + } + async sendExtensionActions(provider: ContractProvider, + via: Sender, + actions: WalletActions, + value: bigint = toNano('0.1'), + query_id: bigint | number = 0) { + + await provider.internal(via, { + value, + body: WalletV5Test.extensionMessage(actions, query_id), + sendMode: SendMode.PAY_GAS_SEPARATELY + }); + } + + async sendMessagesInternal(provider: ContractProvider, via: Sender, + wallet_id: bigint, + valid_until: number, + seqno: bigint | number, + key: Buffer, messages: MessageOut[], value: bigint = toNano('0.05')) { + + const actions: OutActionSendMsg[] = messages.map(message2action); + + await provider.internal(via, { + value, + body: WalletV5Test.requestMessage(true, wallet_id, valid_until, seqno, {wallet: actions}, key), + sendMode: SendMode.PAY_GAS_SEPARATELY + }); + } + + /* + async sendAddExtensionViaExternal(provider: ContractProvider, + wallet_id: bigint, + valid_until: number, + seqno: bigint | number, + key: Buffer, + extensions: Address[]) { + const reqMsg = WalletV5Test.requestMessage(false, wallet_id, valid_until, seqno, {extension: beginCell().endCell()}, key); + + await provider.external(reqMsg); + } + */ +} diff --git a/wrappers/wallet-v5.ts b/wrappers/wallet-v5.ts index fe0c708..27963df 100644 --- a/wrappers/wallet-v5.ts +++ b/wrappers/wallet-v5.ts @@ -9,12 +9,21 @@ import { contractAddress, ContractProvider, Dictionary, + MessageRelaxed, + storeOutList, + OutAction, Sender, - SendMode -} from 'ton-core'; + SendMode, + Builder, + OutActionSendMsg, + toNano +} from '@ton/core'; import { bufferToBigInt } from '../tests/utils'; +import { sign } from '@ton/crypto'; + export type WalletV5Config = { + signatureAllowed: boolean; seqno: number; walletId: bigint; publicKey: Buffer; @@ -23,10 +32,11 @@ export type WalletV5Config = { export function walletV5ConfigToCell(config: WalletV5Config): Cell { return beginCell() - .storeInt(config.seqno, 33) - .storeUint(config.walletId, 80) + .storeBit(config.signatureAllowed) + .storeUint(config.seqno, 32) + .storeUint(config.walletId, 32) .storeBuffer(config.publicKey, 32) - .storeDict(config.extensions, Dictionary.Keys.BigUint(256), Dictionary.Values.BigInt(8)) + .storeDict(config.extensions, Dictionary.Keys.BigUint(256), Dictionary.Values.BigInt(1)) .endCell(); } @@ -34,9 +44,9 @@ export const Opcodes = { action_send_msg: 0x0ec3c86d, action_set_code: 0xad4de08e, action_extended_set_data: 0x1ff8ea0b, - action_extended_add_extension: 0x1c40db9f, - action_extended_remove_extension: 0x5eaef4a4, - action_extended_set_signature_auth_allowed: 0x20cbb95a, + action_extended_add_extension: 0x02, + action_extended_remove_extension: 0x03, + action_extended_set_signature_auth_allowed: 0x04, auth_extension: 0x6578746e, auth_signed: 0x7369676e, auth_signed_internal: 0x73696e74 @@ -47,30 +57,30 @@ export class WalletId { v5: 0 }; - static deserialize(walletId: bigint | Buffer): WalletId { - const bitReader = new BitReader( - new BitString( - typeof walletId === 'bigint' ? Buffer.from(walletId.toString(16), 'hex') : walletId, - 0, - 80 - ) - ); - const networkGlobalId = bitReader.loadInt(32); - const workChain = bitReader.loadInt(8); - const walletVersionRaw = bitReader.loadUint(8); - const subwalletNumber = bitReader.loadUint(32); - - const walletVersion = Object.entries(this.versionsSerialisation).find( - ([_, value]) => value === walletVersionRaw - )?.[0] as WalletId['walletVersion'] | undefined; - - if (walletVersion === undefined) { - throw new Error( - `Can't deserialize walletId: unknown wallet version ${walletVersionRaw}` - ); - } - - return new WalletId({ networkGlobalId, workChain, walletVersion, subwalletNumber }); + static deserialize(walletId: bigint): WalletId { + // const bitReader = new BitReader( + // new BitString( + // typeof walletId === 'bigint' ? Buffer.from(walletId.toString(16), 'hex') : walletId, + // 0, + // 32 + // ) + // ); + // const networkGlobalId = bitReader.loadInt(32); + // const workChain = bitReader.loadInt(8); + // const walletVersionRaw = bitReader.loadUint(8); + const subwalletNumber = walletId; + // + // const walletVersion = Object.entries(this.versionsSerialisation).find( + // ([_, value]) => value === walletVersionRaw + // )?.[0] as WalletId['walletVersion'] | undefined; + // + // if (walletVersion === undefined) { + // throw new Error( + // `Can't deserialize walletId: unknown wallet version ${walletVersionRaw}` + // ); + // } + // + return new WalletId({ networkGlobalId: 0, workChain: 0, walletVersion: 'v5', subwalletNumber: Number(walletId) }); } readonly walletVersion: 'v5'; @@ -95,13 +105,13 @@ export class WalletId { this.subwalletNumber = args?.subwalletNumber ?? 0; this.walletVersion = args?.walletVersion ?? 'v5'; - const bitBuilder = new BitBuilder(80); - bitBuilder.writeInt(this.networkGlobalId, 32); - bitBuilder.writeInt(this.workChain, 8); - bitBuilder.writeUint(WalletId.versionsSerialisation[this.walletVersion], 8); - bitBuilder.writeUint(this.subwalletNumber, 32); + // const bitBuilder = new BitBuilder(32); + // bitBuilder.writeInt(this.networkGlobalId, 32); + // bitBuilder.writeInt(this.workChain, 8); + // bitBuilder.writeUint(WalletId.versionsSerialisation[this.walletVersion], 8); + // bitBuilder.writeUint(this.subwalletNumber, 32); - this.serialized = bufferToBigInt(bitBuilder.buffer()); + this.serialized = BigInt(this.subwalletNumber) // bufferToBigInt(bitBuilder.buffer()); } } @@ -157,6 +167,7 @@ export class WalletV5 implements Contract { sendMode: SendMode.PAY_GAS_SEPARATELY, body: beginCell() .storeUint(Opcodes.auth_extension, 32) + .storeUint(0, 64) // query id .storeSlice(opts.body.beginParse()) .endCell() }); @@ -171,12 +182,7 @@ export class WalletV5 implements Contract { } async sendExternalSignedMessage(provider: ContractProvider, body: Cell) { - await provider.external( - beginCell() - // .storeUint(Opcodes.auth_signed, 32) // Is signed inside message - .storeSlice(body.beginParse()) - .endCell() - ); + await provider.external(body); } async sendExternal(provider: ContractProvider, body: Cell) { @@ -201,7 +207,7 @@ export class WalletV5 implements Contract { async getIsSignatureAuthAllowed(provider: ContractProvider) { const state = await provider.getState(); if (state.state.type === 'active') { - let res = await provider.get('get_is_signature_auth_allowed', []); + let res = await provider.get('is_signature_allowed', []); return res.stack.readNumber(); } else { return -1; @@ -209,7 +215,7 @@ export class WalletV5 implements Contract { } async getWalletId(provider: ContractProvider) { - const result = await provider.get('get_wallet_id', []); + const result = await provider.get('get_subwallet_id', []); return WalletId.deserialize(result.stack.readBigNumber()); } @@ -226,14 +232,14 @@ export class WalletV5 implements Contract { const dict: Dictionary = Dictionary.loadDirect( Dictionary.Keys.BigUint(256), - Dictionary.Values.BigInt(8), + Dictionary.Values.BigInt(1), extensions ); return dict.keys().map(key => { - const wc = dict.get(key)!; - const addressHex = key ^ (wc + 1n); - return Address.parseRaw(`${wc}:${addressHex.toString(16)}`); + const wc = this.address.workChain; + const addressHex = key; + return Address.parseRaw(`${wc}:${addressHex.toString(16).padStart(64, '0')}`); }); } } diff --git a/wrappers/wallet_v5.compile.ts b/wrappers/wallet_v5.compile.ts index c4cb126..e9b50f6 100644 --- a/wrappers/wallet_v5.compile.ts +++ b/wrappers/wallet_v5.compile.ts @@ -1,4 +1,4 @@ -import { CompilerConfig } from '@ton-community/blueprint'; +import { CompilerConfig } from '@ton/blueprint'; export const compile: CompilerConfig = { lang: 'func',