diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..3d3ea2bd --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +WALLET_MNEMONIC= + +# v4 +WALLET_VERSION= diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..b8133071 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,91 @@ +module.exports = { + root: true, + ignorePatterns: ['**/*.js'], + overrides: [ + { + files: ['tests/**/*.ts', 'wrappers/**/*.ts', 'scripts/**/*.ts'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + createDefaultProgram: true + }, + plugins: [ + '@typescript-eslint', + 'unused-imports', + 'import' + ], + extends: [ + 'airbnb-typescript/base', + 'plugin:prettier/recommended', + 'prettier' + ], + rules: { + 'import/extensions': [ + 'error', + 'ignorePackages', + { + js: "never", + jsx: "never", + ts: "never", + tsx: "never" + } + ], + 'import/prefer-default-export': 'off', + '@typescript-eslint/no-useless-constructor': 'off', + 'no-plusplus': 'off', + 'class-method-use-this': 'off', + 'no-underscore-dangle': 'off', + 'no-inferrable-types': 'off', + '@typescript-eslint/no-explicit-any': 2, + '@typescript-eslint/no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'error', + { + vars: 'all', + args: 'all', + ignoreRestSiblings: false, + argsIgnorePattern: '^_' + } + ], + '@typescript-eslint/no-inferrable-types': 'off', + 'class-methods-use-this': 'off', + complexity: ['error', 20], + eqeqeq: ['error', 'smart'], + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'enumMember', + format: ['UPPER_CASE'] + } + ], + 'no-empty': ['error', { 'allowEmptyCatch': true }], + + 'array-bracket-spacing': ['error', 'never'], + 'object-curly-spacing': ['error', 'always'], + indent: 'off', + 'comma-dangle': 'off', + '@typescript-eslint/comma-dangle': ['error', 'never'], + 'import/no-extraneous-dependencies': 'off', + '@typescript-eslint/dot-notation': 'off', + 'no-restricted-globals': 'off', + '@typescript-eslint/no-empty-function': 'off', + 'no-param-reassign': 'off', + 'max-classes-per-file': 'off', + radix: ['warn', 'as-needed'], + 'no-prototype-builtins': 'off', + 'no-return-assign': 'off', + 'no-restricted-syntax': [ + 'error', + 'LabeledStatement', + 'WithStatement' + ], + 'no-console': 'off', + 'import/export': 0, + '@typescript-eslint/no-shadow': 'off', + '@typescript-eslint/return-await': 'off' + } + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..20001d42 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: Run tests + +on: + push: + branches: [ "main", "feature/optimisation" ] + pull_request: + branches: [ "main", "feature/optimisation" ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Read .nvmrc + run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_ENV + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '${{ env.NVMRC }}' + + - name: Set up dependencies + run: npm ci + + - name: Test + run: npm run test diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml new file mode 100644 index 00000000..db9ffebf --- /dev/null +++ b/.github/workflows/webpack.yml @@ -0,0 +1,28 @@ +tabname: NodeJS with Webpack + +esc on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + tab runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Build + run: | + npm install + npx webpack diff --git a/.gitignore b/.gitignore index 211f8113..6f054627 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules temp -build +.idea +.env diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..860cc500 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v18.17.1 diff --git a/.prettierignore b/.prettierignore index 378eac25..d9a7d485 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ build +contracts diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 24a6660a..00000000 --- a/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "printWidth": 120, - "tabWidth": 4, - "singleQuote": true, - "bracketSpacing": true, - "semi": true -} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..cb1a82e9 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json.schemastore.org/prettierrc", + "arrowParens": "avoid", + "bracketSpacing": true, + "printWidth": 100, + "proseWrap": "always", + "quoteProps": "as-needed", + "semi": true, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "none", + "useTabs": false +} diff --git a/README.md b/README.md index c334f65f..c5c68978 100644 --- a/README.md +++ b/README.md @@ -1,370 +1,89 @@ -# Extensible Wallet V5 +tab# W5: wallet smart contract v5 -Author: Oleg Andreev +New version of wallet smart contract, the previous one was [v4r2](https://github.com/ton-blockchain/wallet-contract). -This is an extensible wallet specification aimed at replacing V4 and allowing arbitrary extensions. +The entire concept is proposed by the [Tonkeeper team](https://tonkeeper.com/). -* [Features](#features) -* [Overview](#overview) -* [Discussion](#discussion) -* [Wallet ID](#wallet-id) -* [TL-B definitions](#tl-b-definitions) -* [Source code](#source-code) +New Features: +- Send up to 255 messages at once; -## Credits +- Signed actions can be sent not only by external message, but also by internal messages (can be used for gasless transactions); -Thanks to [Andrew Gutarev](https://github.com/pyAndr3w) for the idea to set c5 register to a [list of pre-composed actions](https://github.com/pyAndr3w/ton-preprocessed-wallet-v2). +- Unlimited extensions; -Thanks to [@subden](https://t.me/subden) and [@botpult](https://t.me/botpult) for ideas and discussion. +- Extension can prohibit signed actions in the wallet (can be used for 2fa or key recovery); +- Optimizations to reduce network fees; -## Features +- Better foolproofing safety - reply-protection for external messages, wallet id rethinking; -* Arbitrary amount of outgoing messages is supported via action list. -* Wallet code can be upgraded transparently without breaking user's address in the future. -* Unlimited number of plugins can be deployed sharing the same code. -* Wallet code can be extended by anyone in a decentralized and conflict-free way: multiple feature extensions can co-exist. -* Extensions can perform the same operations as the signer: emit arbitrary messages on behalf of the owner, add and remove extensions. -* Signed requests can be delivered via internal message to allow 3rd party pay for gas. -* Extensible ABI for future additions. +esc## Project structure -## Overview +- [Specification](Specification.md) +- `contracts` - source code of all the smart contracts of the project and their dependencies. +- `wrappers` - wrapper classes (implementing `Contract` from ton-core) for the contracts, including any [de]serialization primitives and compilation functions. +- `tests` - tests for the contracts. +- `scripts` - scripts used by the project, mainly the deployment scripts, additionally contains utilities for gas optimisation. +- `fift` - contains standard Fift v0.4.4 library including the assembler and disassembler for gas optimisation utilities. -Wallet V5 supports **2 authentication modes**, all standard output actions (send message, set library, code replacement) plus additional **3 operation types**. +## How to use -Authentication: -* by signature -* by extension +### Build -Operation types: -* standard output actions -* “set data” -* install extension -* remove extension +`npm run build` -Signed messages can be delivered both by external and internal messages. +### Test -All operation types are available to all authentication modes. +`npm run test` -## Discussion +### Deployment -### What is the job of the wallet? +Deploy wallet: `npm run deploy-wallet` -The job of the wallet is to send messages to other apps in the TON network on behalf of a single user identified by a single public key. -User may delegate this job to other apps via extensions. +tab### Known issues -### The wallet is not for: +1) Since the `valid_until` is uint32 it will not work after 2106 year. We believe new versions of wallet smart contract will be available by then. -* multi-user operation: you should use a multisig or DAO solution instead. -* routing of incoming payments and messages: use a specialized contract instead. -* imposing limits on access to certain assets: put account restriction inside a jetton, or use a lockup contract instead. +2) If the `action_send_msg` content is invalid and the sendmode has +2, the error will not be ignored. An update of the node is planned where this behaviour will be changed (with +2 sendmode and `action_send_msg` invalid content the error will be ignored). -### Extending the wallet +3) It would be good to do `end_parse()` for messages and contract data. But this is not done within optimisations. -**A. Code optimization** +### Gasless flow -Backwards compatible code optimization **can be performed** with a single `set_code` action (`action_set_code#ad4de08e`) signed by the user. That is, hypothetical upgrade from v5R1 to v5R2 can be done in-place without forcing users to change wallet address. +1. When sending an USDt (or other Jetton) the user signs one message containing two outgoing USDt transfers: -If the optimized code requires changes to the data layout (e.g. reordering fields) the user can sign a request with two actions: `set_code` (in the standard action) and `set_data` (an extended action per this specification). Note that `set_data` action must make sure `seqno` is properly incremented after the upgrade as to prevent replays. Also, `set_data` must be performed right before the standard actions to not get overwritten by extension actions. + * USDt transfer to the recipient's address. -User agents **should not** make `set_code` and `set_data` actions available via general-purpose API to prevent misuse and mistakes. Instead, they should be used as a part of migration logic for a specific wallet code. + * Transfer of a small amount of USDt in favor of the Service. -**B. Substantial upgrades** +2. This signed message is sent offchain by HTTPS to the Service backend. The Service backend checks message and sends it to the TON blockchain paying Toncoins for network fees. -We **do not recommend** performing substantial wallet upgrades in-place using `set_code`/`set_data` actions. Instead, user agents should have support for multiple accounts and easy switching between them. +### Gasless known issues -In-place migration requires maintaining backwards compatibility for all wallet features, which in turn could lead to increase in code size and higher gas and rent costs. +1) By requesting a gasless service, a user can have time to increase the seqno on his own, or via another service. -**C. Delegation/Capabilities schemes** + In this case, the gasless service will incur gas costs. -We recommend trying out new wallet capabilities via the extensions scheme instead of upgrading the wallet code. + However, this is a non-scalable scenario, as it requires the user to incur gas costs as well. -Wallet V5 supports scalable extensions that permit delegating access to the wallet to other contracts. + A blacklist on the service backend side solves the problem. -From the perspective of the wallet, every extension can perform the same actions as the user. Therefore limits and capabilities can be embedded in such an extension with a custom storage scheme. +2) The user can request a gasless service and by means of a specialised extension have time to withdraw the entire balance of Jettons without change seqno. -Extensions can co-exist simultaneously, so experimental capabilities can be deployed and tested independently from each other. + In this case, the Jetton transfer message from the service will encounter a balance shortage and the Toncoins attached to message will return to the user's wallet. -### Can the wallet outsource payment for gas fees? + However, this is a non-scalable scenario, as it requires the user to incur gas costs as well. -Yes! You can deliver signed messages via an internal message from a 3rd party wallet. Also, the message is handled exactly like an external one: after the basic checks the wallet takes care of the fees itself, so that 3rd party does not need to overpay for users who actually do have TONs. + A blacklist on the service backend side solves the problem. -### Does the wallet grow with number of plugins? +### Suggested extensions -Not really. Wallet only accumulates code extensions. So if even you have 100500 plugins based on just three types of contracts, your wallet would only store extra ≈96 bytes of data. +1) Decentralised subscriptions. The extension can withdraw a given number of Toncoins or Jettons once in a given period. -### Can plugins implement subscriptions that collect tokens? +2) 2FA: Multisig extension is added, extension prohibits wallet signature; -Yes. Plugins can emit arbitrary messages, including token transfers, on behalf of the wallet. +3) Key recovery: 2FA, but in multisig extension there is an option to change the control keys. Possible cooldown period when the other party can cancel the key change. -### How can a plugin collect funds? +4) Key compromise: An extension with a new key is added, extension prohibits wallet signature; -Plugin needs to send a request with a message to its own address. - -### How can a plugin self-destruct? - -Plugin does not need to remove its extension code from the wallet — they can simply self-destroy by sending all TONs to the wallet with sendmode 128. - -### How can I deploy a plugin, install its code and send it a message in one go? - -You need two requests in your message body: first one installs the extension code, the second one sends raw message to your plugin address. - -### How does the wallet know which plugins it has installed? - -Extension contracts are designed in such way that each one checks that it was deployed by its proper wallet. For an example of this initialization pattern see how NFT items or jetton wallets do that. - -Your wallet can only trust the extension code that was audited to perform such authenticated initialization. Users are not supposed to install arbitrary extensions unknown to the user agent. - - -## Wallet ID - -Wallet ID disambiguates requests signed with the same public key to different wallet versions (V3/V4/V5) or wallets deployed on different chains. - -For Wallet V5 we suggest using the following wallet ID: - -``` -mainnet: 20230823 + workchain -testnet: 30230823 + workchain -``` - -## TL-B definitions - -Action types: - -```tlb -// Standard actions from block.tlb: -out_list_empty$_ = OutList 0; -out_list$_ {n:#} prev:^(OutList n) action:OutAction - = OutList (n + 1); -action_send_msg#0ec3c86d mode:(## 8) - out_msg:^(MessageRelaxed Any) = OutAction; -action_set_code#ad4de08e new_code:^Cell = OutAction; -action_reserve_currency#36e6b809 mode:(## 8) - currency:CurrencyCollection = OutAction; -libref_hash$0 lib_hash:bits256 = LibRef; -libref_ref$1 library:^Cell = LibRef; -action_change_library#26fa1dd4 mode:(## 7) { mode <= 2 } - libref:LibRef = OutAction; - -// Extended actions in W5: -action_list_basic$0 {n:#} actions:^(OutList n) = ActionList n 0; -action_list_extended$1 {m:#} {n:#} prev:^(ActionList n m) action:ExtendedAction = ActionList n (m+1); - -action_set_data#1ff8ea0b data:^Cell = ExtendedAction; -action_add_ext#1c40db9f code_hash:uint256 = ExtendedAction; -action_delete_ext#5eaef4a4 code_hash:uint256 = ExtendedAction; -``` - -Authentication modes: - -```tlb -signed_request$_ - signature: bits512 // 512 - subwallet_id: uint32 // 512+32 - valid_until: uint32 // 512+32+32 - msg_seqno: uint32 // 512+32+32+32 = 608 - inner: InnerRequest = SignedRequest; - -internal_signed#7369676E signed:SignedRequest = InternalMsgBody; -internal_extension#6578746E code:^Cell data:^Cell inner:InnerRequest = InternalMsgBody; -external_signed#7369676E signed:SignedRequest = ExternalMsgBody; - -actions$_ {m:#} {n:#} actions:(ActionList n m) = InnerRequest; -``` - - -## Source code - -```func -#pragma version =0.2.0; - -;; Extensible wallet contract v5 - -(slice, int) dict_get?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; -(cell, int) dict_add_builder?(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTADDB"; -(cell, int) dict_delete?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; -() set_actions(cell action_list) impure asm "c5 POP"; - -;; Verifies signed request, prevents replays and proceeds with `dispatch_request`. -() process_signed_request(slice body, int stored_seqno, int stored_subwallet, int public_key, cell extensions) impure { - var signature = body~load_bits(512); - var cs = body; - var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); - - throw_if(36, valid_until <= now()); - throw_unless(33, msg_seqno == stored_seqno); - throw_unless(34, subwallet_id == stored_subwallet); - throw_unless(35, check_signature(slice_hash(body), signature, public_key)); - - accept_message(); - - ;; Store and commit the seqno increment to prevent replays even if the requests fail. - stored_seqno = stored_seqno + 1; - set_data(begin_cell() - .store_uint(stored_seqno, 32) - .store_uint(stored_subwallet, 32) - .store_uint(public_key, 256) - .store_dict(extensions) - .end_cell()); - - commit(); - - dispatch_request(cs, stored_seqno, stored_subwallet, public_key, extensions); -} - - -;; Dispatches already authenticated request based on a 2-bit opcode: -;; - emit message -;; - install extension -;; - remove extension -;; - process more requests recursively -() dispatch_request(slice cs, int stored_seqno, int stored_subwallet, int public_key, cell extensions) impure { - - ;; Recurse into extended actions until we reach standard actions - while (cs~load_uint(1)) { - int op = cs~load_uint(4); - - ;; Raw set_data - if (op == 0x1ff8ea0b) { - set_data(cs~load_ref()); - } - - ;; Add/remove extensions - if (op == 0x1c40db9f || op == 0x5eaef4a4) { - int code_hash = cs~load_uint(256); - ;; Add extension - if (op == 0x1c40db9f) { - (extensions, int success?) = extensions.dict_add_builder?(256, code_hash, begin_cell()); - throw_unless(39, success?); - } - ;; Remove extension - if (op == 0x5eaef4a4) { - (extensions, int success?) = extensions.dict_delete?(256, code_hash); - throw_unless(39, success?); - } - - set_data(begin_cell() - .store_uint(stored_seqno, 32) - .store_uint(stored_subwallet, 32) - .store_uint(public_key, 256) - .store_dict(extensions) - .end_cell()); - } - - ;; Other actions are no-op - ;; FIXME: is it costlier to check for unsupported actions and throw? - - cs = cs~load_ref().begin_parse() - } - ;; At this point we are `action_list_basic$0 {n:#} actions:^(OutList n) = ActionList n 0;` - ;; Simply set the C5 register with all pre-computed actions: - set_actions(cs~load_ref()); - return (); -} - -() recv_external(slice body) impure { - var ds = get_data().begin_parse(); - var (stored_seqno, stored_subwallet, public_key, extensions) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); - ds.end_parse(); - int auth_kind = body~load_uint(32); - if (auth_kind == 0x7369676E) { ;; "sign" - process_signed_request(body, stored_seqno, stored_subwallet, public_key, extensions); - } else { - ;; FIXME: probably need to throw here? - return (); - } -} - - -() recv_internal(int msg_value, cell full_msg, slice body) impure { - var full_msg_slice = full_msg.begin_parse(); - var flags = full_msg_slice~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool - if (flags & 1) { - ;; ignore all bounced messages - return (); - } - if (body.slice_bits() < 32) { - ;; ignore simple transfers - return (); - } - int auth_kind = body~load_uint(32); - - ;; We accept two kinds of authenticated messages: - ;; - 0x6578746E "extn" authenticated by extension - ;; - 0x7369676E "sign" authenticated by signature - if (auth_kind != 0x6578746E) & (auth_kind != 0x7369676E) { ;; "extn" & "sign" - ;; ignore all unauthenticated messages - return (); - } - - var ds = get_data().begin_parse(); - var (stored_seqno, stored_subwallet, public_key, extensions) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); - ds.end_parse(); - - if (auth_kind == 0x6578746E) { ;; "extn" - ;; 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). - - ;; FIXME: - ;; In this revision we send full code+data refs instead of their hashes. - ;; In the future this should be optimized either with pruned cells or - ;; with an explicit pair of 256-bit strings in the body. - ;; Also consider subden's hack: transfer code+data in the stateinit for this wallet. - (cell code, cell data) = (body~load_ref(), body~load_ref()); - var (_, success?) = extensions.dict_get?(256, cell_hash(code)); - if ~(success?) { - return (); ;; did not find extension - } - ;; Check that the sender indeed has the declared code in its contract. - (_, int sender_addr_hash) = parse_std_addr(full_msg_slice~load_msg_addr()); - cell state_init = begin_cell().store_uint(0, 2).store_dict(code).store_dict(data).store_uint(0, 1).end_cell(); - if !(sender_addr_hash == cell_hash(state_init)) { - return (); ;; sender is not our extension - } - - ;; The remainder of the body (up to 2 refs) can now be dispatched - dispatch_request(body, stored_seqno, stored_subwallet, public_key, extensions); - } - if (auth_kind == 0x7369676E) { ;; "sign" - ;; Process the rest of the slice just like the signed request. - process_signed_request(body, stored_seqno, stored_subwallet, public_key, extensions); - } -} - - -;; Get methods - -int seqno() method_id { - return get_data().begin_parse().preload_uint(32); -} - -int get_subwallet_id() method_id { - return get_data().begin_parse().skip_bits(32).preload_uint(32); -} - -int get_public_key() method_id { - var cs = get_data().begin_parse().skip_bits(64); - return cs.preload_uint(256); -} - -int has_extension(int code_hash) method_id { - var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); - var extensions = ds~load_dict(); - var (_, success?) = extensions.dict_get?(256, begin_cell().store_uint(code_hash, 256).end_cell().begin_parse()); - return success?; -} - -tuple get_extensions_list() method_id { - var list = null(); - var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); - var extensions = ds~load_dict(); - do { - var (slice, _, f) = extensions~dict::delete_get_min(256); - if (f) { - list = cons(slice~load_uint(256), list); - } - } until (~ f); - return list; -} -``` diff --git a/Specification.md b/Specification.md new file mode 100644 index 00000000..9896b0b6 --- /dev/null +++ b/Specification.md @@ -0,0 +1,118 @@ +# Extensible Wallet V5 + +Author: Oleg Andreev + +This is an extensible wallet specification aimed at replacing V4 and allowing arbitrary extensions. + +* [Features](#features) +* [Overview](#overview) +* [Discussion](#discussion) +* [TL-B definitions](#tl-b-definitions) +* [Source code](#source-code) + + +## Credits + +Thanks to [Andrew Gutarev](https://github.com/pyAndr3w) for the idea to set c5 register to a [list of pre-composed actions](https://github.com/pyAndr3w/ton-preprocessed-wallet-v2). + +Thanks to [@subden](https://t.me/subden), [@botpult](https://t.me/botpult) and [@tvorogme](https://t.me/tvorogme) for ideas and discussion. + +Thanks to [Skydev](https://github.com/Skydev0h) for optimization and preparing the second revision of the contract. + + +## Features + +* 25% smaller computation fees. +* Arbitrary amount of outgoing messages is supported via action list. +* Wallet code can be extended by anyone in a decentralized and conflict-free way: multiple feature extensions can co-exist. +* Extensions can perform the same operations as the signer: emit arbitrary messages on behalf of the owner, add and remove extensions. +* Signed requests can be delivered via internal message to allow 3rd party pay for gas. +* For consistency and ease of indexing, external messages also receive a 32-bit opcode. +* To lay foundation for support of scenarios like 2FA or access recovery it is possible to disable signature authentication by extension. + +## Overview + +Wallet V5 supports **2 authentication modes** and **3 operations types**. + +Authentication: +* by signature +* by extension + +Operations: +* standard "send message" action (up to 255 messages at once), +* enable/disable signature authentication (can be invoked only by extension), +* install/remove extension. + +Signed messages can be delivered both by external and internal messages. + +All operations are available to all authentication modes. + +## Discussion + +### What is the job of the wallet? + +The job of the wallet is to send messages to other apps in the TON network on behalf of a single user identified by a single public key. +User may delegate this job to other apps via extensions. + +### The wallet is not for: + +* multi-user operation: you should use a multisig or DAO solution instead. +* routing of incoming payments and messages: use a specialized contract instead. +* imposing limits on access to certain assets: put account restriction inside a jetton, or use a lockup contract instead. + +### Extending the wallet + +The best way to extend functionality of the wallet is to use the extensions mechanism that permit delegating access to the wallet to other contracts. + +From the perspective of the wallet, every extension can perform the same actions as the owner of a private key. Therefore limits and capabilities can be embedded in such an extension with a custom storage scheme. + +Extensions can co-exist simultaneously, so experimental capabilities can be deployed and tested independently from each other. + +### Can the wallet outsource payment for gas fees? + +Yes! You can deliver signed messages via an internal message from a 3rd party wallet. Also, the message is handled exactly like an external one: after the basic checks the wallet takes care of the fees itself, so that 3rd party does not need to overpay for users who actually do have TONs. + +### Can plugins implement subscriptions that collect tokens? + +Yes. Plugins can emit arbitrary messages, including token transfers, on behalf of the wallet. + +### How can a plugin collect funds? + +Plugin needs to send a request with a message to its own address. + +### How can a plugin self-destruct? + +Plugin can self-destroy by sending all TONs to the wallet with sendmode 128 and adding one more action that removes itself from the list. + +### How can I deploy a plugin, install its code and send it a message in one go? + +You need to put two requests in your message body: +1. add the extension address, +2. send a message with stateinit to that address. + +### Does the wallet grow with number of plugins? + +Yes. We have considered constant-size schemes where the wallet only stores trusted extension code. However, extension authentication becomes combursome and expensive: plugin needs to transmit additional data and each request needs to recompute plugin’s address. We estimate that for the reasonably sized wallets (less than 100 plugins) authentication via the dictionary lookup would not exceed costs of indirect address authentication. + +### Why it can be useful to disallow authentication with signature? + +Ability to disallow authentication with signature enables two related use-cases: + +1. Two-factor authentication schemes: where control over wallet is fully delegated to an extension that checks two signatures: the user’s one and the signature from the auth service. Naturally, if the signature authentication in the wallet remains allowed, the second factor check is bypassed. + +2. Account recovery: delegating full control to another wallet in case of key compromise or loss. Wallet may contain larger amount of assets and its address could be tied to long-term contracts, therefore delegation to another controlling account is preferred to simply transferring the assets. + +### What is library on masterchain? + +Library is a special code storage mechanism that allows to reduce storage cost for a new Wallet V5 contract instance. Wallet V5 contract code is stored into a masterchain library. +When wallet contract is being deployed, original code hash is being used as the contract code. +Library contract itself data and code are empty cells. That leads to the inability to change the library code, delete the contract, or withdraw funds from it. +Therefore, any Wallet V5 user can top up the library contract balance if they are afraid that the library code of their wallet will be frozen. + +## TL-B definitions + +See `types.tlb`. + +## Source code + +See [contracts/wallet_v5.fc](contracts/wallet_v5.fc). diff --git a/WalletV5.md b/WalletV5.md deleted file mode 100644 index 54ad7546..00000000 --- a/WalletV5.md +++ /dev/null @@ -1,26 +0,0 @@ -# WalletV5 - -## Project structure - -- `contracts` - source code of all the smart contracts of the project and their dependencies. -- `wrappers` - wrapper classes (implementing `Contract` from ton-core) for the contracts, including any [de]serialization primitives and compilation functions. -- `tests` - tests for the contracts. -- `scripts` - scripts used by the project, mainly the deployment scripts. - -## How to use - -### Build - -`npx blueprint build` or `yarn blueprint build` - -### Test - -`npx blueprint test` or `yarn blueprint test` - -### Deploy or run another script - -`npx blueprint run` or `yarn blueprint run` - -### Add a new contract - -`npx blueprint create ContractName` or `yarn blueprint create ContractName` diff --git a/build/library-deployer.compiled.json b/build/library-deployer.compiled.json new file mode 100644 index 00000000..c68d6dbe --- /dev/null +++ b/build/library-deployer.compiled.json @@ -0,0 +1 @@ +{"hash":"7b886902938fda7f8ee72fe31ee250744bf82489579c0b8e502105a49cb72e2a","hashBase64":"e4hpApOP2n+O5y/jHuJQdEv4JIlXnAuOUCEFpJy3Lio=","hex":"b5ee9c72410106010030000114ff00f4a413f4bcf2c80b0102012002050202d1030400053c006000193b511cbec1b232483ec13b55200006f2f0014136d496"} \ 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 00000000..d276e8b3 --- /dev/null +++ b/build/wallet_v5.compiled.json @@ -0,0 +1 @@ +{"hash":"20834b7b72b112147e1b2fb457b84e74d1a30f04f737d4f62a668e9552d2b72f","hashBase64":"IINLe3KxEhR+Gy+0V7hOdNGjDwT3N9T2KmaOlVLSty8=","hex":"b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e"} \ No newline at end of file diff --git a/contracts/imports/stdlib.fc b/contracts/imports/stdlib.fc index fa048f67..241993d6 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/library-deployer.fc b/contracts/library-deployer.fc new file mode 100644 index 00000000..d490c62d --- /dev/null +++ b/contracts/library-deployer.fc @@ -0,0 +1,19 @@ +#pragma version =0.4.4; +#include "imports/stdlib.fc"; + +() set_lib_code(cell code, int mode) impure asm "SETLIBCODE"; + +() deploy_lib() impure { + set_lib_code(get_data(), 2); + cell empty = begin_cell().end_cell(); + set_code(empty); + set_data(empty); +} + +() recv_internal() impure { + deploy_lib(); +} + +() recv_external() impure { + deploy_lib(); +} diff --git a/contracts/wallet_v5.fc b/contracts/wallet_v5.fc new file mode 100644 index 00000000..83b5199c --- /dev/null +++ b/contracts/wallet_v5.fc @@ -0,0 +1,301 @@ +#pragma version =0.4.4; + +#include "imports/stdlib.fc"; + +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::valid_until = 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; + +(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"; + +;;; returns the number of trailing zeroes in slice s. +int count_trailing_zeroes(slice s) asm "SDCNTTRAIL0"; + +;;; 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"; + +;; `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"; + +;;; 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"; + +;;; 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"; + +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(); + + int count = 0; + + 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(); + + throw_unless(error::invalid_c5, cs.slice_bits() == 8); ;; send_mode uint8 + throw_unless(error::invalid_c5, cs.slice_refs() == 2); ;; next-action-ref and MessageRelaxed ref + + ;; 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. + + ;; 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); + + return c5; +} + +() 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 (); + } + + ;; Loop extended 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_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()); + + throw_unless(error::extension_wrong_workchain, my_address_wc == address_wc); ;; the extension must be in the same workchain as the wallet. + + 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_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_slice_before_extensions) + .store_dict(extensions) + .end_cell()); + + } elseif (cs~check_and_remove_set_signature_allowed_prefix()) { ;; allow/disallow signature + 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(is_signature_allowed, size::bool) + .store_slice(data_tail) ;; seqno, wallet_id, public_key, extensions + .end_cell()); + } else { + throw(error::unsupported_action); + } + ifnot (cs.slice_refs()) { + return (); + } + cs = cs.preload_ref().begin_parse(); + } +} + +;; ------------------------------------------------------------------------------------------------ + +() process_signed_request(slice in_msg_body, int is_external) 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 (); + } + } + ;; In case the wallet application has initially, by mistake, deployed a contract with the wrong bit (signature is forbidden and extensions are empty) - we allow such a contract to work. + 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()); + + if (is_external) { + accept_message(); + } + + stored_seqno = stored_seqno + 1; + set_data(begin_cell() + .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()); + + if (is_external) { + ;; For external messages we commit seqno changes, so that even if an exception occurs further on, the reply-protection will still work. + commit(); + } + + process_actions(cs, is_external, false); +} + +() 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); +} + +;; ------------------------------------------------------------------------------------------------ + +() recv_internal(cell in_msg_full, slice in_msg_body) impure inline { + if (in_msg_body.slice_bits() < size::message_operation_prefix) { + return (); ;; just receive Toncoins + } + int op = in_msg_body.preload_uint(size::message_operation_prefix); + if ((op != prefix::extension_action) & (op != prefix::signed_internal)) { + return (); ;; just receive Toncoins + } + + ;; bounced messages has 0xffffffff prefix and skipped by op check + + 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 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()); + + 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). + (_, 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 + + process_actions(in_msg_body, false, true); + return (); + + } + + ;; Before signature checking we handle errors silently (return), after signature checking we throw exceptions. + + ;; 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); +} + +;; ------------------------------------------------------------------------------------------------ +;; Get methods + +int is_signature_allowed() method_id { + return get_data().begin_parse() + .preload_int(size::bool); +} + +int seqno() method_id { + return get_data().begin_parse() + .skip_bits(size::bool) + .preload_uint(size::seqno); +} + +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 { + 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 address hashes. Workchains of extensions are same with wallet smart contract workchain. +cell get_extensions() method_id { + 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/contracts/wallet_v5_1.fc b/contracts/wallet_v5_1.fc deleted file mode 100644 index 19a7efc6..00000000 --- a/contracts/wallet_v5_1.fc +++ /dev/null @@ -1,181 +0,0 @@ -#pragma version =0.4.4; - -#include "imports/stdlib.fc"; - -;; Extensible wallet contract v5 - -(slice, int) dict_get?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; -(cell, int) dict_add_builder?(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTADDB"; -(cell, int) dict_delete?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; - - -;; Verifies signed request, prevents replays and proceeds with `dispatch_request`. -() process_signed_request(slice body, int stored_seqno, int stored_subwallet, int public_key, cell extensions) impure { - var signature = body~load_bits(512); - var cs = body; - var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); - - throw_if(36, valid_until <= now()); - throw_unless(33, msg_seqno == stored_seqno); - throw_unless(34, subwallet_id == stored_subwallet); - throw_unless(35, check_signature(slice_hash(body), signature, public_key)); - - accept_message(); - - ;; Store and commit the seqno increment to prevent replays even if the requests fail. - stored_seqno = stored_seqno + 1; - set_data(begin_cell() - .store_uint(stored_seqno, 32) - .store_uint(stored_subwallet, 32) - .store_uint(public_key, 256) - .store_dict(extensions) - .end_cell()); - - commit(); - - dispatch_request(cs, stored_seqno, stored_subwallet, public_key, extensions); -} - - -;; Dispatches already authenticated request based on a 2-bit opcode: -;; - emit message -;; - install extension -;; - remove extension -;; - process more requests recursively -() dispatch_request(slice cs, int stored_seqno, int stored_subwallet, int public_key, cell extensions) impure { - - ;; Read all the requests until we run out of bits. - while (cs.slice_refs()) { - int op = cs~load_uint(2); - - ;; Emit raw message with a given sendmode. - if (op == 0) { - var mode = cs~load_uint(8); - send_raw_message(cs~load_ref(), mode); - } - - ;; Add/remove extensions - if (op == 1 || op == 2) { - cell ext_code = cs~load_ref(); - int key = cell_hash(ext_code); - ;; Add extension - if (op == 1) { - (extensions, int success?) = extensions.dict_add_builder?(256, key, begin_cell()); - throw_unless(39, success?); - } - ;; Remove extension - if (op == 2) { - (extensions, int success?) = extensions.dict_delete?(256, key); - throw_unless(39, success?); - } - - set_data(begin_cell() - .store_uint(stored_seqno, 32) - .store_uint(stored_subwallet, 32) - .store_uint(public_key, 256) - .store_dict(extensions) - .end_cell()); - } - - ;; Tail-call into a ref to process more requests. - ;; This terminates iteration of the refs in this cell. - if (op == 3) { - cs = cs~load_ref().begin_parse() - } - } - return (); -} - -() recv_external(slice body) impure { - var ds = get_data().begin_parse(); - var (stored_seqno, stored_subwallet, public_key, extensions) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); - ds.end_parse(); - process_signed_request(body, stored_seqno, stored_subwallet, public_key, extensions); -} - - -() recv_internal(int msg_value, cell full_msg, slice body) impure { - var full_msg_slice = full_msg.begin_parse(); - var flags = full_msg_slice~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool - if (flags & 1) { - ;; ignore all bounced messages - return (); - } - if (body.slice_bits() < 32) { - ;; ignore simple transfers - return (); - } - int auth_kind = body~load_uint(32); - - ;; We accept two kinds of authenticated messages: - ;; - 0x6578746E "extn" authenticated by extension - ;; - 0x7369676E "sign" authenticated by signature - if (auth_kind != 0x6578746E) & (auth_kind != 0x7369676E) { ;; "extn" & "sign" - ;; ignore all unauthenticated messages - return (); - } - - var ds = get_data().begin_parse(); - var (stored_seqno, stored_subwallet, public_key, extensions) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); - ds.end_parse(); - - if (auth_kind == 0x6578746E) { ;; "extn" - ;; 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). - - (cell code, cell data) = (body~load_ref(), body~load_ref()); - var (_, success?) = extensions.dict_get?(256, cell_hash(code)); - if ~(success?) { - return (); ;; did not find extension - } - ;; Check that the sender indeed has the declared code in its contract. - (_, int sender_addr_hash) = parse_std_addr(full_msg_slice~load_msg_addr()); - cell state_init = begin_cell().store_uint(0, 2).store_dict(code).store_dict(data).store_uint(0, 1).end_cell(); - if !(sender_addr_hash == cell_hash(state_init)) { - return (); ;; sender is not our extension - } - - ;; The remainder of the body (up to 2 refs) can now be dispatched - dispatch_request(body, stored_seqno, stored_subwallet, public_key, extensions); - } - if (auth_kind == 0x7369676E) { ;; "sign" - ;; Process the rest of the slice just like the signed request. - process_signed_request(body, stored_seqno, stored_subwallet, public_key, extensions); - } -} - - -;; Get methods - -int seqno() method_id { - return get_data().begin_parse().preload_uint(32); -} - -int get_subwallet_id() method_id { - return get_data().begin_parse().skip_bits(32).preload_uint(32); -} - -int get_public_key() method_id { - var cs = get_data().begin_parse().skip_bits(64); - return cs.preload_uint(256); -} - -int has_extension(int code_hash) method_id { - var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); - var extensions = ds~load_dict(); - var (_, success?) = extensions.dict_get?(256, begin_cell().store_uint(code_hash, 256).end_cell().begin_parse()); - return success?; -} - -tuple get_extensions_list() method_id { - var list = null(); - var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); - var extensions = ds~load_dict(); - do { - var (slice, _, f) = extensions~dict::delete_get_min(256); - if (f) { - list = cons(slice~load_uint(256), list); - } - } until (~ f); - return list; -} \ No newline at end of file diff --git a/contracts/wallet_v5_2.fc b/contracts/wallet_v5_2.fc deleted file mode 100644 index 53d31191..00000000 --- a/contracts/wallet_v5_2.fc +++ /dev/null @@ -1,192 +0,0 @@ -#pragma version =0.4.4; - -#include "imports/stdlib.fc"; - -;; Extensible wallet contract v5 - -(slice, int) dict_get?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; -(cell, int) dict_add_builder?(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTADDB"; -(cell, int) dict_delete?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; -() set_actions(cell action_list) impure asm "c5 POP"; - -;; Verifies signed request, prevents replays and proceeds with `dispatch_request`. -() process_signed_request(slice body, int stored_seqno, int stored_subwallet, int public_key, cell extensions) impure { - var signature = body~load_bits(512); - var cs = body; - var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); - - throw_if(36, valid_until <= now()); - throw_unless(33, msg_seqno == stored_seqno); - throw_unless(34, subwallet_id == stored_subwallet); - throw_unless(35, check_signature(slice_hash(body), signature, public_key)); - - accept_message(); - - ;; Store and commit the seqno increment to prevent replays even if the requests fail. - stored_seqno = stored_seqno + 1; - set_data(begin_cell() - .store_uint(stored_seqno, 32) - .store_uint(stored_subwallet, 32) - .store_uint(public_key, 256) - .store_dict(extensions) - .end_cell()); - - commit(); - - dispatch_request(cs, stored_seqno, stored_subwallet, public_key, extensions); -} - - -;; Dispatches already authenticated request based on a 2-bit opcode: -;; - emit message -;; - install extension -;; - remove extension -;; - process more requests recursively -() dispatch_request(slice cs, int stored_seqno, int stored_subwallet, int public_key, cell extensions) impure { - - ;; Recurse into extended actions until we reach standard actions - while (cs~load_uint(1)) { - int op = cs~load_uint(4); - - ;; Raw set_data - if (op == 0x1ff8ea0b) { - set_data(cs~load_ref()); - } - - ;; Add/remove extensions - if (op == 0x1c40db9f || op == 0x5eaef4a4) { - int code_hash = cs~load_uint(256); - ;; Add extension - if (op == 0x1c40db9f) { - (extensions, int success?) = extensions.dict_add_builder?(256, code_hash, begin_cell()); - throw_unless(39, success?); - } - ;; Remove extension - if (op == 0x5eaef4a4) { - (extensions, int success?) = extensions.dict_delete?(256, code_hash); - throw_unless(39, success?); - } - - set_data(begin_cell() - .store_uint(stored_seqno, 32) - .store_uint(stored_subwallet, 32) - .store_uint(public_key, 256) - .store_dict(extensions) - .end_cell()); - } - - ;; Other actions are no-op - ;; FIXME: is it costlier to check for unsupported actions and throw? - - cs = cs~load_ref().begin_parse() - } - ;; At this point we are `action_list_basic$0 {n:#} actions:^(OutList n) = ActionList n 0;` - ;; Simply set the C5 register with all pre-computed actions: - set_actions(cs~load_ref()); - return (); -} - -() recv_external(slice body) impure { - var ds = get_data().begin_parse(); - var (stored_seqno, stored_subwallet, public_key, extensions) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); - ds.end_parse(); - int auth_kind = body~load_uint(32); - if (auth_kind == 0x7369676E) { ;; "sign" - process_signed_request(body, stored_seqno, stored_subwallet, public_key, extensions); - } else { - ;; FIXME: probably need to throw here? - return (); - } -} - - -() recv_internal(int msg_value, cell full_msg, slice body) impure { - var full_msg_slice = full_msg.begin_parse(); - var flags = full_msg_slice~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool - if (flags & 1) { - ;; ignore all bounced messages - return (); - } - if (body.slice_bits() < 32) { - ;; ignore simple transfers - return (); - } - int auth_kind = body~load_uint(32); - - ;; We accept two kinds of authenticated messages: - ;; - 0x6578746E "extn" authenticated by extension - ;; - 0x7369676E "sign" authenticated by signature - if (auth_kind != 0x6578746E) & (auth_kind != 0x7369676E) { ;; "extn" & "sign" - ;; ignore all unauthenticated messages - return (); - } - - var ds = get_data().begin_parse(); - var (stored_seqno, stored_subwallet, public_key, extensions) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); - ds.end_parse(); - - if (auth_kind == 0x6578746E) { ;; "extn" - ;; 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). - - ;; FIXME: - ;; In this revision we send full code+data refs instead of their hashes. - ;; In the future this should be optimized either with pruned cells or - ;; with an explicit pair of 256-bit strings in the body. - ;; Also consider subden's hack: transfer code+data in the stateinit for this wallet. - (cell code, cell data) = (body~load_ref(), body~load_ref()); - var (_, success?) = extensions.dict_get?(256, cell_hash(code)); - if ~(success?) { - return (); ;; did not find extension - } - ;; Check that the sender indeed has the declared code in its contract. - (_, int sender_addr_hash) = parse_std_addr(full_msg_slice~load_msg_addr()); - cell state_init = begin_cell().store_uint(0, 2).store_dict(code).store_dict(data).store_uint(0, 1).end_cell(); - if !(sender_addr_hash == cell_hash(state_init)) { - return (); ;; sender is not our extension - } - - ;; The remainder of the body (up to 2 refs) can now be dispatched - dispatch_request(body, stored_seqno, stored_subwallet, public_key, extensions); - } - if (auth_kind == 0x7369676E) { ;; "sign" - ;; Process the rest of the slice just like the signed request. - process_signed_request(body, stored_seqno, stored_subwallet, public_key, extensions); - } -} - - -;; Get methods - -int seqno() method_id { - return get_data().begin_parse().preload_uint(32); -} - -int get_subwallet_id() method_id { - return get_data().begin_parse().skip_bits(32).preload_uint(32); -} - -int get_public_key() method_id { - var cs = get_data().begin_parse().skip_bits(64); - return cs.preload_uint(256); -} - -int has_extension(int code_hash) method_id { - var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); - var extensions = ds~load_dict(); - var (_, success?) = extensions.dict_get?(256, begin_cell().store_uint(code_hash, 256).end_cell().begin_parse()); - return success?; -} - -tuple get_extensions_list() method_id { - var list = null(); - var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); - var extensions = ds~load_dict(); - do { - var (slice, _, f) = extensions~dict::delete_get_min(256); - if (f) { - list = cons(slice~load_uint(256), list); - } - } until (~ f); - return list; -} diff --git a/fift/Asm.fif b/fift/Asm.fif new file mode 100644 index 00000000..70503554 --- /dev/null +++ b/fift/Asm.fif @@ -0,0 +1,1457 @@ +library TVM_Asm +// simple TVM Assembler +namespace Asm +Asm definitions +"0.4.4" constant asm-fif-version + +variable @atend +variable @was-split +false @was-split ! +{ "not in asm context" abort } @atend ! +{ `normal eq? not abort"must be terminated by }>" } : @normal? +{ context@ @atend @ 2 { @atend ! context! @normal? } does @atend ! } : @pushatend +{ @pushatend Asm +{ }> b> } : }>c +{ }>c s +{ @atend @ 2 { true @was-split ! @atend ! rot b> ref, swap @endblk } does @atend ! = -rot <= and } : 2x<= +{ 2 pick brembitrefs 1- 2x<= } : @havebitrefs +{ @havebits ' @| ifnot } : @ensurebits +{ @havebitrefs ' @| ifnot } : @ensurebitrefs +{ rot over @ensurebits -rot u, } : @simpleuop +{ tuck sbitrefs @ensurebitrefs swap s, } : @addop +{ tuck bbitrefs @ensurebitrefs swap b+ } : @addopb +' @addopb : @inline +{ 1 ' @addop does create } : @Defop +{ 1 { } : si() +// x mi ma -- ? +{ rot tuck >= -rot <= and } : @range +{ rot tuck < -rot > or } : @-range +{ @-range abort"Out of range" } : @rangechk +{ dup 0 < over 255 > or abort"Invalid stack register number" si() } : s() +{ si() constant } : @Sreg +-2 @Sreg s(-2) +-1 @Sreg s(-1) +0 @Sreg s0 +1 @Sreg s1 +2 @Sreg s2 +3 @Sreg s3 +4 @Sreg s4 +5 @Sreg s5 +6 @Sreg s6 +7 @Sreg s7 +8 @Sreg s8 +9 @Sreg s9 +10 @Sreg s10 +11 @Sreg s11 +12 @Sreg s12 +13 @Sreg s13 +14 @Sreg s14 +15 @Sreg s15 +{ dup 0 < over 7 > or abort"Invalid control register number" } : c() +{ c() constant } : @Creg +0 @Creg c0 +1 @Creg c1 +2 @Creg c2 +3 @Creg c3 +4 @Creg c4 +5 @Creg c5 +7 @Creg c7 +{ abort"not a stack register" 12 i@+ s> } : @bigsridx +{ @bigsridx dup 16 >= over 0< or abort"stack register s0..s15 expected" } : @sridx +{ rot @bigsridx tuck < -rot tuck > rot or abort"stack register out of range" } : @sridxrange +{ swap @bigsridx + dup 16 >= over 0< or abort"stack register out of range" } : @sridx+ +{ swap 0xcc <> over 7 > or over 6 = or abort"not a control register c0..c5 or c7" } : @cridx +{ = + { tuck 16 >= + { = and + { 15 and abort"integer too large" 8 + 2dup fits } until + > 2- 5 u, -rot i, + } cond + } cond + } cond + @addopb } dup : PUSHINT : INT +{ dup 256 = abort"use PUSHNAN instead of 256 PUSHPOW2" = or abort"invalid slice padding" + swap 1 1 u, 0 rot u, } : @scomplete +{ tuck sbitrefs swap 26 + swap @havebitrefs not + { PUSHREFSLICE } + { over sbitrefs 2dup 123 0 2x<= + { drop tuck 4 + 3 >> swap x{8B} s, over 4 u, 3 roll s, + -rot 3 << 4 + swap - @scomplete } + { 2dup 1 >= swap 248 <= and + { rot x{8C} s, swap 1- 2 u, over 7 + 3 >> tuck 5 u, 3 roll s, + -rot 3 << 1 + swap - @scomplete } + { rot x{8D} s, swap 3 u, over 2 + 3 >> tuck 7 u, 3 roll s, + -rot 3 << 6 + swap - @scomplete + } cond + } cond + } cond +} dup : PUSHSLICE : SLICE +// ( b' -- ? ) +{ bbitrefs or 0= } : @cont-empty? +{ bbits 7 and 0= } : @cont-aligned? +// ( b b' -- ? ) +{ bbitrefs over 7 and { 2drop drop false } { + swap 16 + swap @havebitrefs nip + } cond +} : @cont-fits? +// ( b b' -- ? ) +{ bbitrefs over 7 and { 2drop drop false } { + 32 1 pair+ @havebitrefs nip + } cond +} : @cont-ref-fit? +// ( b b' b'' -- ? ) +{ over @cont-aligned? over @cont-aligned? and not { 2drop drop false } { + bbitrefs rot bbitrefs pair+ swap 32 + swap @havebitrefs nip + } cond +} : @two-cont-fit? +{ 2dup @cont-fits? not + { b> PUSHREFCONT } + { swap over bbitrefs 2dup 120 0 2x<= + { drop swap x{9} s, swap 3 >> 4 u, swap b+ } + { rot x{8F_} s, swap 2 u, swap 3 >> 7 u, swap b+ } cond + } cond +} dup : PUSHCONT : CONT +{ }> PUSHCONT } : }>CONT +{ { @normal? PUSHCONT } @doafter<{ } : CONT:<{ + +// arithmetic operations +{ 2 { rot dup 8 fits + { nip = { rot drop -rot PUSHINT swap LSHIFT# } { + { drop PUSHINT } { + not pow2decomp swap -1 = { nip PUSHPOW2DEC } { + drop PUSHINT + } cond } cond } cond } cond } cond } cond +} dup : PUSHINTX : INTX + +// integer comparison +x{B8} @Defop SGN +x{B9} @Defop LESS +x{BA} @Defop EQUAL +x{BB} @Defop LEQ +x{BC} @Defop GREATER +x{BD} @Defop NEQ +x{BE} @Defop GEQ +x{BF} @Defop CMP +x{C0} x{BA} @Defop(8i,alt) EQINT +x{C000} @Defop ISZERO +x{C1} x{B9} @Defop(8i,alt) LESSINT +{ 1+ LESSINT } : LEQINT +x{C100} @Defop ISNEG +x{C101} @Defop ISNPOS +x{C2} x{BC} @Defop(8i,alt) GTINT +{ 1- GTINT } : GEQINT +x{C200} @Defop ISPOS +x{C2FF} @Defop ISNNEG +x{C3} x{BD} @Defop(8i,alt) NEQINT +x{C300} @Defop ISNZERO +x{C4} @Defop ISNAN +x{C5} @Defop CHKNAN + +// other comparison +x{C700} @Defop SEMPTY +x{C701} @Defop SDEMPTY +x{C702} @Defop SREMPTY +x{C703} @Defop SDFIRST +x{C704} @Defop SDLEXCMP +x{C705} @Defop SDEQ +x{C708} @Defop SDPFX +x{C709} @Defop SDPFXREV +x{C70A} @Defop SDPPFX +x{C70B} @Defop SDPPFXREV +x{C70C} @Defop SDSFX +x{C70D} @Defop SDSFXREV +x{C70E} @Defop SDPSFX +x{C70F} @Defop SDPSFXREV +x{C710} @Defop SDCNTLEAD0 +x{C711} @Defop SDCNTLEAD1 +x{C712} @Defop SDCNTTRAIL0 +x{C713} @Defop SDCNTTRAIL1 + +// cell serialization (Builder manipulation primitives) +x{C8} @Defop NEWC +x{C9} @Defop ENDC +x{CA} @Defop(8u+1) STI +x{CB} @Defop(8u+1) STU +x{CC} @Defop STREF +x{CD} dup @Defop STBREFR @Defop ENDCST +x{CE} @Defop STSLICE +x{CF00} @Defop STIX +x{CF01} @Defop STUX +x{CF02} @Defop STIXR +x{CF03} @Defop STUXR +x{CF04} @Defop STIXQ +x{CF05} @Defop STUXQ +x{CF06} @Defop STIXRQ +x{CF07} @Defop STUXRQ +x{CF08} @Defop(8u+1) STI_l +x{CF09} @Defop(8u+1) STU_l +x{CF0A} @Defop(8u+1) STIR +x{CF0B} @Defop(8u+1) STUR +x{CF0C} @Defop(8u+1) STIQ +x{CF0D} @Defop(8u+1) STUQ +x{CF0E} @Defop(8u+1) STIRQ +x{CF0F} @Defop(8u+1) STURQ +x{CF10} @Defop STREF_l +x{CF11} @Defop STBREF +x{CF12} @Defop STSLICE_l +x{CF13} @Defop STB +x{CF14} @Defop STREFR +x{CF15} @Defop STBREFR_l +x{CF16} @Defop STSLICER +x{CF17} dup @Defop STBR @Defop BCONCAT +x{CF18} @Defop STREFQ +x{CF19} @Defop STBREFQ +x{CF1A} @Defop STSLICEQ +x{CF1B} @Defop STBQ +x{CF1C} @Defop STREFRQ +x{CF1D} @Defop STBREFRQ +x{CF1E} @Defop STSLICERQ +x{CF1F} dup @Defop STBRQ @Defop BCONCATQ +x{CF20} @Defop(ref) STREFCONST +{ > tuck 3 u, 3 roll s, + -rot 3 << 2 + swap - @scomplete } + { 2drop swap PUSHSLICE STSLICER } cond + } cond +} : STSLICECONST +x{CF81} @Defop STZERO +x{CF83} @Defop STONE + +// cell deserialization (CellSlice primitives) +x{D0} @Defop CTOS +x{D1} @Defop ENDS +x{D2} @Defop(8u+1) LDI +x{D3} @Defop(8u+1) LDU +x{D4} @Defop LDREF +x{D5} @Defop LDREFRTOS +x{D6} @Defop(8u+1) LDSLICE +x{D700} @Defop LDIX +x{D701} @Defop LDUX +x{D702} @Defop PLDIX +x{D703} @Defop PLDUX +x{D704} @Defop LDIXQ +x{D705} @Defop LDUXQ +x{D706} @Defop PLDIXQ +x{D707} @Defop PLDUXQ +x{D708} @Defop(8u+1) LDI_l +x{D709} @Defop(8u+1) LDU_l +x{D70A} @Defop(8u+1) PLDI +x{D70B} @Defop(8u+1) PLDU +x{D70C} @Defop(8u+1) LDIQ +x{D70D} @Defop(8u+1) LDUQ +x{D70E} @Defop(8u+1) PLDIQ +x{D70F} @Defop(8u+1) PLDUQ +{ dup 31 and abort"argument must be a multiple of 32" 5 >> 1- + > swap x{D72A_} s, over 7 u, 3 roll s, + -rot 3 << 3 + swap - @scomplete } : SDBEGINS:imm +{ tuck sbitrefs abort"no references allowed in slice" dup 26 <= + { drop > swap x{D72E_} s, over 7 u, 3 roll s, + -rot 3 << 3 + swap - @scomplete } : SDBEGINSQ:imm +{ tuck sbitrefs abort"no references allowed in slice" dup 26 <= + { drop rot 2 } { + swap @| swap 2dup @cont-fits? { rot 1 } { + b> rot 2 + } cond } cond } cond } cond + [] execute +} : @run-cont-op +{ triple 1 ' @run-cont-op does create } : @def-cont-op +{ DROP } { PUSHCONT IF } { IFREF } @def-cont-op IF-cont +{ IFRET } { PUSHCONT IFJMP } { IFJMPREF } @def-cont-op IFJMP-cont +{ DROP } { PUSHCONT IFNOT } { IFNOTREF } @def-cont-op IFNOT-cont +{ IFNOTRET } { PUSHCONT IFNOTJMP } { IFNOTJMPREF } @def-cont-op IFNOTJMP-cont +{ dup 2over rot } : 3dup + +recursive IFELSE-cont2 { + dup @cont-empty? { drop IF-cont } { + over @cont-empty? { nip IFNOT-cont } { + 3dup @two-cont-fit? { -rot PUSHCONT swap PUSHCONT IFELSE } { + 3dup nip @cont-ref-fit? { rot swap PUSHCONT swap b> IFREFELSE } { + 3dup drop @cont-ref-fit? { -rot PUSHCONT swap b> IFELSEREF } { + rot 32 2 @havebitrefs { rot b> rot b> IFREFELSEREF } { + @| -rot IFELSE-cont2 + } cond } cond } cond } cond } cond } cond +} swap ! + +{ }> IF-cont } : }>IF +{ }> IFNOT-cont } : }>IFNOT +{ }> IFJMP-cont } : }>IFJMP +{ }> IFNOTJMP-cont } : }>IFNOTJMP +{ { @normal? IFJMP-cont } @doafter<{ } : IFJMP:<{ +{ { @normal? IFNOTJMP-cont } @doafter<{ } : IFNOTJMP:<{ +{ `else @endblk } : }>ELSE<{ +{ `else: @endblk } : }>ELSE: +{ 1 { swap @normal? swap IFELSE-cont2 } does @doafter<{ } : @doifelse +{ 1 { swap @normal? IFELSE-cont2 } does @doafter<{ } : @doifnotelse +{ + { dup `else eq? + { drop @doifelse } + { dup `else: eq? + { drop IFJMP-cont } + { @normal? IF-cont + } cond + } cond + } @doafter<{ +} : IF:<{ +{ + { dup `else eq? + { drop @doifnotelse } + { dup `else: eq? + { drop IFNOTJMP-cont } + { @normal? IFNOT-cont + } cond + } cond + } @doafter<{ +} : IFNOT:<{ + +x{E304} @Defop CONDSEL +x{E305} @Defop CONDSELCHK +x{E308} @Defop IFRETALT +x{E309} @Defop IFNOTRETALT +{ DO<{ +{ `do: @endblk } : }>DO: +{ }> PUSHCONT REPEAT } : }>REPEAT +{ { @normal? PUSHCONT REPEAT } @doafter<{ } : REPEAT:<{ +{ }> PUSHCONT UNTIL } : }>UNTIL +{ { @normal? PUSHCONT UNTIL } @doafter<{ } : UNTIL:<{ +{ PUSHCONT { @normal? PUSHCONT WHILE } @doafter<{ } : @dowhile +{ + { dup `do eq? + { drop @dowhile } + { `do: eq? not abort"`}>DO<{` expected" PUSHCONT WHILEEND + } cond + } @doafter<{ +} : WHILE:<{ +{ }> PUSHCONT AGAIN } : }>AGAIN +{ { @normal? PUSHCONT AGAIN } @doafter<{ } : AGAIN:<{ + +x{E314} @Defop REPEATBRK +x{E315} @Defop REPEATENDBRK +x{E316} @Defop UNTILBRK +x{E317} dup @Defop UNTILENDBRK @Defop UNTILBRK: +x{E318} @Defop WHILEBRK +x{E319} @Defop WHILEENDBRK +x{E31A} @Defop AGAINBRK +x{E31B} dup @Defop AGAINENDBRK @Defop AGAINBRK: + +{ }> PUSHCONT REPEATBRK } : }>REPEATBRK +{ { @normal? PUSHCONT REPEATBRK } @doafter<{ } : REPEATBRK:<{ +{ }> PUSHCONT UNTILBRK } : }>UNTILBRK +{ { @normal? PUSHCONT UNTILBRK } @doafter<{ } : UNTILBRK:<{ +{ PUSHCONT { @normal? PUSHCONT WHILEBRK } @doafter<{ } : @dowhile +{ + { dup `do eq? + { drop @dowhile } + { `do: eq? not abort"`}>DO<{` expected" PUSHCONT WHILEENDBRK + } cond + } @doafter<{ +} : WHILEBRK:<{ +{ }> PUSHCONT AGAINBRK } : }>AGAINBRK +{ { @normal? PUSHCONT AGAINBRK } @doafter<{ } : AGAINBRK:<{ + + +// +// continuation stack manipulation and continuation creation +// +{ PUSHCONT ATEXIT } : }>ATEXIT +{ { @normal? PUSHCONT ATEXIT } @doafter<{ } : ATEXIT:<{ +x{EDF4} @Defop ATEXITALT +{ }> PUSHCONT ATEXITALT } : }>ATEXITALT +{ { @normal? PUSHCONT ATEXITALT } @doafter<{ } : ATEXITALT:<{ +x{EDF5} @Defop SETEXITALT +{ }> PUSHCONT SETEXITALT } : }>SETEXITALT +{ { @normal? PUSHCONT SETEXITALT } @doafter<{ } : SETEXITALT:<{ +x{EDF6} @Defop THENRET +x{EDF7} @Defop THENRETALT +x{EDF8} @Defop INVERT +x{EDF9} @Defop BOOLEVAL +x{EDFA} @Defop SAMEALT +x{EDFB} @Defop SAMEALTSAVE +// x{EE} is BLESSARGS +// +// dictionary subroutine call/jump primitives +{ c3 PUSH EXECUTE } : CALLVAR +{ c3 PUSH JMPX } : JMPVAR +{ c3 PUSH } : PREPAREVAR +{ dup 14 ufits { + dup 8 ufits { + CATCH<{ +{ PUSHCONT { @normal? PUSHCONT TRY } @doafter<{ } : @trycatch +{ + { `catch eq? not abort"`}>CATCH<{` expected" @trycatch + } @doafter<{ +} : TRY:<{ +// +// dictionary manipulation +' NULL : NEWDICT +' ISNULL : DICTEMPTY +' STSLICE : STDICTS +x{F400} dup @Defop STDICT @Defop STOPTREF +x{F401} dup @Defop SKIPDICT @Defop SKIPOPTREF +x{F402} @Defop LDDICTS +x{F403} @Defop PLDDICTS +x{F404} dup @Defop LDDICT @Defop LDOPTREF +x{F405} dup @Defop PLDDICT @Defop PLDOPTREF +x{F406} @Defop LDDICTQ +x{F407} @Defop PLDDICTQ + +x{F40A} @Defop DICTGET +x{F40B} @Defop DICTGETREF +x{F40C} @Defop DICTIGET +x{F40D} @Defop DICTIGETREF +x{F40E} @Defop DICTUGET +x{F40F} @Defop DICTUGETREF + +x{F412} @Defop DICTSET +x{F413} @Defop DICTSETREF +x{F414} @Defop DICTISET +x{F415} @Defop DICTISETREF +x{F416} @Defop DICTUSET +x{F417} @Defop DICTUSETREF +x{F41A} @Defop DICTSETGET +x{F41B} @Defop DICTSETGETREF +x{F41C} @Defop DICTISETGET +x{F41D} @Defop DICTISETGETREF +x{F41E} @Defop DICTUSETGET +x{F41F} @Defop DICTUSETGETREF + +x{F422} @Defop DICTREPLACE +x{F423} @Defop DICTREPLACEREF +x{F424} @Defop DICTIREPLACE +x{F425} @Defop DICTIREPLACEREF +x{F426} @Defop DICTUREPLACE +x{F427} @Defop DICTUREPLACEREF +x{F42A} @Defop DICTREPLACEGET +x{F42B} @Defop DICTREPLACEGETREF +x{F42C} @Defop DICTIREPLACEGET +x{F42D} @Defop DICTIREPLACEGETREF +x{F42E} @Defop DICTUREPLACEGET +x{F42F} @Defop DICTUREPLACEGETREF + +x{F432} @Defop DICTADD +x{F433} @Defop DICTADDREF +x{F434} @Defop DICTIADD +x{F435} @Defop DICTIADDREF +x{F436} @Defop DICTUADD +x{F437} @Defop DICTUADDREF +x{F43A} @Defop DICTADDGET +x{F43B} @Defop DICTADDGETREF +x{F43C} @Defop DICTIADDGET +x{F43D} @Defop DICTIADDGETREF +x{F43E} @Defop DICTUADDGET +x{F43F} @Defop DICTUADDGETREF + +x{F441} @Defop DICTSETB +x{F442} @Defop DICTISETB +x{F443} @Defop DICTUSETB +x{F445} @Defop DICTSETGETB +x{F446} @Defop DICTISETGETB +x{F447} @Defop DICTUSETGETB + +x{F449} @Defop DICTREPLACEB +x{F44A} @Defop DICTIREPLACEB +x{F44B} @Defop DICTUREPLACEB +x{F44D} @Defop DICTREPLACEGETB +x{F44E} @Defop DICTIREPLACEGETB +x{F44F} @Defop DICTUREPLACEGETB + +x{F451} @Defop DICTADDB +x{F452} @Defop DICTIADDB +x{F453} @Defop DICTUADDB +x{F455} @Defop DICTADDGETB +x{F456} @Defop DICTIADDGETB +x{F457} @Defop DICTUADDGETB + +x{F459} @Defop DICTDEL +x{F45A} @Defop DICTIDEL +x{F45B} @Defop DICTUDEL + +x{F462} @Defop DICTDELGET +x{F463} @Defop DICTDELGETREF +x{F464} @Defop DICTIDELGET +x{F465} @Defop DICTIDELGETREF +x{F466} @Defop DICTUDELGET +x{F467} @Defop DICTUDELGETREF + +x{F469} @Defop DICTGETOPTREF +x{F46A} @Defop DICTIGETOPTREF +x{F46B} @Defop DICTUGETOPTREF +x{F46D} @Defop DICTSETGETOPTREF +x{F46E} @Defop DICTISETGETOPTREF +x{F46F} @Defop DICTUSETGETOPTREF + +x{F470} @Defop PFXDICTSET +x{F471} @Defop PFXDICTREPLACE +x{F472} @Defop PFXDICTADD +x{F473} @Defop PFXDICTDEL + +x{F474} @Defop DICTGETNEXT +x{F475} @Defop DICTGETNEXTEQ +x{F476} @Defop DICTGETPREV +x{F477} @Defop DICTGETPREVEQ +x{F478} @Defop DICTIGETNEXT +x{F479} @Defop DICTIGETNEXTEQ +x{F47A} @Defop DICTIGETPREV +x{F47B} @Defop DICTIGETPREVEQ +x{F47C} @Defop DICTUGETNEXT +x{F47D} @Defop DICTUGETNEXTEQ +x{F47E} @Defop DICTUGETPREV +x{F47F} @Defop DICTUGETPREVEQ + +x{F482} @Defop DICTMIN +x{F483} @Defop DICTMINREF +x{F484} @Defop DICTIMIN +x{F485} @Defop DICTIMINREF +x{F486} @Defop DICTUMIN +x{F487} @Defop DICTUMINREF +x{F48A} @Defop DICTMAX +x{F48B} @Defop DICTMAXREF +x{F48C} @Defop DICTIMAX +x{F48D} @Defop DICTIMAXREF +x{F48E} @Defop DICTUMAX +x{F48F} @Defop DICTUMAXREF + +x{F492} @Defop DICTREMMIN +x{F493} @Defop DICTREMMINREF +x{F494} @Defop DICTIREMMIN +x{F495} @Defop DICTIREMMINREF +x{F496} @Defop DICTUREMMIN +x{F497} @Defop DICTUREMMINREF +x{F49A} @Defop DICTREMMAX +x{F49B} @Defop DICTREMMAXREF +x{F49C} @Defop DICTIREMMAX +x{F49D} @Defop DICTIREMMAXREF +x{F49E} @Defop DICTUREMMAX +x{F49F} @Defop DICTUREMMAXREF + +x{F4A0} @Defop DICTIGETJMP +x{F4A1} @Defop DICTUGETJMP +x{F4A2} @Defop DICTIGETEXEC +x{F4A3} @Defop DICTUGETEXEC +{ dup sbitrefs tuck 1 > swap 1 <> or abort"not a dictionary" swap 1 u@ over <> abort"not a dictionary" } : @chkdicts +{ dup null? tuck { idict! + not abort"cannot add key to procedure info dictionary" + @procinfo ! +} : @procinfo! +// ( x v1 v2 -- ) +{ not 2 pick @procinfo@ and xor swap @procinfo! } : @procinfo~! +// ( s i f -- ) +{ over @procdictkeylen fits not abort"procedure index out of range" + over swap dup @procinfo~! 2dup @proclistadd + 1 'nop does swap 0 (create) +} : @declproc +{ 1 'nop does swap 0 (create) } : @declglobvar +{ @proccnt @ 1+ dup @proccnt ! 1 @declproc } : @newproc +{ @gvarcnt @ 1+ dup @gvarcnt ! @declglobvar } : @newglobvar +variable @oldcurrent variable @oldctx +Fift-wordlist dup @oldcurrent ! @oldctx ! +{ current@ @oldcurrent ! context@ @oldctx ! Asm definitions + @proccnt @ @proclist @ @procdict @ @procinfo @ @gvarcnt @ @parent-state @ current@ @oldcurrent @ @oldctx @ + 9 tuple @parent-state ! + hole current! + 0 =: main @proclist null! @proccnt 0! @gvarcnt 0! + { bl word @newproc } : NEWPROC + { bl word dup (def?) ' drop ' @newproc cond } : DECLPROC + { bl word dup find + { nip execute <> abort"method redefined with different id" } + { swap 17 @declproc } + cond } : DECLMETHOD + { bl word @newglobvar } : DECLGLOBVAR + "main" 0 @proclistadd + dictnew dup @procdict ! + @procinfo ! 16 0 @procinfo! +} : PROGRAM{ +{ over sbits < { s>c + swap @addop + } { + drop + swap @procdictkeylen DICTPUSHCONST DICTIGETJMPZ 11 THROWARG + } cond +}> } : }END> +{ }END> b> } : }END>c +{ }END>c s + +0 constant recv_internal +-1 constant recv_external +-2 constant run_ticktock +-3 constant split_prepare +-4 constant split_install +-1111 constant entry_point +-1112 constant entry_point_recv + +{ asm-mode 0 3 ~! } : asm-no-remove-unused +{ asm-mode 1 1 ~! } : asm-remove-unused // enabled by default +{ asm-mode 3 3 ~! } : asm-warn-remove-unused +{ asm-mode 4 4 ~! } : asm-warn-inline-mix +{ asm-mode 0 4 ~! } : asm-no-warn-inline-mix // disabled by default +{ asm-mode 8 8 ~! } : asm-warn-unused +{ asm-mode 0 8 ~! } : asm-no-warn-unused // disabled by default + +// ( c -- ) add vm library for later use with runvmcode +{ spec } : hash>libref +// ( c -- c' ) +{ hash hash>libref } : >libref + +{ dup "." $pos dup -1 = + { drop 0 } + { $| 1 $| nip swap (number) 1- abort"invalid version" + dup dup 0 < swap 999 > or abort"invalid version" + } + cond +} : parse-version-level + +{ + 0 swap + "." $+ + { swap 1000 * swap parse-version-level rot + swap } 3 times + "" $= not abort"invalid version" +} : parse-asm-fif-version + +{ + dup =: required-version parse-asm-fif-version + asm-fif-version parse-asm-fif-version + = 1+ { + "Required Asm.fif version: " @' required-version "; actual Asm.fif version: " asm-fif-version $+ $+ $+ abort + } if +} : require-asm-fif-version + +{ + dup =: required-version parse-asm-fif-version + asm-fif-version parse-asm-fif-version + swap + >= 1+ { + "Required Asm.fif version: " @' required-version "; actual Asm.fif version: " asm-fif-version $+ $+ $+ abort + } if +} : require-asm-fif-version>= + + +Fift definitions Asm +' <{ : <{ +' PROGRAM{ : PROGRAM{ +' asm-fif-version : asm-fif-version +' require-asm-fif-version : require-asm-fif-version +' require-asm-fif-version>= : require-asm-fif-version>= +Fift diff --git a/fift/Disasm.fif b/fift/Disasm.fif new file mode 100644 index 00000000..26f8f4ad --- /dev/null +++ b/fift/Disasm.fif @@ -0,0 +1,141 @@ +library TVM_Disasm +// simple TVM Disassembler +"Lists.fif" include + +variable 'disasm +{ 'disasm @ execute } : disasm // disassemble a slice +// usage: x{74B0} disasm + +variable @dismode @dismode 0! +{ rot over @ and rot xor swap ! } : andxor! +{ -2 0 @dismode andxor! } : stack-disasm // output 's1 s4 XCHG' +{ -2 1 @dismode andxor! } : std-disasm // output 'XCHG s1, s4' +{ -3 2 @dismode andxor! } : show-vm-code +{ -3 0 @dismode andxor! } : hide-vm-code +{ @dismode @ 1 and 0= } : stack-disasm? + +variable @indent @indent 0! +{ ' space @indent @ 2* times } : .indent +{ @indent 1+! } : +indent +{ @indent 1-! } : -indent + +{ " " $pos } : spc-pos +{ dup " " $pos swap "," $pos dup 0< { drop } { + over 0< { nip } { min } cond } cond +} : spc-comma-pos +{ { dup spc-pos 0= } { 1 $| nip } while } : -leading +{ -leading -trailing dup spc-pos dup 0< { + drop dup $len { atom single } { drop nil } cond } { + $| swap atom swap -leading 2 { over spc-comma-pos dup 0>= } { + swap 1+ -rot $| 1 $| nip -leading rot + } while drop tuple + } cond +} : parse-op +{ dup "s-1" $= { drop "s(-1)" true } { + dup "s-2" $= { drop "s(-2)" true } { + dup 1 $| swap "x" $= { nip "x{" swap $+ +"}" true } { + 2drop false } cond } cond } cond +} : adj-op-arg +{ over count over <= { drop } { 2dup [] adj-op-arg { swap []= } { drop } cond } cond } : adj-arg[] +{ 1 adj-arg[] 2 adj-arg[] 3 adj-arg[] + dup first + dup `XCHG eq? { + drop dup count 2 = { tpop swap "s0" , swap , } if } { + dup `LSHIFT eq? { + drop dup count 2 = stack-disasm? and { second `LSHIFT# swap pair } if } { + dup `RSHIFT eq? { + drop dup count 2 = stack-disasm? and { second `RSHIFT# swap pair } if } { + drop + } cond } cond } cond +} : adjust-op + +variable @cp @cp 0! +variable @curop +variable @contX variable @contY variable @cdict + +{ atom>$ type } : .atom +{ dup first .atom dup count 1 > { space 0 over count 2- { 1+ 2dup [] type .", " } swap times 1+ [] type } { drop } cond } : std-show-op +{ 0 over count 1- { 1+ 2dup [] type space } swap times drop first .atom } : stk-show-op +{ @dismode @ 2 and { @curop @ csr. } if } : .curop? +{ .curop? .indent @dismode @ 1 and ' std-show-op ' stk-show-op cond cr +} : show-simple-op +{ dup 4 u@ 9 = { 8 u@+ swap 15 and 3 << s@ } { + dup 7 u@ 0x47 = { 7 u@+ nip 2 u@+ 7 u@+ -rot 3 << swap sr@ } { + dup 8 u@ 0x8A = { ref@ " cr } : show-cont-op +{ swap scont-swap ":<{" show-cont-bodyx scont-swap + "" show-cont-bodyx .indent ."}>" cr } : show-cont2-op + +{ @contX @ null? { "CONT" show-cont-op } ifnot +} : flush-contX +{ @contY @ null? { scont-swap "CONT" show-cont-op scont-swap } ifnot +} : flush-contY +{ flush-contY flush-contX } : flush-cont +{ @contX @ null? not } : have-cont? +{ @contY @ null? not } : have-cont2? +{ flush-contY @contY ! scont-swap } : save-cont-body + +{ @cdict ! } : save-const-dict +{ @cdict null! } : flush-dict +{ @cdict @ null? not } : have-dict? + +{ flush-cont .indent type .":<{" cr + @curop @ ref@ " cr +} : show-ref-op +{ flush-contY .indent rot type .":<{" cr + @curop @ ref@ " cr +} : show-cont-ref-op +{ flush-cont .indent swap type .":<{" cr + @curop @ ref@+ " cr +} : show-ref2-op + +{ flush-cont first atom>$ dup 5 $| drop "DICTI" $= swap + .indent type ." {" cr +indent @cdict @ @cdict null! unpair + rot { + swap .indent . ."=> <{" cr +indent disasm -indent .indent ."}>" cr true + } swap ' idictforeach ' dictforeach cond drop + -indent .indent ."}" cr +} : show-const-dict-op + +( `PUSHCONT `PUSHREFCONT ) constant @PushContL +( `REPEAT `UNTIL `IF `IFNOT `IFJMP `IFNOTJMP ) constant @CmdC1 +( `IFREF `IFNOTREF `IFJMPREF `IFNOTJMPREF `CALLREF `JMPREF ) constant @CmdR1 +( `DICTIGETJMP `DICTIGETJMPZ `DICTUGETJMP `DICTUGETJMPZ `DICTIGETEXEC `DICTUGETEXEC ) constant @JmpDictL +{ dup first `DICTPUSHCONST eq? { + flush-cont @curop @ get-const-dict save-const-dict show-simple-op } { + dup first @JmpDictL list-member? have-dict? and { + flush-cont show-const-dict-op } { + flush-dict + dup first @PushContL list-member? { + drop @curop @ get-cont-body save-cont-body } { + dup first @CmdC1 list-member? have-cont? and { + flush-contY first atom>$ .curop? show-cont-op } { + dup first @CmdR1 list-member? { + flush-cont first atom>$ dup $len 3 - $| drop .curop? show-ref-op } { + dup first `WHILE eq? have-cont2? and { + drop "WHILE" "}>DO<{" .curop? show-cont2-op } { + dup first `IFELSE eq? have-cont2? and { + drop "IF" "}>ELSE<{" .curop? show-cont2-op } { + dup first dup `IFREFELSE eq? swap `IFELSEREF eq? or have-cont? and { + first `IFREFELSE eq? "IF" "}>ELSE<{" rot .curop? show-cont-ref-op } { + dup first `IFREFELSEREF eq? { + drop "IF" "}>ELSE<{" .curop? show-ref2-op } { + flush-cont show-simple-op + } cond } cond } cond } cond } cond } cond } cond } cond } cond +} : show-op +{ dup @cp @ (vmoplen) dup 0> { 65536 /mod swap sr@+ swap dup @cp @ (vmopdump) parse-op swap s> true } { drop false } cond } : fetch-one-op +{ { fetch-one-op } { swap @curop ! adjust-op show-op } while } : disasm-slice +{ { disasm-slice dup sbitrefs 1- or 0= } { ref@ B 1 'nop } ::_ B{ +{ swap ({) over 2+ -roll swap (compile) (}) } : does +{ 1 'nop does create } : constant +{ 2 'nop does create } : 2constant +{ hole constant } : variable +10 constant ten +{ bl word 1 { find 0= abort"word not found" } } :: (') +{ bl word find not abort"-?" 0 swap } :: [compile] +{ bl word 1 { + dup find { " -?" $+ abort } ifnot nip execute +} } :: @' +{ bl word 1 { swap 1 'nop does swap 0 (create) } +} :: =: +{ bl word 1 { -rot 2 'nop does swap 0 (create) } +} :: 2=: +{ } : s>c +{ s>c hashB } : shash +// to be more efficiently re-implemented in C++ in the future +{ dup 0< ' negate if } : abs +{ 2dup > ' swap if } : minmax +{ minmax drop } : min +{ minmax nip } : max +"" constant <# +' $reverse : #> +{ swap 10 /mod char 0 + rot swap hold } : # +{ { # over 0<= } until } : #s +{ 0< { char - hold } if } : sign +// { dup abs <# #s rot sign #> nip } : (.) +// { (.) type } : ._ +// { ._ space } : . +{ dup 10 < { 48 } { 55 } cond + } : Digit +{ dup 10 < { 48 } { 87 } cond + } : digit +// x s b -- x' s' +{ rot swap /mod Digit rot swap hold } : B# +{ rot swap /mod digit rot swap hold } : b# +{ 16 B# } : X# +{ 16 b# } : x# +// x s b -- 0 s' +{ -rot { 2 pick B# over 0<= } until rot drop } : B#s +{ -rot { 2 pick b# over 0<= } until rot drop } : b#s +{ 16 B#s } : X#s +{ 16 b#s } : x#s +variable base +{ 10 base ! } : decimal +{ 16 base ! } : hex +{ 8 base ! } : octal +{ 2 base ! } : binary +{ base @ B# } : Base# +{ base @ b# } : base# +{ base @ B#s } : Base#s +{ base @ b#s } : base#s +// x w -- s +{ over abs <# rot 1- ' X# swap times X#s rot sign #> nip } : (0X.) +{ over abs <# rot 1- ' x# swap times x#s rot sign #> nip } : (0x.) +{ (0X.) type } : 0X._ +{ 0X._ space } : 0X. +{ (0x.) type } : 0x._ +{ 0x._ space } : 0x. +{ bl (-trailing) } : -trailing +{ char 0 (-trailing) } : -trailing0 +{ char " word 1 ' $+ } ::_ +" +{ find 0<> dup ' nip if } : (def?) +{ bl word 1 ' (def?) } :: def? +{ bl word 1 { (def?) not } } :: undef? +{ def? ' skip-to-eof if } : skip-ifdef +{ bl word dup (def?) { drop skip-to-eof } { 'nop swap 0 (create) } cond } : library +{ bl word dup (def?) { 2drop skip-to-eof } { swap 1 'nop does swap 0 (create) } cond } : library-version +{ hole dup 1 'nop does swap 1 { context! } does bl word tuck 0 (create) +"-wordlist" 0 (create) } : namespace +{ context@ current! } : definitions +{ char ) word "$" swap $+ 1 { find 0= abort"undefined parameter" execute } } ::_ $( +// b s -- ? +{ sbitrefs rot brembitrefs rot >= -rot <= and } : s-fits? +// b s x -- ? +{ swap sbitrefs -rot + rot brembitrefs -rot <= -rot <= and } : s-fits-with? +{ 0 swap ! } : 0! +{ tuck @ + swap ! } : +! +{ tuck @ swap - swap ! } : -! +{ 1 swap +! } : 1+! +{ -1 swap +! } : 1-! +{ null swap ! } : null! +{ not 2 pick @ and xor swap ! } : ~! +0 tuple constant nil +{ 1 tuple } : single +{ 2 tuple } : pair +{ 3 tuple } : triple +{ 1 untuple } : unsingle +{ 2 untuple } : unpair +{ 3 untuple } : untriple +{ over tuple? { swap count = } { 2drop false } cond } : tuple-len? +{ 0 tuple-len? } : nil? +{ 1 tuple-len? } : single? +{ 2 tuple-len? } : pair? +{ 3 tuple-len? } : triple? +{ 0 [] } : first +{ 1 [] } : second +{ 2 [] } : third +' pair : cons +' unpair : uncons +{ 0 [] } : car +{ 1 [] } : cdr +{ cdr car } : cadr +{ cdr cdr } : cddr +{ cdr cdr car } : caddr +{ null ' cons rot times } : list +{ -rot pair swap ! } : 2! +{ @ unpair } : 2@ +{ true (atom) drop } : atom +{ bl word atom 1 'nop } ::_ ` +{ hole dup 1 { @ execute } does create } : recursive +{ 0 { 1+ dup 1 ' $() does over (.) "$" swap $+ 0 (create) } rot times drop } : :$1..n +{ 10 hold } : +cr +{ 9 hold } : +tab +{ "" swap { 0 word 2dup $cmp } { rot swap $+ +cr swap } while 2drop } : scan-until-word +{ 0 word -trailing scan-until-word 1 'nop } ::_ $<< +{ 0x40 runvmx } : runvmcode +{ 0x48 runvmx } : gasrunvmcode +{ 0xc8 runvmx } : gas2runvmcode +{ 0x43 runvmx } : runvmdict +{ 0x4b runvmx } : gasrunvmdict +{ 0xcb runvmx } : gas2runvmdict +{ 0x45 runvmx } : runvm +{ 0x4d runvmx } : gasrunvm +{ 0xcd runvmx } : gas2runvm +{ 0x55 runvmx } : runvmctx +{ 0x5d runvmx } : gasrunvmctx +{ 0xdd runvmx } : gas2runvmctx +{ 0x75 runvmx } : runvmctxact +{ 0x7d runvmx } : gasrunvmctxact +{ 0xfd runvmx } : gas2runvmctxact +{ 0x35 runvmx } : runvmctxactq +{ 0x3d runvmx } : gasrunvmctxactq diff --git a/fift/Lists.fif b/fift/Lists.fif new file mode 100644 index 00000000..b59e40a0 --- /dev/null +++ b/fift/Lists.fif @@ -0,0 +1,220 @@ +library Lists // List utilities +// +{ hole dup 1 { @ execute } does create } : recursive +// x x' -- ? recursively compares two S-expressions +recursive equal? { + dup tuple? { + over tuple? { + over count over count over = { // t t' l ? + 0 { dup 0>= { 2dup [] 3 pick 2 pick [] equal? { 1+ } { drop -1 } cond + } if } rot times + nip nip 0>= + } { drop 2drop false } cond + } { 2drop false } cond + } { eqv? } cond +} swap ! +// (a1 .. an) -- (an .. a1) +{ null swap { dup null? not } { uncons swap rot cons swap } while drop } : list-reverse +// (a1 .. an) -- an Computes last element of non-empty list l +{ { uncons dup null? { drop true } { nip false } cond } until } : list-last +// l l' -- l++l' Concatenates two lists +recursive list+ { + over null? { nip } { swap uncons rot list+ cons } cond +} swap ! +// l l' -- l'' -1 or 0, where l = l' ++ l'' +// Removes prefix from list +{ { dup null? { drop true true } { + swap dup null? { 2drop false true } { // l' l + uncons swap rot uncons -rot equal? { false } { + 2drop false true + } cond } cond } cond } until +} : list- +// (a1 .. an) -- a1 .. an n Explodes a list +{ 0 { over null? not } { swap uncons rot 1+ } while nip } : explode-list +// (a1 .. an) x -- a1 .. an n x Explodes a list under the topmost element +{ swap explode-list dup 1+ roll } : explode-list-1 +// l -- t Transforms a list into a tuple with the same elements +{ explode-list tuple } : list>tuple +// a1 ... an n x -- (a1 .. an) x +{ null swap rot { -rot cons swap } swap times } : mklist-1 +// (s1 ... sn) -- s1+...+sn Concatenates a list of strings +{ "" { over null? not } { swap uncons -rot $+ } while nip +} : concat-string-list +// (x1 ... xn) -- x1+...+xn Sums a list of integers +{ 0 { over null? not } { swap uncons -rot + } while nip +} : sum-list +// (a1 ... an) a e -- e(...e(e(a,a1),a2),...),an) +{ -rot { over null? not } { swap uncons -rot 3 pick execute } while nip nip +} : foldl +// (a1 ... an) e -- e(...e(e(a1,a2),a3),...),an) +{ swap uncons swap rot foldl } : foldl-ne +// (a1 ... an) a e -- e(a1,e(a2,...,e(an,a)...)) +recursive foldr { + rot dup null? { 2drop } { + uncons -rot 2swap swap 3 pick foldr rot execute + } cond +} swap ! +// (a1 ... an) e -- e(a1,e(a2,...,e(a[n-1],an)...)) +recursive foldr-ne { + over cdr null? { drop car } { + swap uncons 2 pick foldr-ne rot execute + } cond +} swap ! +// (l1 ... ln) -- l1++...++ln Concatenates a list of lists +{ dup null? { ' list+ foldr-ne } ifnot } : concat-list-lists +// (a1 .. an . t) n -- t Computes the n-th tail of a list +{ ' cdr swap times } : list-tail +// (a0 .. an ..) n -- an Computes the n-th element of a list +{ list-tail car } : list-ref +// l -- ? +{ { dup null? { drop true true } { + dup pair? { cdr false } { + drop false true + } cond } cond } until +} : list? +// l -- n +{ 0 { over null? not } { 1+ swap uncons nip swap } while nip +} : list-length +// l e -- t // returns tail of l after first member that satisfies e +{ swap { + dup null? { nip true } { + tuck car over execute { drop true } { + swap cdr false + } cond } cond } until +} : list-tail-from +// a l -- t // tail of l after first occurence of a using eq? +{ swap 1 ' eq? does list-tail-from } : list-member-eq +{ swap 1 ' eqv? does list-tail-from } : list-member-eqv +{ swap 1 ' equal? does list-tail-from } : list-member-equal +// a l -- ? +{ list-member-eq null? not } : list-member? +{ list-member-eqv null? not } : list-member-eqv? +// l -- a -1 or 0 // returns car l if l is non-empty +{ dup null? { drop false } { car true } cond +} : safe-car +{ dup null? { drop false } { car second true } cond +} : get-first-value +// l e -- v -1 or 0 +{ list-tail-from safe-car } : assoc-gen +{ list-tail-from get-first-value } : assoc-gen-x +// a l -- (a.v) -1 or 0 -- returns first entry (a . v) in l +{ swap 1 { swap first eq? } does assoc-gen } : assq +{ swap 1 { swap first eqv? } does assoc-gen } : assv +{ swap 1 { swap first equal? } does assoc-gen } : assoc +// a l -- v -1 or 0 -- returns v from first entry (a . v) in l +{ swap 1 { swap first eq? } does assoc-gen-x } : assq-val +{ swap 1 { swap first eqv? } does assoc-gen-x } : assv-val +{ swap 1 { swap first equal? } does assoc-gen-x } : assoc-val +// (a1 .. an) e -- (e(a1) .. e(an)) +recursive list-map { + over null? { drop } { + swap uncons -rot over execute -rot list-map cons + } cond +} swap ! + +variable ctxdump variable curctx +// (a1 .. an) e -- executes e for a1, ..., an +{ ctxdump @ curctx @ ctxdump 2! curctx 2! + { curctx 2@ over null? not } { swap uncons rot tuck curctx 2! execute } + while 2drop ctxdump 2@ curctx ! ctxdump ! +} : list-foreach +forget ctxdump forget curctx + +// +// Experimental implementation of `for` loops with index +// +variable loopdump variable curloop +{ curloop @ loopdump @ loopdump 2! } : push-loop-ctx +{ loopdump 2@ loopdump ! curloop ! } : pop-loop-ctx +// ilast i0 e -- executes e for i=i0,i0+1,...,ilast-1 +{ -rot 2dup > { + push-loop-ctx { + triple dup curloop ! first execute curloop @ untriple 1+ 2dup <= + } until pop-loop-ctx + } if 2drop drop +} : for +// ilast i0 e -- same as 'for', but pushes current index i before executing e +{ -rot 2dup > { + push-loop-ctx { + triple dup curloop ! untriple nip swap execute curloop @ untriple 1+ 2dup <= + } until pop-loop-ctx + } if 2drop drop +} : for-i +// ( -- i ) Returns innermost loop index +{ curloop @ third } : i +// ( -- j ) Returns outer loop index +{ loopdump @ car third } : j +{ loopdump @ cadr third } : k +forget curloop forget loopdump + +// +// create Lisp-style lists using words "(" and ")" +// +variable ') +'nop box constant ', +{ ") without (" abort } ') ! +{ ') @ execute } : ) +anon constant dot-marker +// m x1 ... xn t m -- (x1 ... xn . t) +{ swap + { -rot 2dup eq? not } + { over dot-marker eq? abort"invalid dotted list" + swap rot cons } while 2drop +} : list-tail-until-marker +// m x1 ... xn m -- (x1 ... xn) +{ null swap list-tail-until-marker } : list-until-marker +{ over dot-marker eq? { nip 2dup eq? abort"invalid dotted list" } + { null swap } cond + list-tail-until-marker +} : list-until-marker-ext +{ ') @ ', @ } : ops-get +{ ', ! ') ! } : ops-set +{ anon dup ops-get 3 { ops-set list-until-marker-ext } does ') ! 'nop ', ! +} : ( +// test of Lisp-style lists +// ( 42 ( `+ 9 ( `* 3 4 ) ) "test" ) .l cr +// ( `eq? ( `* 3 4 ) 3 4 * ) .l cr +// `alpha ( `beta `gamma `delta ) cons .l cr +// { ( `eq? ( `* 3 5 pick ) 3 4 roll * ) } : 3*sample +// 17 3*sample .l cr + +// similar syntax _( x1 .. xn ) for tuples +{ 2 { 1+ 2dup pick eq? } until 3 - nip } : count-to-marker +{ count-to-marker tuple nip } : tuple-until-marker +{ anon dup ops-get 3 { ops-set tuple-until-marker } does ') ! 'nop ', ! } : _( +// test of tuples +// _( _( 2 "two" ) _( 3 "three" ) _( 4 "four" ) ) .dump cr + +// pseudo-Lisp tokenizer +"()[]'" 34 hold constant lisp-delims +{ lisp-delims 11 (word) } : lisp-token +{ null cons `quote swap cons } : do-quote +{ 1 { ', @ 2 { 2 { ', ! execute ', @ execute } does ', ! } + does ', ! } does +} : postpone-prefix +{ ', @ 1 { ', ! } does ', ! } : postpone-', +( `( ' ( pair + `) ' ) pair + `[ ' _( pair + `] ' ) pair + `' ' do-quote postpone-prefix pair + `. ' dot-marker postpone-prefix pair + `" { char " word } pair + `;; { 0 word drop postpone-', } pair +) constant lisp-token-dict +variable eol +{ eol @ eol 0! anon dup ') @ 'nop 3 + { ops-set list-until-marker-ext true eol ! } does ') ! rot ', ! + { lisp-token dup (number) dup { roll drop } { + drop atom dup lisp-token-dict assq { nip second execute } if + } cond + ', @ execute + eol @ + } until + -rot eol ! execute +} :_ List-generic( +{ 'nop 'nop List-generic( } :_ LIST( +// LIST((lambda (x) (+ x 1)) (* 3 4)) +// LIST('(+ 3 4)) +// LIST(2 3 "test" . 9) +// LIST((process '[plus 3 4])) diff --git a/fift/dasm.fif b/fift/dasm.fif new file mode 100644 index 00000000..e406553b --- /dev/null +++ b/fift/dasm.fif @@ -0,0 +1,16 @@ +#!/usr/bin/fift -s +"Asm.fif" include +"Disasm.fif" include + +"../build/wallet_v5_compiled.txt" file>B B>$ x>B B>boc + +show-vm-code std-disasm +indent +indent + +."Disasm" cr +dup B B>x .dump cr cr + +."Free bits in root cell" +dup =0.10.0" } }, "node_modules/@ampproject/remapping": { @@ -43,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", @@ -156,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": { @@ -178,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" }, @@ -194,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" @@ -268,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" @@ -409,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" @@ -598,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": { @@ -633,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": { @@ -674,6 +628,128 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz", + "integrity": "sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@ipld/dag-pb": { "version": "2.1.18", "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-2.1.18.tgz", @@ -683,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", @@ -709,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": { @@ -726,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", @@ -742,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" }, @@ -773,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" @@ -813,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": "*", @@ -867,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", @@ -914,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" @@ -929,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": { @@ -944,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", @@ -957,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", @@ -987,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" @@ -1010,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" @@ -1025,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", @@ -1044,6 +1126,47 @@ "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", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@orbs-network/ton-access": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@orbs-network/ton-access/-/ton-access-2.3.3.tgz", @@ -1118,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 }, @@ -1131,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" @@ -1149,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.5-tvmbeta.3", + "resolved": "https://registry.npmjs.org/@ton-community/func-js-bin/-/func-js-bin-0.4.5-tvmbeta.3.tgz", + "integrity": "sha512-bPeXO1dhjvJuMtiP20eePF1UI536ygot9FIBj8lPwWKBLEphBkz0uPp1vhNg2vvPZACGw6vzw840rAGpCtnFRg==", + "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", @@ -1206,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-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/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/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": { @@ -1262,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", @@ -1281,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", @@ -1367,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": "*" @@ -1418,6 +1603,18 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", @@ -1430,6 +1627,12 @@ "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -1451,19 +1654,315 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/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/@typescript-eslint/eslint-plugin/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==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/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/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/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==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/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/@typescript-eslint/utils/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==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/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/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", @@ -1472,6 +1971,22 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1539,74 +2054,203 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/axios": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", - "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", "dev": true, "dependencies": { - "follow-redirects": "^1.14.7" + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dev": true, "dependencies": { - "@jest/transform": "^29.6.4", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">= 0.4" }, - "peerDependencies": { - "@babel/core": "^7.8.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" }, "engines": { - "node": ">=8" - } - }, + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.7" + } + }, + "node_modules/babel-jest": { + "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.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/babel-plugin-jest-hoist": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", @@ -1714,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", @@ -1725,21 +2375,21 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "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": [ { @@ -1756,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" @@ -1820,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" @@ -1861,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": [ { @@ -1958,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": { @@ -1976,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" @@ -2088,6 +2744,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "node_modules/constant-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", @@ -2105,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", @@ -2125,177 +2808,802 @@ "node": ">= 8" } }, - "node_modules/dataloader": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", - "integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/dataloader": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", + "integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "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" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "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", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "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://dotenvx.com" + } + }, + "node_modules/electron-to-chromium": { + "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": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "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" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz", + "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.3", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", + "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", + "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.8.0", + "has": "^1.0.3", + "is-core-module": "^2.13.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", + "object.values": "^1.1.6", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", "dev": true, "dependencies": { - "ms": "2.1.2" + "prettier-linter-helpers": "^1.0.0" }, "engines": { - "node": ">=6.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "eslint-config-prettier": { "optional": true } } }, - "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "node_modules/eslint-plugin-unused-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz", + "integrity": "sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==", "dev": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, "peerDependencies": { - "babel-plugin-macros": "^3.1.0" + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.0" }, "peerDependenciesMeta": { - "babel-plugin-macros": { + "@typescript-eslint/eslint-plugin": { "optional": true } } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=4.0.0" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "dependencies": { - "clone": "^1.0.2" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8.0.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, "engines": { - "node": ">=0.4.0" + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { - "node": ">=0.3.1" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "engines": { - "node": ">=12" + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/eslint/node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } }, - "node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", - "dev": true + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "dependencies": { - "is-arrayish": "^0.2.1" + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=0.8.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -2311,6 +3619,66 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ethjs-unit": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", @@ -2373,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" @@ -2402,12 +3770,67 @@ "node": ">=4" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -2432,10 +3855,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2457,10 +3892,30 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "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": [ { @@ -2477,6 +3932,15 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2492,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": { @@ -2518,11 +3982,47 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "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", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "dev": true }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2542,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" @@ -2577,6 +4081,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2597,6 +4117,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -2606,12 +4138,71 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/hamt-sharding": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-2.0.1.tgz", @@ -2638,6 +4229,15 @@ "node": ">= 0.4.0" } }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2647,6 +4247,18 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "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": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", @@ -2671,6 +4283,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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", @@ -2728,6 +4367,40 @@ } ] }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -2808,16 +4481,36 @@ "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", "integrity": "sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg==", "dev": true }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "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" @@ -2877,12 +4570,72 @@ "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", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", @@ -2895,6 +4648,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2913,6 +4690,18 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-hex-prefixed": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", @@ -2932,22 +4721,77 @@ "node": ">=8" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-stream": { @@ -2962,6 +4806,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -2974,6 +4863,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3000,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" }, @@ -3015,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" }, @@ -3042,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", @@ -3077,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", @@ -3135,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" @@ -3161,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": { @@ -3175,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" @@ -3206,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": { @@ -3240,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" }, @@ -3285,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" @@ -3312,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" @@ -3354,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", @@ -3366,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" }, @@ -3379,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", @@ -3418,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" }, @@ -3427,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" @@ -3467,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" @@ -3487,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" }, @@ -3532,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", @@ -3550,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" }, @@ -3565,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", @@ -3575,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": { @@ -3629,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", @@ -3646,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", @@ -3656,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" @@ -3675,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": { @@ -3694,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" }, @@ -3754,12 +5639,30 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3781,6 +5684,15 @@ "node": "*" } }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -3799,6 +5711,19 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -3829,6 +5754,12 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -3884,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" }, @@ -3911,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", @@ -3932,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", @@ -3950,6 +5872,15 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -4036,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": { @@ -4062,6 +5993,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -4108,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": { @@ -4155,18 +6092,108 @@ "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" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "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" @@ -4196,6 +6223,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -4324,6 +6368,18 @@ "tslib": "^2.0.3" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -4390,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" @@ -4404,10 +6460,19 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "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": { @@ -4449,6 +6514,15 @@ "integrity": "sha512-ghUWxQ1T9IJmPu6eshc3VU0OwveUtXQ33ZLXYUcz1Oc5ppKLDXKp0TBDj6b0epwhEctzcQSNGR2iHyvQSn4W5A==", "dev": true }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -4464,10 +6538,22 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "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", @@ -4529,10 +6615,26 @@ "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", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "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": [ { @@ -4555,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" @@ -4569,6 +6671,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/rabin-wasm": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", @@ -4606,6 +6728,35 @@ "node": ">= 6" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4675,6 +6826,31 @@ "node": ">=8" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -4684,6 +6860,29 @@ "node": ">=0.12.0" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -4693,6 +6892,24 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4713,6 +6930,20 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4739,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", @@ -4761,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" @@ -4893,6 +7145,51 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4998,6 +7295,12 @@ "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -5065,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" }, @@ -5294,10 +7598,64 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "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": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, "node_modules/tweetnacl": { @@ -5312,6 +7670,18 @@ "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", "dev": true }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -5333,17 +7703,82 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "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": { @@ -5355,10 +7790,31 @@ "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", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "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": [ { @@ -5375,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" @@ -5403,12 +7859,27 @@ "tslib": "^2.0.3" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -5416,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", @@ -5460,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": { @@ -5490,6 +7955,41 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -5587,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 0e97ea84..fee70664 100644 --- a/package.json +++ b/package.json @@ -4,23 +4,38 @@ "scripts": { "start": "blueprint run", "build": "blueprint build", - "build:w5_1": "blueprint build Wallet_V5_1", - "build:w5_2": "blueprint build Wallet_V5_2", - "test": "jest" + "build:v5": "blueprint build wallet_v5", + "deploy-library": "npx blueprint run deployLibrary", + "deploy-wallet": "blueprint run deployWalletV5", + "print-wallet-code": "ts-node ./scripts/printWalletCode.ts", + "test": "jest", + "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", + "@typescript-eslint/eslint-plugin": "^5.38.1", + "@typescript-eslint/parser": "^5.38.1", + "eslint": "8.22.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "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", + "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" + }, + "overrides": { + "@ton-community/func-js-bin": "0.4.5-tvmbeta.3", + "@ton-community/func-js": "0.7.0" } } diff --git a/scripts/deployLibrary.ts b/scripts/deployLibrary.ts new file mode 100644 index 00000000..94059f39 --- /dev/null +++ b/scripts/deployLibrary.ts @@ -0,0 +1,54 @@ +import { toNano } from 'ton-core'; +import { compile, NetworkProvider } from '@ton/blueprint'; +import 'dotenv/config'; +import { LibraryDeployer } from '../wrappers/library-deployer'; + +export async function run(provider: NetworkProvider) { + /* const library: SimpleLibrary = { + public: true, + root: await compile('wallet_v5') + }; + + let secretKey = process.env.LIBRARY_KEEPER_SECRET_KEY; + if (!secretKey) { + const keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + console.log('GENERATED PRIVATE_KEY', keypair.secretKey.toString('hex')); + secretKey = keypair.secretKey.toString('hex'); + fs.appendFileSync('.env', `LIBRARY_KEEPER_SECRET_KEY=${secretKey}`); + } + + const keypair = keyPairFromSecretKey(Buffer.from(secretKey, 'hex')); + + const libraryKeeper = provider.open( + LibraryKeeper.createFromConfig( + { publicKey: keypair.publicKey, seqno: 0 }, + await compile('library-keeper') + ) + ); + + const isActive = await libraryKeeper.getIsActive(); + + if (!isActive) { + await libraryKeeper.sendDeploy(provider.sender(), toNano('0.1')); + await provider.waitForDeploy(libraryKeeper.address); + } + + await libraryKeeper.sendAddLibrary({ + libraryCode: library.root, + secretKey: keypair.secretKey + }); + + console.log('LIBRARY KEEPER ADDRESS', libraryKeeper.address);*/ + + const libraryDeployer = provider.open( + LibraryDeployer.createFromConfig( + { libraryCode: await compile('wallet_v5') }, + await compile('library-deployer') + ) + ); + + await libraryDeployer.sendDeploy(provider.sender(), toNano('0.1')); + await provider.waitForDeploy(libraryDeployer.address); + + console.log('LIBRARY ADDRESS', libraryDeployer.address); +} diff --git a/scripts/deployWalletV5.ts b/scripts/deployWalletV5.ts index 0232b5de..47625220 100644 --- a/scripts/deployWalletV5.ts +++ b/scripts/deployWalletV5.ts @@ -1,21 +1,33 @@ -import { toNano } from 'ton-core'; -import { WalletV5 } from '../wrappers/WalletV5'; -import { compile, NetworkProvider } from '@ton-community/blueprint'; +import { Dictionary, toNano } from 'ton-core'; +import { WalletId, WalletV5 } from '../wrappers/wallet-v5'; +import { compile, NetworkProvider } from '@ton/blueprint'; +import { LibraryDeployer } from '../wrappers/library-deployer'; +import { getSecureRandomBytes, keyPairFromSeed } from 'ton-crypto'; +/* + DOESN'T WORK WITH TONKEEPER. CHOOSE DEPLOY WITH MNEMONIC + */ export async function run(provider: NetworkProvider) { - // const walletV5 = provider.open( - // WalletV5.createFromConfig( - // { - // id: Math.floor(Math.random() * 10000), - // counter: 0, - // }, - // await compile('WalletV5') - // ) - // ); + const keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + console.log('KEYPAIR PUBKEY', keypair.publicKey.toString('hex')); + console.log('KEYPAIR PRIVATE_KEY', keypair.secretKey.toString('hex')); - // await walletV5.sendDeploy(provider.sender(), toNano('0.05')); + const walletV5 = provider.open( + WalletV5.createFromConfig( + { + signatureAllowed: true, + seqno: 0, + walletId: new WalletId({ networkGlobalId: -3 }).serialized, // testnet + publicKey: keypair.publicKey, + extensions: Dictionary.empty() as any + }, + LibraryDeployer.exportLibCode(await compile('wallet_v5')) + ) + ); - // await provider.waitForDeploy(walletV5.address); + await walletV5.sendDeploy(provider.sender(), toNano('0.1')); - // console.log('ID', await walletV5.getID()); + await provider.waitForDeploy(walletV5.address); + + console.log('WALLET ADDRESS', walletV5.address); } diff --git a/scripts/deployWalletV5WithoutLibrary.ts b/scripts/deployWalletV5WithoutLibrary.ts new file mode 100644 index 00000000..e43e6991 --- /dev/null +++ b/scripts/deployWalletV5WithoutLibrary.ts @@ -0,0 +1,28 @@ +import { Dictionary, toNano } from 'ton-core'; +import { WalletId, WalletV5 } from '../wrappers/wallet-v5'; +import { compile, NetworkProvider } from '@ton-community/blueprint'; +import { getSecureRandomBytes, keyPairFromSeed } from 'ton-crypto'; + +export async function run(provider: NetworkProvider) { + const keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + console.log('KEYPAIR PUBKEY', keypair.publicKey.toString('hex')); + console.log('KEYPAIR PRIVATE_KEY', keypair.secretKey.toString('hex')); + + const walletV5 = provider.open( + WalletV5.createFromConfig( + { + seqno: 0, + walletId: new WalletId({ networkGlobalId: -3 }).serialized, // testnet + publicKey: keypair.publicKey, + extensions: Dictionary.empty() + }, + await compile('wallet_v5') + ) + ); + + await walletV5.sendDeploy(provider.sender(), toNano('0.05')); + + await provider.waitForDeploy(walletV5.address); + + console.log('WALLET ADDRESS', walletV5.address); +} diff --git a/scripts/disasm.sh b/scripts/disasm.sh new file mode 100755 index 00000000..aaa06c55 --- /dev/null +++ b/scripts/disasm.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cd "$(dirname "$0")" || exit +cd ../fift || exit + +jq -j ".hex" ../build/wallet_v5.compiled.json > ../build/wallet_v5_compiled.txt + +fift dasm.fif diff --git a/scripts/print.sh b/scripts/print.sh new file mode 100755 index 00000000..70db9540 --- /dev/null +++ b/scripts/print.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cd "$(dirname "$0")" || exit +cd ../fift || exit + +func -SP ../contracts/wallet_v5.fc > ../build/wallet_v5_code.fif + +fift print.fif diff --git a/scripts/printWalletCode.ts b/scripts/printWalletCode.ts new file mode 100644 index 00000000..e7e3f507 --- /dev/null +++ b/scripts/printWalletCode.ts @@ -0,0 +1,13 @@ +import { compile } from '@ton-community/blueprint'; +import { LibraryDeployer } from '../wrappers/library-deployer'; + +export async function run() { + const walletCode = await compile('wallet_v5'); + const code = LibraryDeployer.exportLibCode(walletCode); + + console.log('WALLET CODE HEX', code.toBoc().toString('hex'), '\n'); + console.log('WALLET CODE BASE64', code.toBoc().toString('base64'), '\n'); + console.log('WALLET FULL CODE BASE64', walletCode.toBoc().toString('base64')); +} + +run(); diff --git a/scripts/scalpel.sh b/scripts/scalpel.sh new file mode 100755 index 00000000..52a29707 --- /dev/null +++ b/scripts/scalpel.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +cd "$(dirname "$0")" || exit +cd .. + +RED="\e[31;1m" +YELLOW="\e[33;1m" +GREEN="\e[32;1m" +ENDCOLOR="\e[0m" + +if [[ "$1" == "-r" ]]; then + echo -e "$RED* Cleaning up the instrument *$ENDCOLOR" + rm -f build/wallet_v5*.fif +fi + +if [ ! -f build/wallet_v5.fif ]; then + mkdir -p build + echo -e "$YELLOW* Creating comparation origin *$ENDCOLOR" + echo "Use scalpel.sh -r to clean the instrument after commiting" + func contracts/imports/stdlib.fc contracts/wallet_v5.fc > build/wallet_v5.fif + func -SR contracts/imports/stdlib.fc contracts/wallet_v5.fc >build/wallet_v5_x.fif 2>&1 +fi + +declare -A mlen +declare -A mlen_new + +func contracts/imports/stdlib.fc contracts/wallet_v5.fc > build/wallet_v5_vs.fif +func -SR contracts/imports/stdlib.fc contracts/wallet_v5.fc >build/wallet_v5_vs_x.fif 2>&1 + +KEY="" +CNT=0 +while IFS= read -r line +do + if [[ "$line" =~ ^"//" ]]; then continue; fi + if [[ "$line" =~ ^"DECLPROC" ]]; then continue; fi + if [[ "$line" =~ ^[0-9]+" DECLMETHOD" ]]; then continue; fi + if ! [[ "$line" =~ ^" " ]]; then + if [[ "$line" == "}>" ]]; then + mlen["$KEY"]="$CNT" + else + CNT=0 + KEY="$line" + fi + else + CNT=$((CNT+1)) + fi +done < build/wallet_v5.fif + +KEY="" +CNT=0 +while IFS= read -r line +do + if [[ "$line" =~ ^"//" ]]; then continue; fi + if [[ "$line" =~ ^"DECLPROC" ]]; then continue; fi + if [[ "$line" =~ ^[0-9]+" DECLMETHOD" ]]; then continue; fi + if ! [[ "$line" =~ ^" " ]]; then + if [[ "$line" == "}>" ]]; then + mlen_new["$KEY"]="$CNT" + else + CNT=0 + KEY="$line" + fi + else + CNT=$((CNT+1)) + fi +done < build/wallet_v5_vs.fif + +diff -C 5 build/wallet_v5.fif build/wallet_v5_vs.fif + +LINES=$(grep -v -e '^//' -e '^DECLPROC' -e '^[0-9]\+ DECLMETHOD' build/wallet_v5.fif | wc -l) +NLINES=$(grep -v -e '^//' -e '^DECLPROC' -e '^[0-9]\+ DECLMETHOD' build/wallet_v5_vs.fif | wc -l) +echo "" +echo -e "${YELLOW}Lines: $LINES -> $NLINES$ENDCOLOR" +for key in "${!mlen_new[@]}" +do + PFX="" + if [ "${mlen_new[$key]}" -gt "${mlen[$key]}" ]; then + PFX=$RED + fi + if [ "${mlen_new[$key]}" -lt "${mlen[$key]}" ]; then + PFX=$GREEN + fi + echo -e "$PFX${mlen[$key]} -> ${mlen_new[$key]} | $key$ENDCOLOR"; +done +echo "" diff --git a/tests/WalletW5.spec.ts b/tests/WalletW5.spec.ts new file mode 100644 index 00000000..263fd85e --- /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/Wallet_V5_1.spec.ts b/tests/Wallet_V5_1.spec.ts deleted file mode 100644 index 631d08c0..00000000 --- a/tests/Wallet_V5_1.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Blockchain, SandboxContract } from '@ton-community/sandbox'; -import { Cell, Dictionary, toNano } from 'ton-core'; -import { WalletV5 } from '../wrappers/WalletV5'; -import '@ton-community/test-utils'; -import { compile } from '@ton-community/blueprint'; -import { KeyPair, getSecureRandomBytes, keyPairFromSeed } from 'ton-crypto'; - -describe('Wallet_V5_1', () => { - let code: Cell; - - beforeAll(async () => { - code = await compile('Wallet_V5_1'); - }); - - let blockchain: Blockchain; - let walletV5: SandboxContract; - let keypair: KeyPair; - - beforeEach(async () => { - blockchain = await Blockchain.create(); - keypair = keyPairFromSeed(await getSecureRandomBytes(32)); - - walletV5 = blockchain.openContract( - WalletV5.createFromConfig( - { - seqno: 0, - subwallet: 20230823 + 0, - publicKey: keypair.publicKey, - extensions: Dictionary.empty(), - }, - code - ) - ); - - const deployer = await blockchain.treasury('deployer'); - - const deployResult = await walletV5.sendDeploy(deployer.getSender(), toNano('0.05')); - - expect(deployResult.transactions).toHaveTransaction({ - from: deployer.address, - to: walletV5.address, - deploy: true, - success: true, - }); - }); - - it('should deploy', async () => { - // the check is done inside beforeEach - // blockchain and walletV5 are ready to use - }); - -}); diff --git a/tests/Wallet_V5_2.spec.ts b/tests/Wallet_V5_2.spec.ts deleted file mode 100644 index 50bee460..00000000 --- a/tests/Wallet_V5_2.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Blockchain, SandboxContract } from '@ton-community/sandbox'; -import { Cell, Dictionary, toNano } from 'ton-core'; -import { WalletV5 } from '../wrappers/WalletV5'; -import '@ton-community/test-utils'; -import { compile } from '@ton-community/blueprint'; -import { KeyPair, getSecureRandomBytes, keyPairFromSeed } from 'ton-crypto'; - -describe('Wallet_V5_2', () => { - let code: Cell; - - beforeAll(async () => { - code = await compile('Wallet_V5_2'); - }); - - let blockchain: Blockchain; - let walletV5: SandboxContract; - let keypair: KeyPair; - - beforeEach(async () => { - blockchain = await Blockchain.create(); - keypair = keyPairFromSeed(await getSecureRandomBytes(32)); - - walletV5 = blockchain.openContract( - WalletV5.createFromConfig( - { - seqno: 0, - subwallet: 20230823 + 0, - publicKey: keypair.publicKey, - extensions: Dictionary.empty(), - }, - code - ) - ); - - const deployer = await blockchain.treasury('deployer'); - - const deployResult = await walletV5.sendDeploy(deployer.getSender(), toNano('0.05')); - - expect(deployResult.transactions).toHaveTransaction({ - from: deployer.address, - to: walletV5.address, - deploy: true, - success: true, - }); - }); - - it('should deploy', async () => { - // the check is done inside beforeEach - // blockchain and walletV5 are ready to use - }); - -}); diff --git a/tests/actions.ts b/tests/actions.ts new file mode 100644 index 00000000..cacba0b7 --- /dev/null +++ b/tests/actions.ts @@ -0,0 +1,137 @@ +import { Address, beginCell, Cell, MessageRelaxed, SendMode, storeMessageRelaxed } from '@ton/core'; +import { isTestOnlyExtendedAction, TestOnlyExtendedAction, TestOnlyOutAction } from './test-only-actions'; + +export class ActionSendMsg { + public static readonly tag = 0x0ec3c86d; + + public readonly tag = ActionSendMsg.tag; + + constructor(public readonly mode: SendMode, public readonly outMsg: MessageRelaxed) {} + + public serialize(): Cell { + return beginCell() + .storeUint(this.tag, 32) + .storeUint(this.mode | SendMode.IGNORE_ERRORS, 8) + .storeRef(beginCell().store(storeMessageRelaxed(this.outMsg)).endCell()) + .endCell(); + } +} + +export class ActionAddExtension { + public static readonly tag = 0x02; + + public readonly tag = ActionAddExtension.tag; + + constructor(public readonly address: Address) {} + + public serialize(): Cell { + return beginCell().storeUint(this.tag, 8).storeAddress(this.address).endCell(); + } +} + +export class ActionRemoveExtension { + public static readonly tag = 0x03; + + public readonly tag = ActionRemoveExtension.tag; + + constructor(public readonly address: Address) {} + + public serialize(): Cell { + return beginCell().storeUint(this.tag, 8).storeAddress(this.address).endCell(); + } +} + +export class ActionSetSignatureAuthAllowed { + public static readonly tag = 0x04; + + public readonly tag = ActionSetSignatureAuthAllowed.tag; + + constructor(public readonly allowed: Boolean) {} + + public serialize(): Cell { + return beginCell() + .storeUint(this.tag, 8) + .storeUint(this.allowed ? 1 : 0, 1) + .endCell(); + } +} + +export type OutAction = ActionSendMsg | TestOnlyOutAction; +export type ExtendedAction = + | ActionAddExtension + | ActionRemoveExtension + | ActionSetSignatureAuthAllowed + | TestOnlyExtendedAction; + +export function isExtendedAction(action: OutAction | ExtendedAction): action is ExtendedAction { + return ( + action.tag === ActionAddExtension.tag || + action.tag === ActionRemoveExtension.tag || + action.tag === ActionSetSignatureAuthAllowed.tag || + isTestOnlyExtendedAction(action) + ); +} + +function packActionsListOut(actions: (OutAction | ExtendedAction)[]): Cell { + if (actions.length === 0) { + return beginCell().endCell(); + } + + const [action, ...rest] = actions; + + if (isExtendedAction(action)) { + throw new Error('Actions bust be in an order: all extended actions, all out actions'); + } + + return beginCell() + .storeRef(packActionsListOut(rest)) + .storeSlice(action.serialize().beginParse()) + .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(); +} + +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 { + return packActionsListExtended(actions); +} diff --git a/tests/config.ts b/tests/config.ts new file mode 100644 index 00000000..fc688698 --- /dev/null +++ b/tests/config.ts @@ -0,0 +1,6 @@ +const config: any = { + // Inspect per-instruction execution in primary (contest) test cases + microscope: false +} + +export default config; diff --git a/tests/gasUtils.ts b/tests/gasUtils.ts new file mode 100644 index 00000000..f322cea4 --- /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 new file mode 100644 index 00000000..00ca35df --- /dev/null +++ b/tests/test-only-actions.ts @@ -0,0 +1,82 @@ +import { + Address, + beginCell, + Cell, + CurrencyCollection, + MessageRelaxed, + SendMode, + storeCurrencyCollection, + storeMessageRelaxed +} from '@ton/core'; +import { + ExtendedAction, + OutAction +} from './actions'; + +export type LibRef = Cell | bigint; + +export class ActionSetCode { + public static readonly tag = 0xad4de08e; + + public readonly tag = ActionSetCode.tag; + + constructor(public readonly newCode: Cell) {} + + public serialize(): Cell { + return beginCell().storeUint(this.tag, 32).storeRef(this.newCode).endCell(); + } +} + +export class ActionReserveCurrency { + public static readonly tag = 0x36e6b809; + + public readonly tag = ActionReserveCurrency.tag; + + constructor(public readonly mode: SendMode, public readonly currency: CurrencyCollection) {} + + public serialize(): Cell { + return beginCell() + .storeUint(this.tag, 32) + .storeUint(this.mode, 8) + .store(storeCurrencyCollection(this.currency)) + .endCell(); + } +} + +export class ActionChangeLibrary { + public static readonly tag = 0x26fa1dd4; + + public readonly tag = ActionChangeLibrary.tag; + + constructor(public readonly mode: number, public readonly libRef: LibRef) {} + + public serialize(): Cell { + const cell = beginCell().storeUint(this.tag, 32).storeUint(this.mode, 7); + if (typeof this.libRef === 'bigint') { + return cell.storeUint(0, 1).storeUint(this.libRef, 256).endCell(); + } + + return cell.storeUint(1, 1).storeRef(this.libRef).endCell(); + } +} + +export class ActionSetData { + public static readonly tag = 0x1ff8ea0b; + + public readonly tag = ActionSetData.tag; + + constructor(public readonly data: Cell) {} + + public serialize(): Cell { + return beginCell().storeUint(this.tag, 32).storeRef(this.data).endCell(); + } +} + +export type TestOnlyOutAction = ActionSetCode | ActionReserveCurrency | ActionChangeLibrary; +export type TestOnlyExtendedAction = ActionSetData; + +export function isTestOnlyExtendedAction(action: OutAction | ExtendedAction): action is ExtendedAction { + return ( + action.tag === ActionSetData.tag + ); +} diff --git a/tests/utils.ts b/tests/utils.ts new file mode 100644 index 00000000..8a326885 --- /dev/null +++ b/tests/utils.ts @@ -0,0 +1,108 @@ +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) { + return bufferToBigInt(address.hash); +} + +export function validUntil(ttlMs = 1000 * 60 * 3) { + return Math.floor((Date.now() + ttlMs) / 1000); +} + +export function createMsgInternal(params: { + bounce?: boolean; + dest: Address; + value: bigint | CurrencyCollection; + body?: Cell; + init?: StateInit | null; +}): MessageRelaxed { + return { + info: { + type: 'internal', + ihrDisabled: true, + bounce: params.bounce ?? false, + bounced: false, + dest: params.dest, + value: typeof params.value === 'bigint' ? { coins: params.value } : params.value, + ihrFee: 0n, + forwardFee: 0n, + createdLt: 0n, + createdAt: 0 + }, + body: params.body || beginCell().endCell(), + init: params.init + }; +} + +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 = () => {}; + await callback(); + console.error = errorsHandler; +} diff --git a/tests/wallet-v5-extensions.spec.ts b/tests/wallet-v5-extensions.spec.ts new file mode 100644 index 00000000..68221053 --- /dev/null +++ b/tests/wallet-v5-extensions.spec.ts @@ -0,0 +1,523 @@ +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/test-utils'; +import { compile } from '@ton/blueprint'; +import { getSecureRandomBytes, KeyPair, keyPairFromSeed, sign } from 'ton-crypto'; +import { bufferToBigInt, createMsgInternal, packAddress, validUntil } from './utils'; +import { + ActionAddExtension, + ActionRemoveExtension, + ActionSendMsg, ActionSetSignatureAuthAllowed, + packActionsList +} from './actions'; +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'; + +const WALLET_ID = new WalletId({ networkGlobalId: -239, workChain: 0, subwalletNumber: 0 }); + +describe('Wallet V5 extensions auth', () => { + let code: Cell; + + beforeAll(async () => { + code = await compile('wallet_v5'); + }); + + let blockchain: Blockchain; + let walletV5: SandboxContract; + let keypair: KeyPair; + let sender: Sender; + let seqno: number; + + let ggc: bigint = BigInt(0); + function accountForGas(transactions: BlockchainTransaction[]) { + transactions.forEach((tx) => { + ggc += ((tx?.description as TransactionDescriptionGeneric)?.computePhase as TransactionComputeVm)?.gasUsed ?? BigInt(0); + }) + } + + afterAll(async() => { + console.log("EXTENSIONS TESTS: Total gas " + ggc); + }); + + function createBody(actionsList: Cell) { + const payload = beginCell() + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + seqno++; + return beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + } + + beforeEach(async () => { + blockchain = await Blockchain.create(); + blockchain.libs = buildBlockchainLibraries([code]); + + keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + + walletV5 = blockchain.openContract( + WalletV5.createFromConfig( + { + signatureAllowed: true, + seqno: 0, + walletId: WALLET_ID.serialized, + publicKey: keypair.publicKey, + extensions: Dictionary.empty() + }, + LibraryDeployer.exportLibCode(code) + ) + ); + + const deployer = await blockchain.treasury('deployer'); + sender = deployer.getSender(); + + const deployResult = await walletV5.sendDeploy(sender, toNano('0.05')); + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: walletV5.address, + deploy: true, + success: true + }); + + seqno = 0; + }); + + it('Do a transfer form extension', async () => { + await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(packActionsList([new ActionAddExtension(sender.address!)])) + }); + + 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 actions = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + if (config.microscope) + blockchain.verbosity = { ...blockchain.verbosity, blockchainLogs: true, vmLogs: 'vm_logs_gas', debugLogs: true, print: true } + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actions + }); + + if (config.microscope) + blockchain.verbosity = { ...blockchain.verbosity, blockchainLogs: false, vmLogs: 'none', debugLogs: false, print: false } + + expect(receipt.transactions.length).toEqual(3); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt.transactions[2].totalFees.coins; + console.debug( + 'SINGLE INTERNAL TRANSFER FROM EXTENSION GAS USED:', + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).gasUsed + ); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + }); + + it('Do two transfers form extension and add other extension', async () => { + await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(packActionsList([new ActionAddExtension(sender.address!)])) + }); + + const testOtherExtension = Address.parse( + 'EQCNjd2CuSmxLpS5gUjyhRhVOsA1GaacsRPOBFV-WJRR_RmS' + ); + + const testReceiver1 = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue1 = toNano(0.001); + + const testReceiver2 = Address.parse('EQCgYDKqfTh7zVj9BQwOIPs4SuOhM7wnIjb6bdtM2AJf_Z9G'); + const forwardValue2 = toNano(0.0012); + + const receiver1BalanceBefore = (await blockchain.getContract(testReceiver1)).balance; + const receiver2BalanceBefore = (await blockchain.getContract(testReceiver2)).balance; + + const msg1 = createMsgInternal({ dest: testReceiver1, value: forwardValue1 }); + const msg2 = createMsgInternal({ dest: testReceiver2, value: forwardValue2 }); + + const actions = packActionsList([ + new ActionAddExtension(testOtherExtension), + new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg1), + new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg2) + ]); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actions + }); + + expect(receipt.transactions.length).toEqual(4); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver1, + value: forwardValue1 + }); + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver2, + value: forwardValue2 + }); + + const fee1 = receipt.transactions[2].totalFees.coins; + const fee2 = receipt.transactions[3].totalFees.coins; + + const receiver1BalanceAfter = (await blockchain.getContract(testReceiver1)).balance; + const receiver2BalanceAfter = (await blockchain.getContract(testReceiver2)).balance; + expect(receiver1BalanceAfter).toEqual(receiver1BalanceBefore + forwardValue1 - fee1); + expect(receiver2BalanceAfter).toEqual(receiver2BalanceBefore + forwardValue2 - fee2); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + + expect(extensionsDict.size).toEqual(2); + + expect(extensionsDict.get(packAddress(sender.address!))).toEqual( + -1n + ); + expect(extensionsDict.get(packAddress(testOtherExtension))).toEqual( + -1n + ); + }); + + it('Add and remove other extension form extension', async () => { + await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(packActionsList([new ActionAddExtension(sender.address!)])) + }); + + const otherExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + + const actions1 = packActionsList([new ActionAddExtension(otherExtension)]); + const receipt1 = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actions1 + }); + + expect(receipt1.transactions.length).toEqual(2); + accountForGas(receipt1.transactions); + + const extensionsDict1 = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict1.size).toEqual(2); + expect(extensionsDict1.get(packAddress(sender.address!))).toEqual( + -1n + ); + expect(extensionsDict1.get(packAddress(otherExtension))).toEqual( + -1n + ); + + const actions2 = packActionsList([new ActionRemoveExtension(otherExtension)]); + const receipt2 = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actions2 + }); + + expect(receipt2.transactions.length).toEqual(2); + accountForGas(receipt2.transactions); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict.size).toEqual(1); + expect(extensionsDict.get(packAddress(sender.address!))).toEqual( + -1n + ); + expect(extensionsDict.get(packAddress(otherExtension))).toEqual(undefined); + }); + + it('Extension removes itself', async () => { + await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(packActionsList([new ActionAddExtension(sender.address!)])) + }); + + const actions = packActionsList([new ActionRemoveExtension(sender.address!)]); + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actions + }); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict.size).toEqual(0); + expect(extensionsDict.get(packAddress(sender.address!))).toEqual(undefined); + }); + + it('Extension must be in the dict to pass authorization', async () => { + 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 actions = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actions + }); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + expect(receipt.transactions).not.toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + // 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). + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore); + }); + + it('Disallow signature auth and do a transfer from extension', async () => { + await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(packActionsList([ + 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; + + const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); + + const actions = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actions + }); + + expect(receipt.transactions.length).toEqual(3); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt.transactions[2].totalFees.coins; + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + }); + + it('Disallow signature auth; re-allow and self-delete by extension; do signed transfer', async () => { + await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(packActionsList([ + 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); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: packActionsList([ + new ActionSetSignatureAuthAllowed(true), + new ActionRemoveExtension(sender.address!) + ]) + }); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + const isSignatureAuthAllowed1 = await walletV5.getIsSignatureAuthAllowed(); + expect(isSignatureAuthAllowed1).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.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(actionsList2) + }); + + expect(receipt2.transactions.length).toEqual(3); + accountForGas(receipt2.transactions); + + expect(receipt2.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt2.transactions[2].totalFees.coins; + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + }); + + it('Add ext; disallow signature auth by ext; re-allow and self-delete by extension; do signed transfer', async () => { + await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(packActionsList([ + new ActionAddExtension(sender.address!) + ])) + }); + + const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + expect(isSignatureAuthAllowed).toEqual(-1); + + const receipt0 = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: packActionsList([ + new ActionSetSignatureAuthAllowed(false) + ]) + }); + + expect(receipt0.transactions.length).toEqual(2); + accountForGas(receipt0.transactions); + + expect( + ( + (receipt0.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + const isSignatureAuthAllowed0 = await walletV5.getIsSignatureAuthAllowed(); + expect(isSignatureAuthAllowed0).toEqual(0); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: packActionsList([ + new ActionSetSignatureAuthAllowed(true), + new ActionRemoveExtension(sender.address!) + ]) + }); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + const isSignatureAuthAllowed1 = await walletV5.getIsSignatureAuthAllowed(); + expect(isSignatureAuthAllowed1).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.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(actionsList2) + }); + + expect(receipt2.transactions.length).toEqual(3); + accountForGas(receipt2.transactions); + + expect(receipt2.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt2.transactions[2].totalFees.coins; + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + }); +}); diff --git a/tests/wallet-v5-external.spec.ts b/tests/wallet-v5-external.spec.ts new file mode 100644 index 00000000..8f4a9150 --- /dev/null +++ b/tests/wallet-v5-external.spec.ts @@ -0,0 +1,940 @@ +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/test-utils'; +import { compile } from '@ton/blueprint'; +import { getSecureRandomBytes, KeyPair, keyPairFromSeed, sign } from '@ton/crypto'; + +import { + bufferToBigInt, + createMsgInternal, + disableConsoleError, + packAddress, + validUntil +} from './utils'; +import { + ActionAddExtension, + ActionRemoveExtension, + ActionSendMsg, ActionSetSignatureAuthAllowed, + packActionsList +} from './actions'; +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'; + +const WALLET_ID = new WalletId({ networkGlobalId: -239, workChain: -1, subwalletNumber: 0 }); + +describe('Wallet V5 sign auth external', () => { + let code: Cell; + + beforeAll(async () => { + code = await compile('wallet_v5'); + }); + + let blockchain: Blockchain; + let walletV5: SandboxContract; + let keypair: KeyPair; + let sender: Sender; + let seqno: number; + + let ggc: bigint = BigInt(0); + function accountForGas(transactions: BlockchainTransaction[]) { + transactions.forEach((tx) => { + ggc += ((tx?.description as TransactionDescriptionGeneric)?.computePhase as TransactionComputeVm)?.gasUsed ?? BigInt(0); + }) + } + + afterAll(async() => { + console.log("EXTERNAL TESTS: Total gas " + ggc); + }); + + async function deployOtherWallet( + params?: Partial[0]> + ) { + const _keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + + const _walletV5 = blockchain.openContract( + WalletV5.createFromConfig( + { + signatureAllowed: true, + seqno: params?.seqno ?? 0, + walletId: params?.walletId ?? WALLET_ID.serialized, + publicKey: params?.publicKey ?? _keypair.publicKey, + extensions: params?.extensions ?? Dictionary.empty() + }, + LibraryDeployer.exportLibCode(code) + ) + ); + + const deployer = await blockchain.treasury('deployer'); + const _sender = deployer.getSender(); + + const deployResult = await _walletV5.sendDeploy(_sender, toNano('0.05')); + return { sender: _sender, walletV5: _walletV5, keypair: _keypair, deployer, deployResult }; + } + + function createBody(actionsList: Cell) { + const payload = beginCell() + .storeUint(Opcodes.auth_signed, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + seqno++; + return beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + } + + beforeEach(async () => { + blockchain = await Blockchain.create(); + blockchain.libs = buildBlockchainLibraries([code]); + + keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + + walletV5 = blockchain.openContract( + WalletV5.createFromConfig( + { + signatureAllowed: true, + seqno: 0, + walletId: WALLET_ID.serialized, + publicKey: keypair.publicKey, + extensions: Dictionary.empty() + }, + LibraryDeployer.exportLibCode(code) + ) + ); + + const deployer = await blockchain.treasury('deployer'); + sender = deployer.getSender(); + + const deployResult = await walletV5.sendDeploy(sender, toNano('0.05')); + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: walletV5.address, + deploy: true, + success: true + }); + + seqno = 0; + }); + + it('Send a simple transfer', async () => { + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue = toNano(0.001); + + const receiverBalanceBefore = (await blockchain.getContract(testReceiver)).balance; + + const sendTxMsg = beginCell() + .storeUint(0x10, 6) + .storeAddress(testReceiver) + .storeCoins(forwardValue) + .storeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .storeRef(beginCell().endCell()) + .endCell(); + + const sendTxactionAction = beginCell() + .storeUint(Opcodes.action_send_msg, 32) + .storeInt(SendMode.PAY_GAS_SEPARATELY | SendMode.IGNORE_ERRORS, 8) + .storeRef(sendTxMsg) + .endCell(); + + const actionsList = beginCell() + .storeMaybeRef( + beginCell() + .storeRef(beginCell().endCell()) // empty child - end of action list + .storeSlice(sendTxactionAction.beginParse()) + .endCell() + ) + .storeUint(0, 1) // no other_actions + .endCell(); + + if (config.microscope) + blockchain.verbosity = { ...blockchain.verbosity, blockchainLogs: true, vmLogs: 'vm_logs_gas', debugLogs: true, print: true } + + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + + if (config.microscope) + blockchain.verbosity = { ...blockchain.verbosity, blockchainLogs: false, vmLogs: 'none', debugLogs: false, print: false } + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt.transactions[1].totalFees.coins; + console.debug( + 'SINGLE EXTERNAL TRANSFER GAS USED:', + ( + (receipt.transactions[0].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).gasUsed + ); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + }); + + it('Add an extension', async () => { + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + + const addExtensionAction = beginCell() + .storeUint(Opcodes.action_extended_add_extension, 8) + .storeAddress(testExtension) + .endCell(); + + const actionsList = beginCell() + .storeUint(0, 1) // no c5 actions + .storeUint(1, 1) // have other actions + .storeSlice(addExtensionAction.beginParse()) + .endCell(); + + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + + expect(receipt.transactions.length).toEqual(1); + accountForGas(receipt.transactions); + + const extensions = await walletV5.getExtensions(); + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + extensions + ); + + expect(extensionsDict.size).toEqual(1); + + const dictValue = extensionsDict.get(packAddress(testExtension)); + expect(dictValue).toEqual(-1n); + }); + + it('Send single transfers to a deployed wallet', async () => { + const forwardValue = toNano(0.001); + + const { walletV5: receiver } = await deployOtherWallet(); + + const receiverBalanceBefore = (await blockchain.getContract(receiver.address)).balance; + + const msg = internal({ to: receiver.address, value: forwardValue }); + + const actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: receiver.address, + value: forwardValue + }); + + const fee = receipt.transactions[1].totalFees.coins; + + const receiverBalanceAfter = (await blockchain.getContract(receiver.address)).balance; + + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + }); + + it('Send two transfers', async () => { + const testReceiver1 = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue1 = toNano(0.001); + + const { walletV5: testReceiver2Wallet } = await deployOtherWallet(); + + const testReceiver2 = testReceiver2Wallet.address; + const forwardValue2 = toNano(0.002); + + const receiver1BalanceBefore = (await blockchain.getContract(testReceiver1)).balance; + const receiver2BalanceBefore = (await blockchain.getContract(testReceiver2)).balance; + + const msg1 = createMsgInternal({ dest: testReceiver1, value: forwardValue1 }); + const msg2 = createMsgInternal({ dest: testReceiver2, value: forwardValue2, bounce: true }); + + const actionsList = packActionsList([ + new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg1), + new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg2) + ]); + + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + + expect(receipt.transactions.length).toEqual(3); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver1, + value: forwardValue1 + }); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver2, + value: forwardValue2 + }); + + const fee1 = receipt.transactions[1].totalFees.coins; + const fee2 = receipt.transactions[2].totalFees.coins; + + const receiver1BalanceAfter = (await blockchain.getContract(testReceiver1)).balance; + const receiver2BalanceAfter = (await blockchain.getContract(testReceiver2)).balance; + + expect(receiver1BalanceAfter).toEqual(receiver1BalanceBefore + forwardValue1 - fee1); + expect(receiver2BalanceAfter).toEqual(receiver2BalanceBefore + forwardValue2 - fee2); + }); + + it('Add two extensions and do a transfer', async () => { + const testExtension1 = Address.parse('EQA2pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX5Bf'); + const testExtension2 = Address.parse('EQCgYDKqfTh7zVj9BQwOIPs4SuOhM7wnIjb6bdtM2AJf_Z9G'); + + 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 actionsList = packActionsList([ + new ActionAddExtension(testExtension1), + new ActionAddExtension(testExtension2), + new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg) + ]); + + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt.transactions[1].totalFees.coins; + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + + expect(extensionsDict.size).toEqual(2); + accountForGas(receipt.transactions); + + expect(extensionsDict.get(packAddress(testExtension1))).toEqual( + -1n + ); + expect(extensionsDict.get(packAddress(testExtension2))).toEqual( + -1n + ); + }); + + it('Remove extension', async () => { + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + + const actionsList1 = packActionsList([new ActionAddExtension(testExtension)]); + const receipt1 = await walletV5.sendExternalSignedMessage(createBody(actionsList1)); + const extensionsDict1 = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict1.size).toEqual(1); + expect(extensionsDict1.get(packAddress(testExtension))).toEqual( + -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(1), + await walletV5.getExtensions() + ); + + expect(extensionsDict2.size).toEqual(0); + expect(extensionsDict2.get(packAddress(testExtension))).toEqual(undefined); + + accountForGas(receipt1.transactions); + accountForGas(receipt2.transactions); + }); + + it('Should fail SetData action', async () => { + const cell = beginCell().endCell(); + + const actionsList = packActionsList([ + new ActionSetData(cell) + ]); + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + + expect( + ( + (receipt.transactions[0].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(141); + }); + + it('Should fail SetCode action', async () => { + const cell = beginCell().endCell(); + + const actionsList = packActionsList([ + new ActionSetCode(cell) + ]); + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + + expect( + ( + (receipt.transactions[0].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(9); + }); + + it('Should fail adding existing extension', async () => { + const testExtension = Address.parseRaw('0:' + '0'.repeat(64)); + + const actionsList1 = packActionsList([new ActionAddExtension(testExtension)]); + const receipt1 = await walletV5.sendExternalSignedMessage(createBody(actionsList1)); + accountForGas(receipt1.transactions); + const extensionsDict1 = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict1.size).toEqual(1); + expect(extensionsDict1.get(packAddress(testExtension))).toEqual( + -1n + ); + + const actionsList2 = packActionsList([new ActionAddExtension(testExtension)]); + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList2)); + + expect( + ( + (receipt.transactions[0].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(139); + + const extensionsDict2 = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict2.size).toEqual(1); + expect(extensionsDict2.get(packAddress(testExtension))).toEqual( + -1n + ); + }); + + it('Should fail removing not existing extension', async () => { + const testExtension = Address.parseRaw('0:' + '0'.repeat(64)); + + const actionsList = packActionsList([new ActionRemoveExtension(testExtension)]); + const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList)); + + expect( + ( + (receipt.transactions[0].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(140); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict.size).toEqual(0); + expect(extensionsDict.get(packAddress(testExtension))).toEqual(undefined); + }); + + it('Should fail if signature is invalid: wrong payload signed', async () => { + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue = toNano(0.001); + + const walletBalanceBefore = (await blockchain.getContract(walletV5.address)).balance; + const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); + const actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const vu = validUntil(); + + const payload = beginCell() + .storeUint(WALLET_ID.serialized, 32) + .storeUint(vu, 32) + .storeUint(seqno, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const fakePayload = beginCell() + .storeUint(Opcodes.auth_signed, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(vu, 32) + .storeUint(seqno + 1, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(fakePayload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + await disableConsoleError(() => + expect(walletV5.sendExternalSignedMessage(body)).rejects.toThrow() + ); + + const walletBalanceAfter = (await blockchain.getContract(walletV5.address)).balance; + + expect(walletBalanceBefore).toEqual(walletBalanceAfter); + }); + + it('Should fail if signature is invalid: wrong private key used', async () => { + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue = toNano(0.001); + + const walletBalanceBefore = (await blockchain.getContract(walletV5.address)).balance; + const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); + const actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const fakeKeypair = keyPairFromSeed(await getSecureRandomBytes(32)); + + const signature = sign(payload.hash(), fakeKeypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + await disableConsoleError(() => + expect(walletV5.sendExternalSignedMessage(body)).rejects.toThrow() + ); + + const walletBalanceAfter = (await blockchain.getContract(walletV5.address)).balance; + + expect(walletBalanceBefore).toEqual(walletBalanceAfter); + }); + + it('Should fail if seqno is invalid', async () => { + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue = toNano(0.001); + + const walletBalanceBefore = (await blockchain.getContract(walletV5.address)).balance; + const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); + const actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno + 1, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + await disableConsoleError(() => + expect(walletV5.sendExternalSignedMessage(body)).rejects.toThrow() + ); + + const walletBalanceAfter = (await blockchain.getContract(walletV5.address)).balance; + + expect(walletBalanceBefore).toEqual(walletBalanceAfter); + }); + + it('Should fail if valid_until is expired', async () => { + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue = toNano(0.001); + + const walletBalanceBefore = (await blockchain.getContract(walletV5.address)).balance; + const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); + const actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(Math.round(Date.now() / 1000) - 600, 32) + .storeUint(seqno, 32) + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + await disableConsoleError(() => + expect(walletV5.sendExternalSignedMessage(body)).rejects.toThrow() + ); + + const walletBalanceAfter = (await blockchain.getContract(walletV5.address)).balance; + + expect(walletBalanceBefore).toEqual(walletBalanceAfter); + }); + + it('Should fail if walletId id is wrong', async () => { + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue = toNano(0.001); + + const walletBalanceBefore = (await blockchain.getContract(walletV5.address)).balance; + const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); + const actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed, 32) + .storeUint(new WalletId({ ...WALLET_ID, subwalletNumber: 1 }).serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + await disableConsoleError(() => + expect(walletV5.sendExternalSignedMessage(body)).rejects.toThrow() + ); + + const walletBalanceAfter = (await blockchain.getContract(walletV5.address)).balance; + + expect(walletBalanceBefore).toEqual(walletBalanceAfter); + }); + + it('Should skip message if auth kind is wrong', async () => { + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue = toNano(0.001); + + const walletBalanceBefore = (await blockchain.getContract(walletV5.address)).balance; + const msg = createMsgInternal({ dest: testReceiver, value: forwardValue }); + const actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() // auth_signed_internal used instead of auth_signed + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + await disableConsoleError(() => + expect( + walletV5.sendExternal( + beginCell().storeSlice(body.beginParse()).endCell() + ) + ).rejects.toThrow() + ); + + const walletBalanceAfter = (await blockchain.getContract(walletV5.address)).balance; + + expect(walletBalanceBefore).toEqual(walletBalanceAfter); + }); + + it('Should skip message if auth kind not given', async () => { + const walletBalanceBefore = (await blockchain.getContract(walletV5.address)).balance; + + await disableConsoleError(() => + expect(walletV5.sendExternal(beginCell().endCell())).rejects.toThrow() + ); + + const walletBalanceAfter = (await blockchain.getContract(walletV5.address)).balance; + + expect(walletBalanceBefore).toEqual(walletBalanceAfter); + }); + + it('Should skip message with simple text comment', async () => { + const walletBalanceBefore = (await blockchain.getContract(walletV5.address)).balance; + + await disableConsoleError(() => + expect( + walletV5.sendExternal( + beginCell().storeUint(0, 32).storeStringTail('Hello world').endCell() + ) + ).rejects.toThrow() + ); + + const walletBalanceAfter = (await blockchain.getContract(walletV5.address)).balance; + + expect(walletBalanceBefore).toEqual(walletBalanceAfter); + }); + + it('only_extension_can_change_signature_mode', 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(146); // only_extension_can_change_signature_mode + + 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'); + // + // 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 new file mode 100644 index 00000000..1283249e --- /dev/null +++ b/tests/wallet-v5-get.spec.ts @@ -0,0 +1,157 @@ +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/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'; + +const WALLET_ID = new WalletId({ networkGlobalId: -239, workChain: 0, subwalletNumber: 0 }); + +describe('Wallet V5 get methods', () => { + let code: Cell; + + beforeAll(async () => { + code = await compile('wallet_v5'); + }); + + let blockchain: Blockchain; + let walletV5: SandboxContract; + let keypair: KeyPair; + let sender: Sender; + + async function deploy(params?: Partial[0]>) { + blockchain = await Blockchain.create(); + blockchain.libs = buildBlockchainLibraries([code]); + if (!params?.publicKey) { + keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + } + + walletV5 = blockchain.openContract( + WalletV5.createFromConfig( + { + signatureAllowed: true, + seqno: params?.seqno ?? 0, + walletId: params?.walletId ?? WALLET_ID.serialized, + publicKey: params?.publicKey ?? keypair.publicKey, + extensions: params?.extensions ?? Dictionary.empty() + }, + LibraryDeployer.exportLibCode(code) + ) + ); + + const deployer = await blockchain.treasury('deployer'); + sender = deployer.getSender(); + + const deployResult = await walletV5.sendDeploy(sender, toNano('0.05')); + return { deployer, deployResult }; + } + + beforeEach(async () => { + const { deployer, deployResult } = await deploy(); + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: walletV5.address, + deploy: true, + success: true + }); + }); + + it('Get seqno', async () => { + const expectedSeqno = 12345; + await deploy({ seqno: expectedSeqno }); + const actualSeqno = await walletV5.getSeqno(); + expect(expectedSeqno).toEqual(actualSeqno); + }); + + it('Get pubkey', async () => { + const actualPubkey = await walletV5.getPublicKey(); + expect(actualPubkey).toEqual(bufferToBigInt(keypair.publicKey)); + }); + + it('Get wallet id', async () => { + const expectedWalletId = new WalletId({ + networkGlobalId: -239, + workChain: 0, + subwalletNumber: 1 + }); + await deploy({ walletId: expectedWalletId.serialized }); + const actualWalletId = await walletV5.getWalletId(); + expect(expectedWalletId.subwalletNumber).toEqual(actualWalletId.subwalletNumber); + }); + + it('Get subwallet number', async () => { + const subwalletNumber = 12345; + const walletId = new WalletId({ + networkGlobalId: -239, + workChain: 0, + subwalletNumber + }); + await deploy({ walletId: walletId.serialized }); + const actualSubwalletNumber = (await walletV5.getWalletId()).subwalletNumber; + expect(subwalletNumber).toEqual(actualSubwalletNumber); + }); + + it('Default wallet id', async () => { + const walletId = new WalletId({ + networkGlobalId: -239, + workChain: 0, + subwalletNumber: 0, + walletVersion: 'v5' + }); + const defaultWalletId = new WalletId(); + + expect(walletId.serialized).toBe(defaultWalletId.serialized); + }); + + it('Get extensions dict', async () => { + const plugin1 = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const plugin2 = Address.parse('EQA2pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX5Bf'); + + const extensions: Dictionary = Dictionary.empty( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1) + ); + 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(1)) + .endCell(); + expect(actual?.equals(expected)).toBeTruthy(); + }); + + it('Get extensions array', async () => { + 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(1) + ); + 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(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 () => { + const actual = await walletV5.getExtensionsArray(); + expect(actual.length).toBe(0); + }); +}); diff --git a/tests/wallet-v5-internal.spec.ts b/tests/wallet-v5-internal.spec.ts new file mode 100644 index 00000000..963535f1 --- /dev/null +++ b/tests/wallet-v5-internal.spec.ts @@ -0,0 +1,1264 @@ +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/test-utils'; +import { compile } from '@ton/blueprint'; +import { getSecureRandomBytes, KeyPair, keyPairFromSeed, sign } from 'ton-crypto'; +import { bufferToBigInt, createMsgInternal, disableConsoleError, packAddress, validUntil } from './utils'; +import { + ActionAddExtension, + ActionRemoveExtension, + ActionSendMsg, ActionSetSignatureAuthAllowed, + packActionsList +} from './actions'; +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'; + +const WALLET_ID = new WalletId({ networkGlobalId: -239, workChain: 0, subwalletNumber: 0 }); + +describe('Wallet V5 sign auth internal', () => { + let code: Cell; + + beforeAll(async () => { + code = await compile('wallet_v5'); + }); + + let blockchain: Blockchain; + let walletV5: SandboxContract; + let keypair: KeyPair; + let sender: Sender; + let seqno: number; + + let ggc: bigint = BigInt(0); + function accountForGas(transactions: BlockchainTransaction[]) { + transactions.forEach((tx) => { + ggc += ((tx?.description as TransactionDescriptionGeneric)?.computePhase as TransactionComputeVm)?.gasUsed ?? BigInt(0); + }) + } + + afterAll(async() => { + console.log("INTERNAL TESTS: Total gas " + ggc); + }); + + async function deployOtherWallet( + params?: Partial[0]> + ) { + const _keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + + const _walletV5 = blockchain.openContract( + WalletV5.createFromConfig( + { + signatureAllowed: true, + seqno: params?.seqno ?? 0, + walletId: params?.walletId ?? WALLET_ID.serialized, + publicKey: params?.publicKey ?? _keypair.publicKey, + extensions: params?.extensions ?? Dictionary.empty() + }, + LibraryDeployer.exportLibCode(code) + ) + ); + + const deployer = await blockchain.treasury('deployer'); + const _sender = deployer.getSender(); + + const deployResult = await _walletV5.sendDeploy(_sender, toNano('0.05')); + return { sender: _sender, walletV5: _walletV5, keypair: _keypair, deployer, deployResult }; + } + + function createBody(actionsList: Cell) { + const payload = beginCell() + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + seqno++; + return beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + } + + beforeEach(async () => { + blockchain = await Blockchain.create(); + blockchain.libs = buildBlockchainLibraries([code]); + + keypair = keyPairFromSeed(await getSecureRandomBytes(32)); + + walletV5 = blockchain.openContract( + WalletV5.createFromConfig( + { + signatureAllowed: true, + seqno: 0, + walletId: WALLET_ID.serialized, + publicKey: keypair.publicKey, + extensions: Dictionary.empty() + }, + LibraryDeployer.exportLibCode(code) + ) + ); + + const deployer = await blockchain.treasury('deployer'); + sender = deployer.getSender(); + + const deployResult = await walletV5.sendDeploy(sender, toNano('0.05')); + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: walletV5.address, + deploy: true, + success: true + }); + + seqno = 0; + }); + + it('Send a simple transfer', async () => { + const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue = toNano(0.001); + + const receiverBalanceBefore = (await blockchain.getContract(testReceiver)).balance; + + const sendTxMsg = beginCell() + .storeUint(0x10, 6) + .storeAddress(testReceiver) + .storeCoins(forwardValue) + .storeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .storeRef(beginCell().endCell()) + .endCell(); + + const sendTxactionAction = beginCell() + .storeUint(Opcodes.action_send_msg, 32) + .storeInt(SendMode.PAY_GAS_SEPARATELY | SendMode.IGNORE_ERRORS, 8) + .storeRef(sendTxMsg) + .endCell(); + + const actionsList = beginCell() + .storeMaybeRef( + beginCell() + .storeRef(beginCell().endCell()) + .storeSlice(sendTxactionAction.beginParse()) + .endCell() + ) + .storeUint(0, 1) // no other actions + .endCell(); + + if (config.microscope) + blockchain.verbosity = { ...blockchain.verbosity, blockchainLogs: true, vmLogs: 'vm_logs_gas', debugLogs: true, print: true } + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList) + }); + + if (config.microscope) + blockchain.verbosity = { ...blockchain.verbosity, blockchainLogs: false, vmLogs: 'none', debugLogs: false, print: false } + + expect(receipt.transactions.length).toEqual(3); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt.transactions[2].totalFees.coins; + console.debug( + 'SINGLE INTERNAL TRANSFER GAS USED:', + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).gasUsed + ); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + }); + + it('Add an extension', async () => { + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + + const addExtensionAction = beginCell() + .storeUint(Opcodes.action_extended_add_extension, 8) + .storeAddress(testExtension) + .endCell(); + + const actionsList = beginCell() + .storeUint(0, 1) // no c5 actions + .storeUint(1, 1) + .storeSlice(addExtensionAction.beginParse()) + .endCell(); + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList) + }); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + const extensions = await walletV5.getExtensions(); + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + extensions + ); + + expect(extensionsDict.size).toEqual(1); + + const storedWC = extensionsDict.get(packAddress(testExtension)); + expect(storedWC).toEqual(-1n); + }); + + it('Send two transfers', async () => { + const testReceiver1 = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + const forwardValue1 = toNano(0.001); + + const { walletV5: testReceiver2Wallet } = await deployOtherWallet(); + + const testReceiver2 = testReceiver2Wallet.address; + const forwardValue2 = toNano(0.002); + + const receiver1BalanceBefore = (await blockchain.getContract(testReceiver1)).balance; + const receiver2BalanceBefore = (await blockchain.getContract(testReceiver2)).balance; + + const msg1 = createMsgInternal({ dest: testReceiver1, value: forwardValue1 }); + const msg2 = createMsgInternal({ dest: testReceiver2, value: forwardValue2, bounce: true }); + + const actionsList = packActionsList([ + new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg1), + new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg2) + ]); + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList) + }); + + expect(receipt.transactions.length).toEqual(4); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver1, + value: forwardValue1 + }); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver2, + value: forwardValue2 + }); + + const fee1 = receipt.transactions[2].totalFees.coins; + const fee2 = receipt.transactions[3].totalFees.coins; + + const receiver1BalanceAfter = (await blockchain.getContract(testReceiver1)).balance; + const receiver2BalanceAfter = (await blockchain.getContract(testReceiver2)).balance; + + expect(receiver1BalanceAfter).toEqual(receiver1BalanceBefore + forwardValue1 - fee1); + expect(receiver2BalanceAfter).toEqual(receiver2BalanceBefore + forwardValue2 - fee2); + }); + + it('Add two extensions and do a transfer', async () => { + const testExtension1 = Address.parse('EQA2pT4d8T7TyRsjW2BpGpGYga-lMA4JjQb4D2tc1PXMX5Bf'); + const testExtension2 = Address.parse('EQCgYDKqfTh7zVj9BQwOIPs4SuOhM7wnIjb6bdtM2AJf_Z9G'); + + 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 actionsList = packActionsList([ + new ActionAddExtension(testExtension1), + new ActionAddExtension(testExtension2), + new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg) + ]); + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList) + }); + + expect(receipt.transactions.length).toEqual(3); + accountForGas(receipt.transactions); + + expect(receipt.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt.transactions[2].totalFees.coins; + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore + forwardValue - fee); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + + expect(extensionsDict.size).toEqual(2); + + expect(extensionsDict.get(packAddress(testExtension1))).toEqual( + -1n + ); + expect(extensionsDict.get(packAddress(testExtension2))).toEqual( + -1n + ); + }); + + it('Remove extension', async () => { + const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y'); + + const actionsList1 = packActionsList([new ActionAddExtension(testExtension)]); + const receipt1 = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList1) + }); + const extensionsDict1 = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict1.size).toEqual(1); + expect(extensionsDict1.get(packAddress(testExtension))).toEqual( + -1n + ); + + const actionsList2 = packActionsList([new ActionRemoveExtension(testExtension)]); + const receipt2 = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList2) + }); + const extensionsDict2 = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + + expect(extensionsDict2.size).toEqual(0); + expect(extensionsDict2.get(packAddress(testExtension))).toEqual(undefined); + + accountForGas(receipt1.transactions); + accountForGas(receipt2.transactions); + }); + + it('Should fail SetData action', async () => { + const cell = beginCell().endCell(); + + const actionsList = packActionsList([ + new ActionSetData(cell) + ]); + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList) + }); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(141); + }); + + it('Should fail SetCode action', async () => { + const cell = beginCell().endCell(); + + const actionsList = packActionsList([ + new ActionSetCode(cell) + ]); + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList) + }); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(9); + }); + + it('Should fail adding existing extension', async () => { + const testExtension = Address.parseRaw('0:' + '0'.repeat(64)); + + const actionsList1 = packActionsList([new ActionAddExtension(testExtension)]); + await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList1) + }); + const extensionsDict1 = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict1.size).toEqual(1); + expect(extensionsDict1.get(packAddress(testExtension))).toEqual( + -1n + ); + + const actionsList2 = packActionsList([new ActionAddExtension(testExtension)]); + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList2) + }); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(139); + + const extensionsDict2 = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict2.size).toEqual(1); + expect(extensionsDict2.get(packAddress(testExtension))).toEqual( + -1n + ); + }); + + it('Should fail removing not existing extension', async () => { + const testExtension = Address.parseRaw('0:' + '0'.repeat(64)); + + const actionsList = packActionsList([new ActionRemoveExtension(testExtension)]); + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList) + }); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(140); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + expect(extensionsDict.size).toEqual(0); + expect(extensionsDict.get(packAddress(testExtension))).toEqual(undefined); + }); + + it('Should fail if signature is invalid: wrong payload signed', async () => { + 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 actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const vu = validUntil(); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(vu, 32) + .storeUint(seqno, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const fakePayload = beginCell() + .storeUint(WALLET_ID.serialized, 32) + .storeUint(vu, 32) + .storeUint(seqno + 1, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(fakePayload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body + }); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + expect(receipt.transactions).not.toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore); + }); + + it('Should fail if signature is invalid: wrong private key used', async () => { + 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 actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const fakeKeypair = keyPairFromSeed(await getSecureRandomBytes(32)); + + const signature = sign(payload.hash(), fakeKeypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body + }); + console.debug( + 'SINGLE WRONG SIGNATURE INTERNAL TRANSFER GAS USED:', + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).gasUsed + ); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + expect(receipt.transactions).not.toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore); + }); + + it('Should fail if seqno is invalid', async () => { + 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 actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno + 1, 32) // seqno + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body + }); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(133); + + expect(receipt.transactions).not.toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore); + }); + + it('Should fail if valid_until is expired', async () => { + 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 actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(Math.round(Date.now() / 1000) - 600, 32) + .storeUint(seqno, 32) + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body + }); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(136); + + expect(receipt.transactions).not.toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore); + }); + + it('Should fail if subwallet id is wrong', async () => { + 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 actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(new WalletId({ ...WALLET_ID, subwalletNumber: 1 }).serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + const receipt = await walletV5.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body + }); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(134); + + expect(receipt.transactions).not.toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore); + }); + + it('Should skip message if auth kind is wrong', async () => { + 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 actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]); + + const payload = beginCell() // auth_signed used instead of auth_signed_internal + .storeUint(Opcodes.auth_signed, 32) + .storeUint(WALLET_ID.serialized, 32) + .storeUint(validUntil(), 32) + .storeUint(seqno, 32) + .storeSlice(actionsList.beginParse()) + .endCell(); + + const signature = sign(payload.hash(), keypair.secretKey); + const body = beginCell() + .storeSlice(payload.beginParse()) + .storeUint(bufferToBigInt(signature), 512) + .endCell(); + + const receipt = await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: beginCell().storeSlice(body.beginParse()).endCell() + }); + + expect(receipt.transactions.length).toEqual(2); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + expect(receipt.transactions).not.toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore); + }); + + it('Should skip message if auth kind not given', async () => { + const receipt = await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: beginCell().endCell() + }); + + expect(receipt.transactions.length).toEqual(2); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + }); + + it('Should not revert on short "sint" messages', async () => { + const receipt = await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: beginCell().storeUint(Opcodes.auth_signed_internal, 32).endCell() + }); + + expect(receipt.transactions.length).toEqual(2); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + }); + + it('Should not revert on long incorrect "sint" messages', async () => { + const receipt = await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: beginCell() + .storeUint(Opcodes.auth_signed_internal, 32) + .storeUint(0, 657) + .endCell() + }); + + expect(receipt.transactions.length).toEqual(2); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + }); + + it('Should skip message with simple text comment', async () => { + const receipt = await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: beginCell().storeUint(0, 32).storeStringTail('Hello world').endCell() + }); + + expect(receipt.transactions.length).toEqual(2); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + console.debug( + 'SINGLE SIMPLE INTERNAL TRANSFER GAS USED:', + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).gasUsed + ); + }); + + it('Should skip message with longer text comment', async () => { + const receipt = await walletV5.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: beginCell().storeUint(0, 32).storeStringTail('Hello world'.repeat(20)).endCell() + }); + + expect(receipt.transactions.length).toEqual(2); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + console.debug( + 'SINGLE LONGER SIMPLE INTERNAL TRANSFER GAS USED:', + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).gasUsed + ); + }); + + it('Should fail disallowing signature auth with no exts', async () => { + const actionsList = packActionsList([ + new ActionAddExtension(sender.address!) + ]); + + 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: 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(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.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList + }); + + expect(receipt.transactions.length).toEqual(3); // sender_wallet -> wallet_v5 -> bounced + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).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([ + new ActionAddExtension(testExtension), + new ActionSetSignatureAuthAllowed(false) + ]); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList + }); + + expect(receipt.transactions.length).toEqual(2); + + accountForGas(receipt.transactions); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + + expect(extensionsDict.size).toEqual(2); + + expect(extensionsDict.get(packAddress(testExtension))).toEqual( + -1n + ); + + expect( + ( + (receipt.transactions[1].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 () => { + 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.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList + }); + + expect(receipt.transactions.length).toEqual(2); + + accountForGas(receipt.transactions); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + + expect(extensionsDict.size).toEqual(2); + + expect(extensionsDict.get(packAddress(testExtension))).toEqual( + -1n + ); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(0); + + const isSignatureAuthAllowed = await walletV5.getIsSignatureAuthAllowed(); + expect(isSignatureAuthAllowed).toEqual(-1); + + const actionsList2 = packActionsList([ + new ActionSetSignatureAuthAllowed(false) + ]); + + const receipt2 = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList2 + }); + + expect(receipt2.transactions.length).toEqual(2); + + accountForGas(receipt2.transactions); + + expect( + ( + (receipt2.transactions[1].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 () => { + 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 ActionSetSignatureAuthAllowed(true), + new ActionRemoveExtension(testExtension), + ]); + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList + }); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + expect( + ( + (receipt.transactions[1].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.sendInternal(sender, { + sendMode: SendMode.PAY_GAS_SEPARATELY, + value: toNano(0.1), + body: createBody(actionsList2) + }); + + expect(receipt2.transactions.length).toEqual(3); + accountForGas(receipt2.transactions); + + expect(receipt2.transactions).toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const fee = receipt2.transactions[2].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 () => { + 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(sender.address!) + ]); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList + }); + + expect(receipt.transactions.length).toEqual(3); // sender_wallet -> wallet_v5 -> bounced + accountForGas(receipt.transactions); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).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([ + new ActionAddExtension(testExtension), + new ActionSetSignatureAuthAllowed(false), + new ActionSetSignatureAuthAllowed(false) + ]); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList + }); + + expect(receipt.transactions.length).toEqual(3); // sender_wallet -> wallet_v5 -> bounced + accountForGas(receipt.transactions); + + expect( + ( + (receipt.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).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([ + new ActionAddExtension(testExtension), + new ActionSetSignatureAuthAllowed(false) + ]); + + const receipt = await walletV5.sendInternalMessageFromExtension(sender, { + value: toNano('0.1'), + body: actionsList + }); + + expect(receipt.transactions.length).toEqual(2); + accountForGas(receipt.transactions); + + const extensionsDict = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + await walletV5.getExtensions() + ); + + expect(extensionsDict.size).toEqual(2); + + expect(extensionsDict.get(packAddress(testExtension))).toEqual( + -1n + ); + + expect( + ( + (receipt.transactions[1].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); + + 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.sendInternalSignedMessage(sender, { + value: toNano(0.1), + body: createBody(actionsList2) + }); + + expect( + ( + (receipt2.transactions[1].description as TransactionDescriptionGeneric) + .computePhase as TransactionComputeVm + ).exitCode + ).toEqual(132); + + expect(receipt2.transactions).not.toHaveTransaction({ + from: walletV5.address, + to: testReceiver, + value: forwardValue + }); + + const receiverBalanceAfter = (await blockchain.getContract(testReceiver)).balance; + + expect(receiverBalanceAfter).toEqual(receiverBalanceBefore); + }); +}); diff --git a/types.tlb b/types.tlb new file mode 100644 index 00000000..45eebd0b --- /dev/null +++ b/types.tlb @@ -0,0 +1,29 @@ +// Standard actions from block.tlb: +out_list_empty$_ = OutList 0; +out_list$_ {n:#} prev:^(OutList n) action:OutAction = OutList (n + 1); +action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any) = OutAction; + +// Extended actions in W5: +action_list_basic$_ {n:#} actions:^(OutList n) = ActionList n 0; +action_list_extended$_ {m:#} {n:#} action:ExtendedAction prev:^(ActionList n m) = ActionList n (m+1); + +action_add_ext#02 addr:MsgAddressInt = ExtendedAction; +action_delete_ext#03 addr:MsgAddressInt = ExtendedAction; +action_set_signature_auth_allowed#04 allowed:(## 1) = ExtendedAction; + +signed_request$_ // 32 (opcode from outer) + wallet_id: # // 32 + valid_until: # // 32 + msg_seqno: # // 32 + inner: InnerRequest // + signature: bits512 // 512 += SignedRequest; // Total: 688 .. 976 + ^Cell + +internal_signed#73696e74 signed:SignedRequest = InternalMsgBody; +internal_extension#6578746e query_id:(## 64) inner:InnerRequest = InternalMsgBody; +external_signed#7369676e signed:SignedRequest = ExternalMsgBody; + +actions$_ out_actions:(Maybe OutList) has_other_actions:(## 1) {m:#} {n:#} other_actions:(ActionList n m) = InnerRequest; + +// Contract state +contract_state$_ is_signature_allowed:(## 1) seqno:# wallet_id:(## 32) public_key:(## 256) extensions_dict:(HashmapE 256 int1) = ContractState; diff --git a/wrappers/Errors.ts b/wrappers/Errors.ts new file mode 100644 index 00000000..b1c3b93e --- /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/WalletV5.ts b/wrappers/WalletV5.ts deleted file mode 100644 index fe97c549..00000000 --- a/wrappers/WalletV5.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Dictionary, Sender, SendMode } from 'ton-core'; - -export type WalletV5Config = { - seqno: number; - subwallet: number; - publicKey: Buffer; - extensions: Dictionary; -}; - - -export function walletV5ConfigToCell(config: WalletV5Config): Cell { - return beginCell() - .storeUint(config.seqno, 32) - .storeUint(config.subwallet, 32) - .storeBuffer(config.publicKey, 32) - .storeDict(config.extensions) - .endCell(); -} - -export const Opcodes = { - add: 0x1c40db9f, - remove: 0x5eaef4a4, - extn: 0x6578746E, - sign: 0x7369676E, -}; - -export class WalletV5 implements Contract { - constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} - - static createFromAddress(address: Address) { - return new WalletV5(address); - } - - static createFromConfig(config: WalletV5Config, code: Cell, workchain = 0) { - const data = walletV5ConfigToCell(config); - const init = { code, data }; - return new WalletV5(contractAddress(workchain, init), init); - } - - async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { - await provider.internal(via, { - value, - sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell().endCell(), - }); - } - - async sendInternalSignedMessage( - provider: ContractProvider, - via: Sender, - opts: { - value: bigint, - body: Cell; - } - ) { - await provider.internal(via, { - value: opts.value, - sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell() - .storeUint(Opcodes.sign, 32) - .storeRef(opts.body) - .endCell(), - }); - } - - async getPublicKey(provider: ContractProvider) { - const result = await provider.get('get_public_key', []); - return result.stack.readNumber(); - } - - async getSeqNo(provider: ContractProvider) { - const result = await provider.get('seqno', []); - return result.stack.readNumber(); - } -} diff --git a/wrappers/Wallet_V5_1.compile.ts b/wrappers/Wallet_V5_1.compile.ts deleted file mode 100644 index 60fd1242..00000000 --- a/wrappers/Wallet_V5_1.compile.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { CompilerConfig } from '@ton-community/blueprint'; - -export const compile: CompilerConfig = { - lang: 'func', - targets: ['contracts/wallet_v5_1.fc'], -}; diff --git a/wrappers/Wallet_V5_2.compile.ts b/wrappers/Wallet_V5_2.compile.ts deleted file mode 100644 index e96b3bd4..00000000 --- a/wrappers/Wallet_V5_2.compile.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { CompilerConfig } from '@ton-community/blueprint'; - -export const compile: CompilerConfig = { - lang: 'func', - targets: ['contracts/wallet_v5_2.fc'], -}; diff --git a/wrappers/library-deployer.compile.ts b/wrappers/library-deployer.compile.ts new file mode 100644 index 00000000..5963a5ad --- /dev/null +++ b/wrappers/library-deployer.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['contracts/library-deployer.fc'] +}; diff --git a/wrappers/library-deployer.ts b/wrappers/library-deployer.ts new file mode 100644 index 00000000..cb3c15c6 --- /dev/null +++ b/wrappers/library-deployer.ts @@ -0,0 +1,62 @@ +import { + Address, + beginCell, + BitBuilder, + Cell, + Contract, + contractAddress, + ContractProvider, + Dictionary, + DictionaryValue, + Sender, + SendMode, + SimpleLibrary +} from '@ton/core'; +import { SimpleLibraryValue } from '@ton/core/dist/types/SimpleLibrary'; + +export type LibraryDeployerConfig = { + libraryCode: Cell; +}; + +export function buildBlockchainLibraries(libs: Cell[]): Cell { + const libraries = Dictionary.empty(Dictionary.Keys.BigUint(256), Dictionary.Values.Cell()); + libs.forEach(lib => libraries.set(BigInt('0x' + lib.hash().toString('hex')), lib)); + + return beginCell().storeDictDirect(libraries).endCell(); +} + +export function buildLibraryStateInit(library: SimpleLibrary): Cell { + const libraries = Dictionary.empty( + Dictionary.Keys.BigUint(256), + SimpleLibraryValue as unknown as DictionaryValue + ); + libraries.set(BigInt('0x' + library.root.hash().toString('hex')), library); + + return beginCell().storeDictDirect(libraries).endCell(); +} + +export class LibraryDeployer implements Contract { + static exportLibCode(code: Cell) { + const bits = new BitBuilder(); + bits.writeUint(2, 8); + bits.writeUint(BigInt('0x' + code.hash().toString('hex')), 256); + + return new Cell({ exotic: true, bits: bits.build() }); + } + + constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + + static createFromConfig(config: LibraryDeployerConfig, code: Cell, workchain = -1) { + const data = config.libraryCode; + const init = { code, data }; + return new LibraryDeployer(contractAddress(workchain, init), init); + } + + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().endCell() + }); + } +} diff --git a/wrappers/wallet-v5-test.ts b/wrappers/wallet-v5-test.ts new file mode 100644 index 00000000..2d5c09ed --- /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 new file mode 100644 index 00000000..27963df1 --- /dev/null +++ b/wrappers/wallet-v5.ts @@ -0,0 +1,245 @@ +import { + Address, + beginCell, + BitBuilder, + BitReader, + BitString, + Cell, + Contract, + contractAddress, + ContractProvider, + Dictionary, + MessageRelaxed, + storeOutList, + OutAction, + Sender, + 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; + extensions: Dictionary; +}; + +export function walletV5ConfigToCell(config: WalletV5Config): Cell { + return beginCell() + .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(1)) + .endCell(); +} + +export const Opcodes = { + action_send_msg: 0x0ec3c86d, + action_set_code: 0xad4de08e, + action_extended_set_data: 0x1ff8ea0b, + 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 +}; + +export class WalletId { + static readonly versionsSerialisation: Record = { + v5: 0 + }; + + 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'; + + // -239 is mainnet, -3 is testnet + readonly networkGlobalId: number; + + readonly workChain: number; + + readonly subwalletNumber: number; + + readonly serialized: bigint; + + constructor(args?: { + networkGlobalId?: number; + workChain?: number; + subwalletNumber?: number; + walletVersion?: 'v5'; + }) { + this.networkGlobalId = args?.networkGlobalId ?? -239; + this.workChain = args?.workChain ?? 0; + this.subwalletNumber = args?.subwalletNumber ?? 0; + this.walletVersion = args?.walletVersion ?? 'v5'; + + // 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 = BigInt(this.subwalletNumber) // bufferToBigInt(bitBuilder.buffer()); + } +} + +export class WalletV5 implements Contract { + constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + + static createFromAddress(address: Address) { + return new WalletV5(address); + } + + static createFromConfig(config: WalletV5Config, code: Cell, workchain = 0) { + const data = walletV5ConfigToCell(config); + const init = { code, data }; + return new WalletV5(contractAddress(workchain, init), init); + } + + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().endCell() + }); + } + + async sendInternalSignedMessage( + provider: ContractProvider, + via: Sender, + opts: { + value: bigint; + body: Cell; + } + ) { + await provider.internal(via, { + value: opts.value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell() + // .storeUint(Opcodes.auth_signed_internal, 32) // Is signed inside message + .storeSlice(opts.body.beginParse()) + .endCell() + }); + } + + async sendInternalMessageFromExtension( + provider: ContractProvider, + via: Sender, + opts: { + value: bigint; + body: Cell; + } + ) { + await provider.internal(via, { + value: opts.value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell() + .storeUint(Opcodes.auth_extension, 32) + .storeUint(0, 64) // query id + .storeSlice(opts.body.beginParse()) + .endCell() + }); + } + + async sendInternal( + provider: ContractProvider, + via: Sender, + opts: Parameters[1] + ) { + await provider.internal(via, opts); + } + + async sendExternalSignedMessage(provider: ContractProvider, body: Cell) { + await provider.external(body); + } + + async sendExternal(provider: ContractProvider, body: Cell) { + await provider.external(body); + } + + async getPublicKey(provider: ContractProvider) { + const result = await provider.get('get_public_key', []); + return result.stack.readBigNumber(); + } + + async getSeqno(provider: ContractProvider) { + const state = await provider.getState(); + if (state.state.type === 'active') { + let res = await provider.get('seqno', []); + return res.stack.readNumber(); + } else { + return 0; + } + } + + async getIsSignatureAuthAllowed(provider: ContractProvider) { + const state = await provider.getState(); + if (state.state.type === 'active') { + let res = await provider.get('is_signature_allowed', []); + return res.stack.readNumber(); + } else { + return -1; + } + } + + async getWalletId(provider: ContractProvider) { + const result = await provider.get('get_subwallet_id', []); + return WalletId.deserialize(result.stack.readBigNumber()); + } + + async getExtensions(provider: ContractProvider) { + const result = await provider.get('get_extensions', []); + return result.stack.readCellOpt(); + } + + async getExtensionsArray(provider: ContractProvider) { + const extensions = await this.getExtensions(provider); + if (!extensions) { + return []; + } + + const dict: Dictionary = Dictionary.loadDirect( + Dictionary.Keys.BigUint(256), + Dictionary.Values.BigInt(1), + extensions + ); + + return dict.keys().map(key => { + 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 new file mode 100644 index 00000000..e9b50f69 --- /dev/null +++ b/wrappers/wallet_v5.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['contracts/wallet_v5.fc'] +};