diff --git a/CHANGELOG.md b/CHANGELOG.md index b5321445..24940b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. +## [1.18.0] – 2021-06-26 + +### New +- Iterators in `net` module: robust way to iterate blockchain items (blocks, transactions) + in specified range. See documentation for `create_block_iterator` , `create_transaction_iterator`, + `resume_block_iterator`, `resume_transaction_iterator`, `iterator_next`, `iterator_remove` + functions. +- Library adds `http://` protocol to endpoints `localhost`, `127.0.0.1`, `0.0.0.0` if protocol + isn't specified in config. +- **Debot module**: + - added tests for Json interface. ## [1.17.0] – 2021-06-21 ### New diff --git a/lerna.json b/lerna.json index 08597787..444f7dba 100644 --- a/lerna.json +++ b/lerna.json @@ -3,7 +3,7 @@ "packages": [ "packages/*" ], - "version": "1.17.0", + "version": "1.18.0", "command": { "version": { "message": "Release" diff --git a/packages/core/package.json b/packages/core/package.json index 0979b767..82f9f1bb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@tonclient/core", - "version": "1.17.0", + "version": "1.18.0", "description": "TON Client for Java Script", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/core/src/bin.ts b/packages/core/src/bin.ts index 303cbadf..634be48b 100644 --- a/packages/core/src/bin.ts +++ b/packages/core/src/bin.ts @@ -134,7 +134,12 @@ export class CommonBinaryBridge implements BinaryBridge { this.checkResponseHandler(); const paramsJson = (functionParams === undefined) || (functionParams === null) ? "" - : JSON.stringify(functionParams); + : JSON.stringify(functionParams, (_, value) => + typeof value === 'bigint' + ? (value < Number.MAX_SAFE_INTEGER && value > Number.MIN_SAFE_INTEGER + ? Number(value) + : value) + : value); lib.sendRequest(context, requestId, functionName, paramsJson); }); } diff --git a/packages/core/src/modules.ts b/packages/core/src/modules.ts index f7233a47..cff60cbe 100644 --- a/packages/core/src/modules.ts +++ b/packages/core/src/modules.ts @@ -48,7 +48,8 @@ export enum ClientErrorCode { CanNotParseRequestResult = 30, UnexpectedCallbackResponse = 31, CanNotParseNumber = 32, - InternalError = 33 + InternalError = 33, + InvalidHandle = 34 } export type ClientError = { @@ -4813,6 +4814,242 @@ export type ResultOfQueryTransactionTree = { transactions: TransactionNode[] } +export type ParamsOfCreateBlockIterator = { + + /** + * Starting time to iterate from. + * + * @remarks + * If the application specifies this parameter then the iteration + * includes blocks with `gen_utime` >= `start_time`. + * Otherwise the iteration starts from zero state. + * + * Must be specified in seconds. + */ + start_time?: number, + + /** + * Optional end time to iterate for. + * + * @remarks + * If the application specifies this parameter then the iteration + * includes blocks with `gen_utime` < `end_time`. + * Otherwise the iteration never stops. + * + * Must be specified in seconds. + */ + end_time?: number, + + /** + * Shard prefix filter. + * + * @remarks + * If the application specifies this parameter and it is not the empty array + * then the iteration will include items related to accounts that belongs to + * the specified shard prefixes. + * Shard prefix must be represented as a string "workchain:prefix". + * Where `workchain` is a signed integer and the `prefix` if a hexadecimal + * representation if the 64-bit unsigned integer with tagged shard prefix. + * For example: "0:3800000000000000". + */ + shard_filter?: string[], + + /** + * Projection (result) string. + * + * @remarks + * List of the fields that must be returned for iterated items. + * This field is the same as the `result` parameter of + * the `query_collection` function. + * Note that iterated items can contains additional fields that are + * not requested in the `result`. + */ + result?: string +} + +export type RegisteredIterator = { + + /** + * Iterator handle. + * + * @remarks + * Must be removed using `remove_iterator` + * when it is no more needed for the application. + */ + handle: number +} + +export type ParamsOfResumeBlockIterator = { + + /** + * Iterator state from which to resume. + * + * @remarks + * Same as value returned from `iterator_next`. + */ + resume_state: any +} + +export type ParamsOfCreateTransactionIterator = { + + /** + * Starting time to iterate from. + * + * @remarks + * If the application specifies this parameter then the iteration + * includes blocks with `gen_utime` >= `start_time`. + * Otherwise the iteration starts from zero state. + * + * Must be specified in seconds. + */ + start_time?: number, + + /** + * Optional end time to iterate for. + * + * @remarks + * If the application specifies this parameter then the iteration + * includes blocks with `gen_utime` < `end_time`. + * Otherwise the iteration never stops. + * + * Must be specified in seconds. + */ + end_time?: number, + + /** + * Shard prefix filters. + * + * @remarks + * If the application specifies this parameter and it is not an empty array + * then the iteration will include items related to accounts that belongs to + * the specified shard prefixes. + * Shard prefix must be represented as a string "workchain:prefix". + * Where `workchain` is a signed integer and the `prefix` if a hexadecimal + * representation if the 64-bit unsigned integer with tagged shard prefix. + * For example: "0:3800000000000000". + * Account address conforms to the shard filter if + * it belongs to the filter workchain and the first bits of address match to + * the shard prefix. Only transactions with suitable account addresses are iterated. + */ + shard_filter?: string[], + + /** + * Account address filter. + * + * @remarks + * Application can specify the list of accounts for which + * it wants to iterate transactions. + * + * If this parameter is missing or an empty list then the library iterates + * transactions for all accounts that pass the shard filter. + * + * Note that the library doesn't detect conflicts between the account filter and the shard filter + * if both are specified. + * So it is an application responsibility to specify the correct filter combination. + */ + accounts_filter?: string[], + + /** + * Projection (result) string. + * + * @remarks + * List of the fields that must be returned for iterated items. + * This field is the same as the `result` parameter of + * the `query_collection` function. + * Note that iterated items can contain additional fields that are + * not requested in the `result`. + */ + result?: string, + + /** + * Include `transfers` field in iterated transactions. + * + * @remarks + * If this parameter is `true` then each transaction contains field + * `transfers` with list of transfer. See more about this structure in function description. + */ + include_transfers?: boolean +} + +export type ParamsOfResumeTransactionIterator = { + + /** + * Iterator state from which to resume. + * + * @remarks + * Same as value returned from `iterator_next`. + */ + resume_state: any, + + /** + * Account address filter. + * + * @remarks + * Application can specify the list of accounts for which + * it wants to iterate transactions. + * + * If this parameter is missing or an empty list then the library iterates + * transactions for all accounts that passes the shard filter. + * + * Note that the library doesn't detect conflicts between the account filter and the shard filter + * if both are specified. + * So it is the application's responsibility to specify the correct filter combination. + */ + accounts_filter?: string[] +} + +export type ParamsOfIteratorNext = { + + /** + * Iterator handle + */ + iterator: number, + + /** + * Maximum count of the returned items. + * + * @remarks + * If value is missing or is less than 1 the library uses 1. + */ + limit?: number, + + /** + * Indicates that function must return the iterator state that can be used for resuming iteration. + */ + return_resume_state?: boolean +} + +export type ResultOfIteratorNext = { + + /** + * Next available items. + * + * @remarks + * Note that `iterator_next` can return an empty items and `has_more` equals to `true`. + * In this case the application have to continue iteration. + * Such situation can take place when there is no data yet but + * the requested `end_time` is not reached. + */ + items: any[], + + /** + * Indicates that there are more available items in iterated range. + */ + has_more: boolean, + + /** + * Optional iterator state that can be used for resuming iteration. + * + * @remarks + * This field is returned only if the `return_resume_state` parameter + * is specified. + * + * Note that `resume_state` corresponds to the iteration position + * after the returned items. + */ + resume_state?: any +} + /** * Network access. */ @@ -5047,6 +5284,194 @@ export class NetModule { query_transaction_tree(params: ParamsOfQueryTransactionTree): Promise { return this.client.request('net.query_transaction_tree', params); } + + /** + * Creates block iterator. + * + * @remarks + * Block iterator uses robust iteration methods that guaranties that every + * block in the specified range isn't missed or iterated twice. + * + * Iterated range can be reduced with some filters: + * - `start_time` – the bottom time range. Only blocks with `gen_utime` + * more or equal to this value is iterated. If this parameter is omitted then there is + * no bottom time edge, so all blocks since zero state is iterated. + * - `end_time` – the upper time range. Only blocks with `gen_utime` + * less then this value is iterated. If this parameter is omitted then there is + * no upper time edge, so iterator never finishes. + * - `shard_filter` – workchains and shard prefixes that reduce the set of interesting + * blocks. Block conforms to the shard filter if it belongs to the filter workchain + * and the first bits of block's `shard` fields matches to the shard prefix. + * Only blocks with suitable shard are iterated. + * + * Items iterated is a JSON objects with block data. The minimal set of returned + * fields is: + * + * id + * gen_utime + * workchain_id + * shard + * after_split + * after_merge + * prev_ref { + * root_hash + * } + * prev_alt_ref { + * root_hash + * } + * + * Application can request additional fields in the `result` parameter. + * + * Application should call the `remove_iterator` when iterator is no longer required. + * + * @param {ParamsOfCreateBlockIterator} params + * @returns RegisteredIterator + */ + create_block_iterator(params: ParamsOfCreateBlockIterator): Promise { + return this.client.request('net.create_block_iterator', params); + } + + /** + * Resumes block iterator. + * + * @remarks + * The iterator stays exactly at the same position where the `resume_state` was catched. + * + * Application should call the `remove_iterator` when iterator is no longer required. + * + * @param {ParamsOfResumeBlockIterator} params + * @returns RegisteredIterator + */ + resume_block_iterator(params: ParamsOfResumeBlockIterator): Promise { + return this.client.request('net.resume_block_iterator', params); + } + + /** + * Creates transaction iterator. + * + * @remarks + * Transaction iterator uses robust iteration methods that guaranty that every + * transaction in the specified range isn't missed or iterated twice. + * + * Iterated range can be reduced with some filters: + * - `start_time` – the bottom time range. Only transactions with `now` + * more or equal to this value are iterated. If this parameter is omitted then there is + * no bottom time edge, so all the transactions since zero state are iterated. + * - `end_time` – the upper time range. Only transactions with `now` + * less then this value are iterated. If this parameter is omitted then there is + * no upper time edge, so iterator never finishes. + * - `shard_filter` – workchains and shard prefixes that reduce the set of interesting + * accounts. Account address conforms to the shard filter if + * it belongs to the filter workchain and the first bits of address match to + * the shard prefix. Only transactions with suitable account addresses are iterated. + * - `accounts_filter` – set of account addresses whose transactions must be iterated. + * Note that accounts filter can conflict with shard filter so application must combine + * these filters carefully. + * + * Iterated item is a JSON objects with transaction data. The minimal set of returned + * fields is: + * + * id + * account_addr + * now + * balance_delta(format:DEC) + * bounce { bounce_type } + * in_message { + * id + * value(format:DEC) + * msg_type + * src + * } + * out_messages { + * id + * value(format:DEC) + * msg_type + * dst + * } + * + * Application can request an additional fields in the `result` parameter. + * + * Another parameter that affects on the returned fields is the `include_transfers`. + * When this parameter is `true` the iterator computes and adds `transfer` field containing + * list of the useful `TransactionTransfer` objects. + * Each transfer is calculated from the particular message related to the transaction + * and has the following structure: + * - message – source message identifier. + * - isBounced – indicates that the transaction is bounced, which means the value will be returned back to the sender. + * - isDeposit – indicates that this transfer is the deposit (true) or withdraw (false). + * - counterparty – account address of the transfer source or destination depending on `isDeposit`. + * - value – amount of nano tokens transfered. The value is represented as a decimal string + * because the actual value can be more precise than the JSON number can represent. Application + * must use this string carefully – conversion to number can follow to loose of precision. + * + * Application should call the `remove_iterator` when iterator is no longer required. + * + * @param {ParamsOfCreateTransactionIterator} params + * @returns RegisteredIterator + */ + create_transaction_iterator(params: ParamsOfCreateTransactionIterator): Promise { + return this.client.request('net.create_transaction_iterator', params); + } + + /** + * Resumes transaction iterator. + * + * @remarks + * The iterator stays exactly at the same position where the `resume_state` was catched. + * Note that `resume_state` doesn't store the account filter. If the application requires + * to use the same account filter as it was when the iterator was created then the application + * must pass the account filter again in `accounts_filter` parameter. + * + * Application should call the `remove_iterator` when iterator is no longer required. + * + * @param {ParamsOfResumeTransactionIterator} params + * @returns RegisteredIterator + */ + resume_transaction_iterator(params: ParamsOfResumeTransactionIterator): Promise { + return this.client.request('net.resume_transaction_iterator', params); + } + + /** + * Returns next available items. + * + * @remarks + * In addition to available items this function returns the `has_more` flag + * indicating that the iterator isn't reach the end of the iterated range yet. + * + * This function can return the empty list of available items but + * indicates that there are more items is available. + * This situation appears when the iterator doesn't reach iterated range + * but database doesn't contains available items yet. + * + * If application requests resume state in `return_resume_state` parameter + * then this function returns `resume_state` that can be used later to + * resume the iteration from the position after returned items. + * + * The structure of the items returned depends on the iterator used. + * See the description to the appropriated iterator creation function. + * + * @param {ParamsOfIteratorNext} params + * @returns ResultOfIteratorNext + */ + iterator_next(params: ParamsOfIteratorNext): Promise { + return this.client.request('net.iterator_next', params); + } + + /** + * Removes an iterator + * + * @remarks + * Frees all resources allocated in library to serve iterator. + * + * Application always should call the `remove_iterator` when iterator + * is no longer required. + * + * @param {RegisteredIterator} params + * @returns + */ + remove_iterator(params: RegisteredIterator): Promise { + return this.client.request('net.remove_iterator', params); + } } // debot module diff --git a/packages/lib-node/package.json b/packages/lib-node/package.json index 5e15941d..60397c70 100644 --- a/packages/lib-node/package.json +++ b/packages/lib-node/package.json @@ -1,6 +1,6 @@ { "name": "@tonclient/lib-node", - "version": "1.17.0", + "version": "1.18.0", "description": "TON Client NodeJs AddOn", "repository": { "type": "git", diff --git a/packages/lib-react-native/package.json b/packages/lib-react-native/package.json index fccb14e7..1a3f9260 100644 --- a/packages/lib-react-native/package.json +++ b/packages/lib-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@tonclient/lib-react-native", - "version": "1.17.0", + "version": "1.18.0", "description": "TON Client React Native Module", "main": "index.js", "repository": { diff --git a/packages/lib-web/package.json b/packages/lib-web/package.json index 92fa4dff..7c8756e0 100644 --- a/packages/lib-web/package.json +++ b/packages/lib-web/package.json @@ -1,6 +1,6 @@ { "name": "@tonclient/lib-web", - "version": "1.17.0", + "version": "1.18.0", "description": "TON Client WASM module for browsers", "main": "index.js", "repository": { diff --git a/packages/tests-node/package.json b/packages/tests-node/package.json index b0fc5fe6..08b118c8 100644 --- a/packages/tests-node/package.json +++ b/packages/tests-node/package.json @@ -1,6 +1,6 @@ { "name": "@tonclient/tests-node", - "version": "1.17.0", + "version": "1.18.0", "private": true, "description": "TON Client Tests runner on NodeJs", "main": "index.js", diff --git a/packages/tests-react-native/package.json b/packages/tests-react-native/package.json index a8b81ec9..7bbcd86f 100644 --- a/packages/tests-react-native/package.json +++ b/packages/tests-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@tonclient/tests-react-native", - "version": "1.17.0", + "version": "1.18.0", "private": true, "main": "index.js", "browser": true, diff --git a/packages/tests-web/package.json b/packages/tests-web/package.json index c4081cc5..58ea6b93 100644 --- a/packages/tests-web/package.json +++ b/packages/tests-web/package.json @@ -1,6 +1,6 @@ { "name": "@tonclient/tests-web", - "version": "1.17.0", + "version": "1.18.0", "private": true, "description": "TON Client WASM module tests runner", "scripts": { diff --git a/packages/tests/package.json b/packages/tests/package.json index 84c4dc8c..152799a8 100644 --- a/packages/tests/package.json +++ b/packages/tests/package.json @@ -1,6 +1,6 @@ { "name": "@tonclient/tests", - "version": "1.17.0", + "version": "1.18.0", "private": true, "description": "TON Client Tests", "main": "dist/index.js", diff --git a/packages/tests/src/account.ts b/packages/tests/src/account.ts index 84acc146..a8980610 100644 --- a/packages/tests/src/account.ts +++ b/packages/tests/src/account.ts @@ -66,6 +66,7 @@ export class Account { signer: this.signer, deploy_set: { tvc: params.tvc, + initial_data: params.initData, }, call_set: params.initFunctionName ? { diff --git a/packages/tests/src/index.ts b/packages/tests/src/index.ts index 02f9c894..7473417f 100644 --- a/packages/tests/src/index.ts +++ b/packages/tests/src/index.ts @@ -5,4 +5,8 @@ export * from './logger'; export * from './tests/client'; export * from './tests/crypto'; export * from './tests/abi'; +export * from './tests/boc'; +export * from './tests/utils'; +export * from './tests/tvm'; export * from './tests/net'; +export * from './tests/contracts'; diff --git a/packages/tests/src/runner.ts b/packages/tests/src/runner.ts index 73fd24f4..99d2393f 100644 --- a/packages/tests/src/runner.ts +++ b/packages/tests/src/runner.ts @@ -158,7 +158,7 @@ export class TestsRunner { return giver; } - async getAccount(packages: { [abiVersion: number]: ContractPackage }, abiVersion: any, signer?: Signer): Promise { + async getAccount(packages: { [abiVersion: number]: ContractPackage }, abiVersion: any, signer?: Signer, initFunctionInput?: any, initData?: any): Promise { const pkg: ContractPackage | undefined = packages[Number(abiVersion) as ABIVersion]; if (!pkg) { throw new Error(`Missing required contract with ABI v${abiVersion}`); @@ -170,6 +170,8 @@ export class TestsRunner { , { tvc: pkg.tvc, initFunctionName: "constructor", + initFunctionInput, + initData, }); } @@ -217,7 +219,6 @@ export class TestsRunner { await this.client.close(); } } - } export const runner = new TestsRunner(); diff --git a/packages/tests/src/tests/abi.ts b/packages/tests/src/tests/abi.ts index b1499d5e..391bf53b 100644 --- a/packages/tests/src/tests/abi.ts +++ b/packages/tests/src/tests/abi.ts @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 TON DEV SOLUTIONS LTD. + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. * * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the @@ -14,9 +14,9 @@ * limitations under the License. */ -import {runner} from "../runner"; -import {expect, test} from "../jest"; -import {contracts} from "../contracts"; +import { ABIVersions, runner } from "../runner"; +import { expect, test } from "../jest"; +import { contracts } from "../contracts"; import { AppSigningBox, KeyPair, @@ -25,7 +25,8 @@ import { signerSigningBox, } from "@tonclient/core"; -test("encode_message", async () => { +test("abi: encode_message", async () => { + // arrange const { crypto, abi, @@ -52,7 +53,6 @@ test("encode_message", async () => { })).signature, }; }, - }; const signer = signerSigningBox((await crypto.register_signing_box(signing_box)).handle); @@ -72,7 +72,75 @@ test("encode_message", async () => { }, signer: signing, }); + + // act const signed_with_box = await abi.encode_message(await deploy_params(signer)); + + // assert expect(signed_with_box.message).toEqual("te6ccgECGAEAA6wAA0eIAAt9aqvShfTon7Lei1PVOhUEkEEZQkhDKPgNyzeTL6YSEbAHAgEA4bE5Gr3mWwDtlcEOWHr6slWoyQlpIWeYyw/00eKFGFkbAJMMFLWnu0mq4HSrPmktmzeeAboa4kxkFymCsRVt44dTHxAj/Hd67jWQF7peccWoU/dbMCBJBB6YdPCVZcJlJkAAAF0ZyXLg19VzGRotV8/gAQHAAwIDzyAGBAEB3gUAA9AgAEHaY+IEf47vXcayAvdLzji1Cn7rZgQJIIPTDp4SrLhMpMwCJv8A9KQgIsABkvSg4YrtU1gw9KEKCAEK9KQg9KEJAAACASANCwHI/38h7UTQINdJwgGOENP/0z/TANF/+GH4Zvhj+GKOGPQFcAGAQPQO8r3XC//4YnD4Y3D4Zn/4YeLTAAGOHYECANcYIPkBAdMAAZTT/wMBkwL4QuIg+GX5EPKoldMAAfJ64tM/AQwAao4e+EMhuSCfMCD4I4ED6KiCCBt3QKC53pL4Y+CANPI02NMfAfgjvPK50x8B8AH4R26S8jzeAgEgEw4CASAQDwC9uotV8/+EFujjXtRNAg10nCAY4Q0//TP9MA0X/4Yfhm+GP4Yo4Y9AVwAYBA9A7yvdcL//hicPhjcPhmf/hh4t74RvJzcfhm0fgA+ELIy//4Q88LP/hGzwsAye1Uf/hngCASASEQDluIAGtb8ILdHCfaiaGn/6Z/pgGi//DD8M3wx/DFvfSDK6mjofSBv6PwikDdJGDhvfCFdeXAyfABkZP2CEGRnwoRnRoIEB9AAAAAAAAAAAAAAAAAAIGeLZMCAQH2AGHwhZGX//CHnhZ/8I2eFgGT2qj/8M8ADFuZPCot8ILdHCfaiaGn/6Z/pgGi//DD8M3wx/DFva4b/yupo6Gn/7+j8AGRF7gAAAAAAAAAAAAAAAAhni2fA58jjyxi9EOeF/+S4/YAYfCFkZf/8IeeFn/wjZ4WAZPaqP/wzwAgFIFxQBCbi3xYJQFQH8+EFujhPtRNDT/9M/0wDRf/hh+Gb4Y/hi3tcN/5XU0dDT/9/R+ADIi9wAAAAAAAAAAAAAAAAQzxbPgc+Rx5YxeiHPC//JcfsAyIvcAAAAAAAAAAAAAAAAEM8Wz4HPklb4sEohzwv/yXH7ADD4QsjL//hDzws/+EbPCwDJ7VR/FgAE+GcActxwItDWAjHSADDcIccAkvI74CHXDR+S8jzhUxGS8jvhwQQighD////9vLGS8jzgAfAB+EdukvI83g=="); }); +test.each(ABIVersions)("abi: encode_message_body -> decode_message_body (ABI v%i)", async (abiVersion) => { + const abi = runner.getClient().abi; + const subscriptionAccount = await runner.getAccount(contracts.Subscription, abiVersion); + const walletAddress = "0:adb63a228837e478c7edf5fe3f0b5d12183e1f22246b67712b99ec538d6c5357"; + + const encodeResult = await abi.encode_message_body({ + abi: subscriptionAccount.abi, + call_set: { + function_name: "constructor", + input: { wallet: walletAddress }, + }, + signer: subscriptionAccount.signer, + is_internal: false, + }); + const decodeResult = await abi.decode_message_body({ + abi: subscriptionAccount.abi, + body: encodeResult.body, + is_internal: false, + }); + + expect(decodeResult.name).toEqual("constructor"); + expect(decodeResult.value).toEqual({ wallet: walletAddress }); + + const encodeResultInternal = await abi.encode_message_body({ + abi: subscriptionAccount.abi, + call_set: { + function_name: "constructor", + input: { wallet: walletAddress }, + }, + signer: subscriptionAccount.signer, + is_internal: true, + }); + const decodeResultInternal = await abi.decode_message_body({ + abi: subscriptionAccount.abi, + body: encodeResultInternal.body, + is_internal: true, + }); + + expect(decodeResultInternal.name).toEqual("constructor"); + expect(decodeResultInternal.value).toEqual({ wallet: walletAddress }); +}); + +test.each(ABIVersions)("abi: encode_message -> decode_message (ABI v%i)", async (abiVersion) => { + const abi = runner.getClient().abi; + const walletAddress = "0:adb63a228837e478c7edf5fe3f0b5d12183e1f22246b67712b99ec538d6c5357"; + const subscriptionAccount = await runner.getAccount(contracts.Subscription, abiVersion, undefined, { wallet: walletAddress }, undefined); + + const encodeResult = await abi.encode_message({ + abi: subscriptionAccount.abi, + address: await subscriptionAccount.getAddress(), + call_set: { + function_name: "constructor", + input: { wallet: walletAddress }, + }, + signer: subscriptionAccount.signer, + }); + const decodeResult = await abi.decode_message({ + abi: subscriptionAccount.abi, + message: encodeResult.message, + }); + + expect(decodeResult.name).toEqual("constructor"); + expect(decodeResult.value).toEqual({ wallet: walletAddress }); +}); diff --git a/packages/tests/src/tests/boc.ts b/packages/tests/src/tests/boc.ts new file mode 100644 index 00000000..5f908215 --- /dev/null +++ b/packages/tests/src/tests/boc.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. + * + * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at: + * + * http://www.ton.dev/licenses + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific TON DEV software governing permissions and + * limitations under the License. + */ + +import { ABIVersions, runner } from '../runner'; +import { expect, test } from "../jest"; +import { contracts } from '../contracts'; + +test("boc: get_boc_hash", async () => { + const boc = runner.getClient().boc; + const bocBase64 = "te6ccgEBAgEAxgABwYgAti0S4VOMe6uIVNX3nuDd7KSO13EsFEXDsUVaKRzBgdQCwaZuyAAAC3iWFUwMAK22OiKIN+R4x+31/j8LXRIYPh8iJGtncSuZ7FONbFNXAAAAAAAAAAAAAAAAAA9CQEABAMD3EJkJ6DsPCkGnV5lMTt6LIPRS7ViXPZjHMhJizNODUeKekStEXEUgmHS2vmokCRRUpsUhmwgFmkWaCatqe4wIlcBqp0PR+QAN1kt1SY8QavS350RCNNfeZ+ommI9hgd8="; + const hash = "adff1e7fd60632bb572b1afe0c2e569d8c68b1169994c48bc1ed92b3515c3b4e"; + + const result = await boc.get_boc_hash({ boc: bocBase64}); + + expect(result.hash).toEqual(hash); +}); + +test.each(ABIVersions)("boc: parse_message (ABI v%i)", async (abiVersion) => { + const { + abi, + boc, + } = runner.getClient(); + + const walletAccount = await runner.getAccount(contracts.WalletContract, abiVersion); + const walletAccountAddress = await walletAccount.getAddress(); + await runner.deploy(walletAccount); + + const encodeResult = await abi.encode_message(walletAccount.getParamsOfDeployMessage()); + const parseResult = await boc.parse_message({ + boc: encodeResult.message + }); + + expect(parseResult.parsed.dst).toEqual(walletAccountAddress); +}); diff --git a/packages/tests/src/tests/client.ts b/packages/tests/src/tests/client.ts index 35ca11d7..1ffa55d8 100644 --- a/packages/tests/src/tests/client.ts +++ b/packages/tests/src/tests/client.ts @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 TON DEV SOLUTIONS LTD. + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. * * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use * this file except in compliance with the License. @@ -12,15 +12,29 @@ * */ -import { - runner, -} from '../runner'; +import { runner } from '../runner'; import { test, expect } from '../jest'; -test('Test versions compatibility', async () => { - const client = runner.getClient(); - const version = (await client.client.version()).version; +test("Test versions compatibility", async () => { + const client = runner.getClient().client; + const version = (await client.version()).version; console.log(version); expect(version.split('.')[0]).toEqual('1'); }); +test("client: Versions in client.get_api_reference() and client.version() should be equal", async () => { + const client = runner.getClient().client; + + const api_reference_version = (await client.get_api_reference()).api.version; + const version_version = (await client.version()).version; + + expect(api_reference_version).toEqual(version_version); +}); + +test("client: build_info", async () => { + const client = runner.getClient().client; + + const build_info = await client.build_info(); + + expect(build_info).not.toBeNull(); +}) diff --git a/packages/tests/src/tests/contracts.ts b/packages/tests/src/tests/contracts.ts new file mode 100644 index 00000000..8e139d6a --- /dev/null +++ b/packages/tests/src/tests/contracts.ts @@ -0,0 +1,468 @@ +/* + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. + * + * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at: + * + * http://www.ton.dev/licenses + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific TON DEV software governing permissions and + * limitations under the License. + */ + +import { + Abi, + abiContract, + AbiErrorCode, + ParamsOfEncodeMessage, + ResultOfProcessMessage, + ResultOfRunTvm, + signerKeys, + TonClient, + TvmErrorCode, +} from "@tonclient/core"; + +import { Account } from "../account"; +import { ContractPackage, contracts } from "../contracts"; +import { expect, test } from "../jest"; +import { ABIVersions, runner } from "../runner"; + +test.each(ABIVersions)("Test hello contract from docs.ton.dev (ABI v%i)", async (abiVersion) => { + const { + abi, + processing, + tvm, + } = runner.getClient(); + + const helloAccount = await runner.getAccount(contracts.Hello, abiVersion); + const helloAccountAddress = await helloAccount.getAddress(); + await runner.deploy(helloAccount); + + await processing.process_message({ + send_events: false, + message_encode_params: { + address: helloAccountAddress, + abi: helloAccount.abi, + call_set: { + function_name: "touch", + }, + signer: helloAccount.signer, + } + }); + helloAccount.dropCachedData(); + + const localResult1 = await run_tvm(helloAccount, "sayHello"); + const localResult2 = await run_tvm(helloAccount, "sayHello"); + + expect(localResult1.decoded?.output?.value0) + .toBeTruthy(); + + expect(localResult1.decoded?.output?.value0) + .toEqual(localResult2.decoded?.output?.value0); + + // end of test, start of local functions + + async function run_tvm(account: Account, function_name: string) { + const encodeResult = await abi.encode_message({ + abi: account.abi, + address: await account.getAddress(), + call_set: { + function_name, + }, + signer: account.signer, + }); + + return await tvm.run_tvm({ + abi: account.abi, + account: await account.boc(), + message: encodeResult.message, + }) + } +}); + +test.each(ABIVersions)("Run aborted transaction (ABI v%i)", async (abiVersion) => { + const { + processing, + } = runner.getClient(); + + const walletAccount = await runner.getAccount(contracts.WalletContract, abiVersion); + const walletAccountAddress = await walletAccount.getAddress(); + await runner.deploy(walletAccount); + + try { + await processing.process_message({ + send_events: false, + message_encode_params: { + address: walletAccountAddress, + abi: walletAccount.abi, + call_set: { + function_name: "sendTransaction", + input: { + dest: "0:0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF", + value: 0, + bounce: false, + }, + }, + signer: walletAccount.signer, + } + }); + } catch (error) { + expect(error.code).toEqual(TvmErrorCode.ContractExecutionError); + expect(error.data.phase).toEqual("computeVm"); + expect(error.data.transaction_id).toBeTruthy(); + expect(error.data.exit_code).toEqual(101); + } + + try { + await processing.process_message({ + send_events: false, + message_encode_params: { + address: walletAccountAddress, + abi: walletAccount.abi, + call_set: { + function_name: "sendTransaction", + input: {}, + }, + signer: walletAccount.signer, + } + }); + } catch (error) { + expect(error.code).toEqual(AbiErrorCode.EncodeRunMessageFailed); + } +}); + +test.each(ABIVersions)("External signing on ABI v%i", async (abiVersion) => { + const { + abi, + crypto, + } = runner.getClient(); + + const keys = await crypto.generate_random_sign_keys(); + const eventsAccount = await runner.getAccount(contracts.Events, abiVersion); + const eventsAccountAddress = await eventsAccount.getAddress(); + const deployParams = { + ...eventsAccount.getParamsOfDeployMessage(), + call_set: { + function_name: "constructor", + header: { + pubkey: keys.public, + time: BigInt(1599458364291), + expire: 1599458404, + } + } + } + + const unsignedDeployMessage = await abi.encode_message({ + ...deployParams, + signer: { + type: "External", + public_key: keys.public, + }, + }); + const signResult = await crypto.sign({ + keys, + unsigned: unsignedDeployMessage.data_to_sign!, + }); + const signedDeployMessage = await abi.attach_signature({ + abi: deployParams.abi, + message: unsignedDeployMessage.message, + public_key: keys.public, + signature: signResult.signature, + }); + + const signedDeployMessage2 = await abi.encode_message({ + ...deployParams, + signer: { + type: "Keys", + keys + }, + }); + + expect(signedDeployMessage.message) + .toEqual(signedDeployMessage2.message); + + const runMessageParams = { + address: eventsAccountAddress, + abi: eventsAccount.abi, + call_set: { + function_name: "returnValue", + input: { id: "0" }, + header: { + pubkey: keys.public, + time: BigInt(Date.now()), + expire: Math.round(Date.now() / 1000) + 100, + } + }, + }; + + const unsignedRunMessage = await abi.encode_message({ + ...runMessageParams, + signer: { + type: "External", + public_key: keys.public, + }, + }); + const signRunMessageResult = await crypto.sign({ + keys, + unsigned: unsignedRunMessage.data_to_sign!, + }); + const signedRunMessage = await abi.attach_signature({ + abi: runMessageParams.abi, + message: unsignedRunMessage.message, + public_key: keys.public, + signature: signRunMessageResult.signature, + }); + const signedRunMessage2 = await abi.encode_message({ + ...runMessageParams, + signer: { + type: "Keys", + keys + }, + }); + + expect(signedRunMessage.message) + .toEqual(signedRunMessage2.message); +}); + +test("Test CheckInitParams contract double deployment with different init data", async () => { + const { + abi, + tvm, + } = runner.getClient(); + + const initData1 = { + addressVariable: "0:fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321", + uintVariable: "0x0", + booleanVariable: true, + bytesVariable: await generateRandomBytesInHex(32), + }; + const checkInitParamsAccount1 = await runner.getAccount(contracts.CheckInitParams, 2, undefined, undefined, initData1); + const checkInitParamsAccount1Address = await checkInitParamsAccount1.getAddress(); + await runner.deploy(checkInitParamsAccount1); + checkInitParamsAccount1.dropCachedData(); + + const initData2 = { + addressVariable: "0:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + uintVariable: "0xffffffff", + booleanVariable: false, + bytesVariable: await generateRandomBytesInHex(64), + }; + const checkInitParamsAccount2 = await runner.getAccount(contracts.CheckInitParams, 2, undefined, undefined, initData2); + const checkInitParamsAccount2Address = await checkInitParamsAccount2.getAddress(); + await runner.deploy(checkInitParamsAccount2); + + expect(checkInitParamsAccount1Address) + .not.toEqual(checkInitParamsAccount2Address); + + + const addressResult1 = await run_tvm(checkInitParamsAccount1, "getAddressVariable"); + const addressResult2 = await run_tvm(checkInitParamsAccount2, "getAddressVariable"); + + expect(addressResult1.decoded?.output).toEqual({ + value0: initData1.addressVariable, + }); + expect(addressResult2.decoded?.output).toEqual({ + value0: initData2.addressVariable, + }); + + + const uintResult1 = await run_tvm(checkInitParamsAccount1, "getUintVariable"); + const uintResult2 = await run_tvm(checkInitParamsAccount2, "getUintVariable"); + + expect(uintResult1.decoded?.output).toEqual({ + value0: "0x" + TonClient.toHex(initData1.uintVariable, 256), + }); + expect(uintResult2.decoded?.output).toEqual({ + value0: "0x" + TonClient.toHex(initData2.uintVariable, 256), + }); + + + const booleanResult1 = await run_tvm(checkInitParamsAccount1, "getBooleanVariable"); + const booleanResult2 = await run_tvm(checkInitParamsAccount2, "getBooleanVariable"); + + expect(booleanResult1.decoded?.output).toEqual({ + value0: initData1.booleanVariable, + }); + expect(booleanResult2.decoded?.output).toEqual({ + value0: initData2.booleanVariable, + }); + + + const bytesResult1 = await run_tvm(checkInitParamsAccount1, "getBytesVariable"); + const bytesResult2 = await run_tvm(checkInitParamsAccount2, "getBytesVariable"); + + expect(bytesResult1.decoded?.output).toEqual({ + value0: initData1.bytesVariable, + }); + expect(bytesResult2.decoded?.output).toEqual({ + value0: initData2.bytesVariable, + }); + + + // end of test, start of local functions + + async function generateRandomBytesInHex(length: number): Promise { + var randomBytesInBase64 = + await runner.getClient() + .crypto.generate_random_bytes({ + length, + }); + + return Buffer.from(randomBytesInBase64.bytes, "base64").toString("hex"); + } + + async function run_tvm(account: Account, function_name: string): Promise { + return await tvm.run_tvm({ + abi: account.abi, + account: await account.boc(), + message: (await abi.encode_message({ + abi: account.abi, + address: await account.getAddress(), + signer: account.signer, + call_set: { + function_name, + } + })).message, + }); + } +}); + +test.each(ABIVersions)("Test SetCode contracts (ABI v%i)", async (abiVersion) => { + const { + boc, + processing, + } = runner.getClient(); + + const setCodeAccount = await runner.getAccount(contracts.Setcode, abiVersion); + const setCode2ContractPackage = getContractPackage(contracts.Setcode2, abiVersion); + await runner.deploy(setCodeAccount); + + const version1 = await run(setCodeAccount, setCodeAccount.abi, "getVersion"); + expect (version1.decoded?.output?.value0) + .toEqual("0x0000000000000000000000000000000000000000000000000000000000000001"); + + await run(setCodeAccount, setCodeAccount.abi, "main", { newcode: (await boc.get_code_from_tvc({ tvc: setCode2ContractPackage.tvc })).code }); + + const version2 = await run(setCodeAccount, abiContract(setCode2ContractPackage.abi), "getNewVersion"); + expect (version2.decoded?.output?.value0) + .toEqual("0x0000000000000000000000000000000000000000000000000000000000000002"); + + // end of test, start of local functions + + function getContractPackage(packages: { [abiVersion: number]: ContractPackage }, abiVersion: any): ContractPackage { + const pkg: ContractPackage | undefined = packages[Number(abiVersion)]; + if (!pkg) { + throw new Error(`Missing required contract with ABI v${abiVersion}`); + } + return pkg; + } + + async function run(account: Account, abi: Abi, function_name: string, input?: any): Promise { + return await processing.process_message({ + message_encode_params: { + abi: abi, + signer: account.signer, + address: await account.getAddress(), + call_set: { + function_name, + input, + }, + }, + send_events: false, + }); + } +}); + +test("Test expire retries", async () => { + const processing = runner.getClient().processing; + + const helloAccount = await runner.getAccount(contracts.Hello, 2); + const helloAccountAddress = await helloAccount.getAddress(); + await runner.deploy(helloAccount); + + let completed = 0; + const run = async () => { + const result = await processing.process_message({ + message_encode_params: { + abi: helloAccount.abi, + signer: helloAccount.signer, + address: helloAccountAddress, + call_set: { + function_name: "touch" + } + }, + send_events: false, + }); + console.log(`>>> run complete ${++completed}`); + return result; + }; + const runs = []; + for (let i = 0; i < 10; i += 1) { + runs.push(run()); + } + await Promise.all(runs); +}); + +test("Signing", async () => { + const { + abi, + crypto, + processing, + } = runner.getClient(); + + const ownerKeys = await crypto.generate_random_sign_keys(); + const deployKeys = await crypto.generate_random_sign_keys(); + const time = Math.round((Date.now() + 10000) / 1000); + + const sensorAccountAbi = abiContract(contracts.Sensor[2].abi); + const sensorAccountTvc = contracts.Sensor[2].tvc; + const paramsOfDeployMessage: ParamsOfEncodeMessage = { + abi: sensorAccountAbi, + signer: signerKeys(deployKeys), + deploy_set: { + tvc: sensorAccountTvc, + }, + call_set: { + function_name: "constructor", + input: { ownerKey: `0x${ownerKeys.public}` }, + header: { + time: BigInt(time), + } + }, + }; + + const futureAddress = (await abi.encode_message(paramsOfDeployMessage)).address; + await runner.sendGramsTo(futureAddress); + + const deployResult = await processing.process_message({ + message_encode_params: { + ...paramsOfDeployMessage, + call_set: { + function_name: "constructor", + input: { ownerKey: `0x${ownerKeys.public}` }, + // without header + }, + }, + send_events: false, + }); + const helloAddress: string = deployResult.transaction.account_addr; + + await processing.process_message({ + message_encode_params: { + address: helloAddress, + abi: sensorAccountAbi, + call_set: { + function_name: "setData", + input: { + input: 8, + }, + }, + signer: signerKeys(ownerKeys), + }, + send_events: false, + }); +}); diff --git a/packages/tests/src/tests/crypto.ts b/packages/tests/src/tests/crypto.ts index 1102a3c6..e3336e0f 100644 --- a/packages/tests/src/tests/crypto.ts +++ b/packages/tests/src/tests/crypto.ts @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 TON DEV SOLUTIONS LTD. + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. * * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the @@ -14,99 +14,156 @@ * limitations under the License. */ -import {runner} from "../runner"; -import { - expect, - test, -} from "../jest"; +import { runner } from "../runner"; +import { expect, test } from "../jest"; + import { AppEncryptionBox, EncryptionBoxHandle, EncryptionBoxInfo, } from "@tonclient/core"; -const mnemonicWordCount = [12, 15, 18, 21, 24]; -const mnemonicDictionary = [1, 2, 3, 4, 5, 6, 7, 8]; - function text2base64(text: string): string { return Buffer.from(text, "utf8").toString("base64"); } -function base642text(base64: string): string { - return Buffer.from(base64, "base64").toString("utf8"); -} +// The "//-----" partitions are derived from TON-SDK/ton_client/src/crypto file structure -test("crypto - encrypt large blocks", async () => { - const client = runner.getClient(); - const ourKeys = await client.crypto.nacl_box_keypair(); - const theirKeys = await client.crypto.nacl_box_keypair(); +// ------------------------- math ------------------------- - async function testBuffer() { - const nonce = Buffer.from((await client.crypto.generate_random_bytes({length: 24})).bytes, "base64").toString("hex"); - const decrypted = (await client.crypto.generate_random_bytes({length: 100000000})).bytes; - const encrypted = (await client.crypto.nacl_box({ - decrypted: decrypted, - secret: ourKeys.secret, - their_public: theirKeys.public, - nonce, - })).encrypted; - const decrypted2 = (await client.crypto.nacl_box_open({ - encrypted, - secret: theirKeys.secret, - their_public: ourKeys.public, - nonce, - })).decrypted; - expect(decrypted2).toEqual(decrypted); - } +test("crypto: factorize", async () => { + const crypto = runner.getClient().crypto; - await Promise.all([0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(_ => testBuffer())); + const result = await crypto.factorize({composite: "17ED48941A08F981"}); + + expect(result.factors.length).toEqual(2); + expect(result.factors[0]).toEqual("494C553B"); + expect(result.factors[1]).toEqual("53911073"); }); -test("crypto", async () => { +test("crypto: modular_power", async () => { const crypto = runner.getClient().crypto; - // Math - - const result1 = await crypto.factorize({composite: "17ED48941A08F981"}); - expect(result1.factors[0]).toEqual("494C553B"); - expect(result1.factors[1]).toEqual("53911073"); - - const result2 = await crypto.modular_power({ + const result = await crypto.modular_power({ base: "0123456789ABCDEF", exponent: "0123", modulus: "01234567", }); - expect(result2.modular_power).toEqual("63bfdf"); - // random + expect(result.modular_power).toEqual("63bfdf"); +}); - const result3 = await crypto.generate_random_bytes({length: 32}); - expect(result3.bytes.length).toEqual(44); +test("crypto: ton_crc16", async () => { + const crypto = runner.getClient().crypto; - // ed25519 + const result = await crypto.ton_crc16({ + data: "RWFzdGVyIGVnZw==", + }); + + expect(result.crc).toEqual(4743); +}); - const result5 = await crypto.generate_random_sign_keys(); - expect(result5.public.length).toEqual(64); - expect(result5.secret.length).toEqual(64); - expect(result5.public).not.toEqual(result5.secret); +test("crypto: generate_random_bytes", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.generate_random_bytes({length: 32}); + const result2 = await crypto.generate_random_bytes({length: 32}); + + expect(result.bytes.length).toEqual(44); + expect(result2.bytes.length).toEqual(44); + expect(result).not.toEqual(result2); +}); - // sha - const hex1 = await crypto.sha512({ +// ------------------------- keys ------------------------- + +test("crypto: convert_public_key_to_ton_safe_format", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.convert_public_key_to_ton_safe_format({ + public_key: "5329c056c33e3a2a787bf2ae4c2afda87e4231898a5438e0cfd7a06dc4fac067", + }); + + expect(result.ton_public_key).toEqual("PuZTKcBWwz46Knh78q5MKv2ofkIxiYpUOODP16BtxPrAZ_ed"); +}); + +test("crypto: generate_random_sign_keys", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.generate_random_sign_keys(); + const result2 = await crypto.generate_random_sign_keys(); + + expect(result.public.length).toEqual(64); + expect(result.secret.length).toEqual(64); + expect(result.public).not.toEqual(result.secret); + + expect(result2.public.length).toEqual(64); + expect(result2.secret.length).toEqual(64); + expect(result2.public).not.toEqual(result2.secret); + + expect(result.public).not.toEqual(result2.public); + expect(result.secret).not.toEqual(result2.secret); +}); + +test("crypto: sign", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.sign({ + keys: { + public: "bb3e649a4675cdb579803820a97dcba9594f1a1aefa7cb2b4da844ca9d32d348", + secret: "52ae9e942cda06326de0905dea4d2c9c2fc7674f3fbd96495a5e92d099fc2507", + }, + unsigned: "RWFzdGVyIGVnZw==", + }); + + expect(result).toEqual({ + signed: "aTRQ4/TbSVcFHB096JpAxZOLwHjs3Sdf07gVVxPsV6csr/ChRoY48Ue9Z5eHzyRxwZZbHl6SYXwgTh0HI1HqB0Vhc3RlciBlZ2c=", + signature: "693450e3f4db4957051c1d3de89a40c5938bc078ecdd275fd3b8155713ec57a72caff0a1468638f147bd679787cf2471c1965b1e5e92617c204e1d072351ea07", + }); +}); + +test("crypto: verify_signature", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.verify_signature({ + public: "bb3e649a4675cdb579803820a97dcba9594f1a1aefa7cb2b4da844ca9d32d348", + signed: "aTRQ4/TbSVcFHB096JpAxZOLwHjs3Sdf07gVVxPsV6csr/ChRoY48Ue9Z5eHzyRxwZZbHl6SYXwgTh0HI1HqB0Vhc3RlciBlZ2c=", + }); + + expect(result.unsigned).toEqual("RWFzdGVyIGVnZw=="); +}); + + +// ------------------------- hash ------------------------- + +test("crypto: sha256", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.sha256({ + data: text2base64("Message to hash with sha 256"), + }); + expect(result.hash).toEqual("16fd057308dd358d5a9b3ba2de766b2dfd5e308478fc1f7ba5988db2493852f5"); + +}); + +test("crypto: sha512", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.sha512({ data: text2base64("Message to hash with sha 512"), }); - expect(hex1.hash) + expect(result.hash) .toEqual( "2616a44e0da827f0244e93c2b0b914223737a6129bc938b8edf2780ac9482960baa9b7c7cdb11457c1cebd5ae77e295ed94577f32d4c963dc35482991442daa5"); - const hex2 = await crypto.sha256({ - data: text2base64("Message to hash with sha 256"), - }); - expect(hex2.hash).toEqual("16fd057308dd358d5a9b3ba2de766b2dfd5e308478fc1f7ba5988db2493852f5"); +}); + +// ------------------------- scrypt ------------------------- - // scrypt +test("crypto: scrypt", async () => { + const crypto = runner.getClient().crypto; - let result6 = await crypto.scrypt({ + let result = await crypto.scrypt({ password: text2base64("Test Password"), salt: text2base64("Test Salt"), log_n: 10, @@ -114,26 +171,123 @@ test("crypto", async () => { p: 16, dk_len: 64, }); - expect(result6.key) + + expect(result.key) .toEqual( "52e7fcf91356eca55fc5d52f16f5d777e3521f54e3c570c9bbb7df58fc15add73994e5db42be368de7ebed93c9d4f21f9be7cc453358d734b04a057d0ed3626d"); - // nacl keys +}); + + +// ------------------------- nacl ------------------------- + +test("crypto: nacl_sign_keypair_from_secret_key", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.nacl_sign_keypair_from_secret_key({ + secret: "56b6a77093d6fdf14e593f36275d872d75de5b341942376b2a08759f3cbae78f", + }); + + expect(result).toEqual({ + public: "1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", + secret: "56b6a77093d6fdf14e593f36275d872d75de5b341942376b2a08759f3cbae78f1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", + }); +}); - const result7 = await crypto.nacl_box_keypair(); - expect(result7.public.length).toEqual(64); - expect(result7.secret.length).toEqual(64); - expect(result7.public).not.toEqual(result7.secret); +test("crypto: nacl_sign", async () => { + const crypto = runner.getClient().crypto; - const result8 = await crypto.nacl_box_keypair_from_secret_key({secret: "e207b5966fb2c5be1b71ed94ea813202706ab84253bdf4dc55232f82a1caf0d4"}); - expect(result8.public) - .toEqual("a53b003d3ffc1e159355cb37332d67fc235a7feb6381e36c803274074dc3933a"); + const result = await crypto.nacl_sign({ + unsigned: text2base64("Test Message"), + secret: "56b6a77093d6fdf14e593f36275d872d75de5b341942376b2a08759f3cbae78f1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", + }); - const result10 = await crypto.nacl_sign_keypair_from_secret_key({secret: "8fb4f2d256e57138fb310b0a6dac5bbc4bee09eb4821223a720e5b8e1f3dd674"}); - expect(result10.public) - .toEqual("aa5533618573860a7e1bf19f34bd292871710ed5b2eafa0dcdbb33405f2231c6"); + expect(result.signed).toEqual("+wz+QO6l1slgZS5s65BNqKcu4vz24FCJz4NSAxef9lu0jFfs8x3PzSZRC+pn5k8+aJi3xYMA3BQzglQmjK3hA1Rlc3QgTWVzc2FnZQ=="); +}); - // nacl box +test("crypto: nacl_sign_open", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.nacl_sign_open({ + signed: "+wz+QO6l1slgZS5s65BNqKcu4vz24FCJz4NSAxef9lu0jFfs8x3PzSZRC+pn5k8+aJi3xYMA3BQzglQmjK3hA1Rlc3QgTWVzc2FnZQ==", + public: "1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", + }); + + expect(result.unsigned).toEqual(text2base64("Test Message")); +}); + +test("crypto: nacl_sign_detached", async () => { + const crypto = runner.getClient().crypto; + + const sign = await crypto.nacl_sign_detached({ + unsigned: text2base64("Test Message"), + secret: "56b6a77093d6fdf14e593f36275d872d75de5b341942376b2a08759f3cbae78f1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", + }); + + expect(sign.signature) + .toEqual( + "fb0cfe40eea5d6c960652e6ceb904da8a72ee2fcf6e05089cf835203179ff65bb48c57ecf31dcfcd26510bea67e64f3e6898b7c58300dc14338254268cade103"); +}); + +test("crypto: nacl_sign_detached_verify success", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.nacl_sign_detached_verify({ + public: "1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", + signature: "fb0cfe40eea5d6c960652e6ceb904da8a72ee2fcf6e05089cf835203179ff65bb48c57ecf31dcfcd26510bea67e64f3e6898b7c58300dc14338254268cade103", + unsigned: text2base64("Test Message"), + }); + + expect(result.succeeded).toBeTruthy(); +}); + +test("crypto: nacl_sign_detached_verify failure", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.nacl_sign_detached_verify({ + public: "2869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", + signature: "fb0cfe40eea5d6c960652e6ceb904da8a72ee2fcf6e05089cf835203179ff65bb48c57ecf31dcfcd26510bea67e64f3e6898b7c58300dc14338254268cade103", + unsigned: text2base64("Test Message") + }); + + expect(result.succeeded).toBeFalsy(); +}); + +test("crypto: nacl_box_keypair", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.nacl_box_keypair(); + const result2 = await crypto.nacl_box_keypair(); + + expect(result.public.length).toEqual(64); + expect(result.secret.length).toEqual(64); + expect(result.public) + .toEqual( + (await crypto.nacl_box_keypair_from_secret_key({ secret: result.secret })).public); + + expect(result2.public.length).toEqual(64); + expect(result2.secret.length).toEqual(64); + expect(result2.public) + .toEqual( + (await crypto.nacl_box_keypair_from_secret_key({ secret: result2.secret })).public); + + expect(result.public).not.toEqual(result2.public); + expect(result.secret).not.toEqual(result2.secret); +}); + +test("crypto: nacl_box_keypair_from_secret_key", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.nacl_box_keypair_from_secret_key({ secret: "e207b5966fb2c5be1b71ed94ea813202706ab84253bdf4dc55232f82a1caf0d4" }); + + expect(result).toEqual({ + public: "a53b003d3ffc1e159355cb37332d67fc235a7feb6381e36c803274074dc3933a", + secret: "e207b5966fb2c5be1b71ed94ea813202706ab84253bdf4dc55232f82a1caf0d4", + }); +}); + +test("crypto: nacl_box", async () => { + const crypto = runner.getClient().crypto; const encrypted1 = await crypto.nacl_box({ decrypted: text2base64("Test Message"), @@ -141,72 +295,203 @@ test("crypto", async () => { their_public: "c4e2d9fe6a6baf8d1812b799856ef2a306291be7a7024837ad33a8530db79c6b", secret: "d9b9dc5033fb416134e5d2107fdbacab5aadb297cb82dbdcd137d663bac59f7f", }); + expect(encrypted1.encrypted).toEqual("li4XED4kx/pjQ2qdP0eR2d/K30uN94voNADxwA=="); +}); - const decrypted1 = await crypto.nacl_box_open({ - encrypted: "li4XED4kx/pjQ2qdP0eR2d/K30uN94voNADxwA==", +test("crypto: nacl_box_open", async () => { + const crypto = runner.getClient().crypto; + + const keysA = await crypto.nacl_box_keypair(); + const keysB = await crypto.nacl_box_keypair(); + + const encrypted = await crypto.nacl_box({ + secret: keysA.secret, + their_public: keysB.public, nonce: "cd7f99924bf422544046e83595dd5803f17536f5c9a11746", - their_public: "c4e2d9fe6a6baf8d1812b799856ef2a306291be7a7024837ad33a8530db79c6b", - secret: "d9b9dc5033fb416134e5d2107fdbacab5aadb297cb82dbdcd137d663bac59f7f", + decrypted: "AQID", + }); + + const decryptedA = await crypto.nacl_box_open({ + secret: keysA.secret, + their_public: keysB.public, + nonce: "cd7f99924bf422544046e83595dd5803f17536f5c9a11746", + encrypted: encrypted.encrypted, + }); + + const decryptedB = await crypto.nacl_box_open({ + secret: keysB.secret, + their_public: keysA.public, + nonce: "cd7f99924bf422544046e83595dd5803f17536f5c9a11746", + encrypted: encrypted.encrypted, }); - expect(base642text(decrypted1.decrypted)).toEqual("Test Message"); - // nacl secret box + expect(decryptedA.decrypted).toEqual("AQID"); + expect(decryptedB.decrypted).toEqual("AQID"); +}); - const encrypted2 = await crypto.nacl_secret_box({ +test("crypto: nacl_secret_box", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.nacl_secret_box({ decrypted: text2base64("Test Message"), nonce: "2a33564717595ebe53d91a785b9e068aba625c8453a76e45", key: "8f68445b4e78c000fe4d6b7fc826879c1e63e3118379219a754ae66327764bd8", }); - expect(encrypted2.encrypted).toEqual("JL7ejKWe2KXmrsns41yfXoQF0t/C1Q8RGyzQ2A=="); - const decrypted2 = await crypto.nacl_secret_box_open({ - encrypted: "JL7ejKWe2KXmrsns41yfXoQF0t/C1Q8RGyzQ2A==", - nonce: "2a33564717595ebe53d91a785b9e068aba625c8453a76e45", - key: "8f68445b4e78c000fe4d6b7fc826879c1e63e3118379219a754ae66327764bd8", + expect(result.encrypted).toEqual("JL7ejKWe2KXmrsns41yfXoQF0t/C1Q8RGyzQ2A=="); +}); + +test("crypto: nacl_secret_box_open", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.nacl_box_open({ + encrypted: "li4XED4kx/pjQ2qdP0eR2d/K30uN94voNADxwA==", + nonce: "cd7f99924bf422544046e83595dd5803f17536f5c9a11746", + their_public: "c4e2d9fe6a6baf8d1812b799856ef2a306291be7a7024837ad33a8530db79c6b", + secret: "d9b9dc5033fb416134e5d2107fdbacab5aadb297cb82dbdcd137d663bac59f7f", }); - expect(base642text(decrypted2.decrypted)).toEqual("Test Message"); - const e = await crypto.nacl_secret_box({ + expect(result.decrypted).toEqual(text2base64("Test Message")); +}); + +test("crypto: nacl_secret_box and nacl_secret_box_open with ' and \" and : {} in the text", async () => { + const crypto = runner.getClient().crypto; + + const box = await crypto.nacl_secret_box({ decrypted: text2base64("Text with ' and \" and : {}"), nonce: "2a33564717595ebe53d91a785b9e068aba625c8453a76e45", key: "8f68445b4e78c000fe4d6b7fc826879c1e63e3118379219a754ae66327764bd8", }); - const d = await crypto.nacl_secret_box_open({ - encrypted: e.encrypted, + const result = await crypto.nacl_secret_box_open({ + encrypted: box.encrypted, nonce: "2a33564717595ebe53d91a785b9e068aba625c8453a76e45", key: "8f68445b4e78c000fe4d6b7fc826879c1e63e3118379219a754ae66327764bd8", }); - expect(d.decrypted).toEqual("VGV4dCB3aXRoICcgYW5kICIgYW5kIDoge30="); - expect(base642text(d.decrypted)).toEqual("Text with ' and \" and : {}"); - // nacl sign - const signed = await crypto.nacl_sign({ - unsigned: text2base64("Test Message"), - secret: "56b6a77093d6fdf14e593f36275d872d75de5b341942376b2a08759f3cbae78f1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", - }); - expect(signed.signed) - .toEqual( - "+wz+QO6l1slgZS5s65BNqKcu4vz24FCJz4NSAxef9lu0jFfs8x3PzSZRC+pn5k8+aJi3xYMA3BQzglQmjK3hA1Rlc3QgTWVzc2FnZQ=="); + expect(result.decrypted).toEqual("VGV4dCB3aXRoICcgYW5kICIgYW5kIDoge30="); + expect(result.decrypted).toEqual(text2base64("Text with ' and \" and : {}")); +}); - const original = await crypto.nacl_sign_open({ - signed: "+wz+QO6l1slgZS5s65BNqKcu4vz24FCJz4NSAxef9lu0jFfs8x3PzSZRC+pn5k8+aJi3xYMA3BQzglQmjK3hA1Rlc3QgTWVzc2FnZQ==", - public: "1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", - }); - expect(base642text(original.unsigned)).toEqual("Test Message"); - const sign = await crypto.nacl_sign_detached({ - unsigned: text2base64("Test Message"), - secret: "56b6a77093d6fdf14e593f36275d872d75de5b341942376b2a08759f3cbae78f1869b7ef29d58026217e9cf163cbfbd0de889bdf1bf4daebf5433a312f5b8d6e", - }); - expect(sign.signature) +// ------------------------- mnemonic ------------------------- + +test("crypto: mnemonic_words", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.mnemonic_words({}); + + expect(result.words.split(" ").length).toEqual(2048); + expect(result.words) .toEqual( - "fb0cfe40eea5d6c960652e6ceb904da8a72ee2fcf6e05089cf835203179ff65bb48c57ecf31dcfcd26510bea67e64f3e6898b7c58300dc14338254268cade103"); + "abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve acid acoustic acquire across " + + "act action actor actress actual adapt add addict address adjust admit adult advance advice aerobic affair afford afraid again age agent " + + "agree ahead aim air airport aisle alarm album alcohol alert alien all alley allow almost alone alpha already also alter always amateur " + + "amazing among amount amused analyst anchor ancient anger angle angry animal ankle announce annual another answer antenna antique anxiety " + + "any apart apology appear apple approve april arch arctic area arena argue arm armed armor army around arrange arrest arrive arrow art " + + "artefact artist artwork ask aspect assault asset assist assume asthma athlete atom attack attend attitude attract auction audit august " + + "aunt author auto autumn average avocado avoid awake aware away awesome awful awkward axis baby bachelor bacon badge bag balance balcony " + + "ball bamboo banana banner bar barely bargain barrel base basic basket battle beach bean beauty because become beef before begin behave " + + "behind believe below belt bench benefit best betray better between beyond bicycle bid bike bind biology bird birth bitter black blade " + + "blame blanket blast bleak bless blind blood blossom blouse blue blur blush board boat body boil bomb bone bonus book boost border boring " + + "borrow boss bottom bounce box boy bracket brain brand brass brave bread breeze brick bridge brief bright bring brisk broccoli broken " + + "bronze broom brother brown brush bubble buddy budget buffalo build bulb bulk bullet bundle bunker burden burger burst bus business busy " + + "butter buyer buzz cabbage cabin cable cactus cage cake call calm camera camp can canal cancel candy cannon canoe canvas canyon capable " + + "capital captain car carbon card cargo carpet carry cart case cash casino castle casual cat catalog catch category cattle caught cause " + + "caution cave ceiling celery cement census century cereal certain chair chalk champion change chaos chapter charge chase chat cheap check " + + "cheese chef cherry chest chicken chief child chimney choice choose chronic chuckle chunk churn cigar cinnamon circle citizen city civil " + + "claim clap clarify claw clay clean clerk clever click client cliff climb clinic clip clock clog close cloth cloud clown club clump " + + "cluster clutch coach coast coconut code coffee coil coin collect color column combine come comfort comic common company concert conduct " + + "confirm congress connect consider control convince cook cool copper copy coral core corn correct cost cotton couch country couple course " + + "cousin cover coyote crack cradle craft cram crane crash crater crawl crazy cream credit creek crew cricket crime crisp critic crop cross " + + "crouch crowd crucial cruel cruise crumble crunch crush cry crystal cube culture cup cupboard curious current curtain curve cushion " + + "custom cute cycle dad damage damp dance danger daring dash daughter dawn day deal debate debris decade december decide decline decorate " + + "decrease deer defense define defy degree delay deliver demand demise denial dentist deny depart depend deposit depth deputy derive " + + "describe desert design desk despair destroy detail detect develop device devote diagram dial diamond diary dice diesel diet differ " + + "digital dignity dilemma dinner dinosaur direct dirt disagree discover disease dish dismiss disorder display distance divert divide " + + "divorce dizzy doctor document dog doll dolphin domain donate donkey donor door dose double dove draft dragon drama drastic draw dream " + + "dress drift drill drink drip drive drop drum dry duck dumb dune during dust dutch duty dwarf dynamic eager eagle early earn earth easily " + + "east easy echo ecology economy edge edit educate effort egg eight either elbow elder electric elegant element elephant elevator elite " + + "else embark embody embrace emerge emotion employ empower empty enable enact end endless endorse enemy energy enforce engage engine " + + "enhance enjoy enlist enough enrich enroll ensure enter entire entry envelope episode equal equip era erase erode erosion error erupt " + + "escape essay essence estate eternal ethics evidence evil evoke evolve exact example excess exchange excite exclude excuse execute " + + "exercise exhaust exhibit exile exist exit exotic expand expect expire explain expose express extend extra eye eyebrow fabric face " + + "faculty fade faint faith fall false fame family famous fan fancy fantasy farm fashion fat fatal father fatigue fault favorite feature " + + "february federal fee feed feel female fence festival fetch fever few fiber fiction field figure file film filter final find fine finger " + + "finish fire firm first fiscal fish fit fitness fix flag flame flash flat flavor flee flight flip float flock floor flower fluid flush " + + "fly foam focus fog foil fold follow food foot force forest forget fork fortune forum forward fossil foster found fox fragile frame " + + "frequent fresh friend fringe frog front frost frown frozen fruit fuel fun funny furnace fury future gadget gain galaxy gallery game gap " + + "garage garbage garden garlic garment gas gasp gate gather gauge gaze general genius genre gentle genuine gesture ghost giant gift giggle " + + "ginger giraffe girl give glad glance glare glass glide glimpse globe gloom glory glove glow glue goat goddess gold good goose gorilla " + + "gospel gossip govern gown grab grace grain grant grape grass gravity great green grid grief grit grocery group grow grunt guard guess " + + "guide guilt guitar gun gym habit hair half hammer hamster hand happy harbor hard harsh harvest hat have hawk hazard head health heart " + + "heavy hedgehog height hello helmet help hen hero hidden high hill hint hip hire history hobby hockey hold hole holiday hollow home honey " + + "hood hope horn horror horse hospital host hotel hour hover hub huge human humble humor hundred hungry hunt hurdle hurry hurt husband " + + "hybrid ice icon idea identify idle ignore ill illegal illness image imitate immense immune impact impose improve impulse inch include " + + "income increase index indicate indoor industry infant inflict inform inhale inherit initial inject injury inmate inner innocent input " + + "inquiry insane insect inside inspire install intact interest into invest invite involve iron island isolate issue item ivory jacket " + + "jaguar jar jazz jealous jeans jelly jewel job join joke journey joy judge juice jump jungle junior junk just kangaroo keen keep ketchup " + + "key kick kid kidney kind kingdom kiss kit kitchen kite kitten kiwi knee knife knock know lab label labor ladder lady lake lamp language " + + "laptop large later latin laugh laundry lava law lawn lawsuit layer lazy leader leaf learn leave lecture left leg legal legend leisure " + + "lemon lend length lens leopard lesson letter level liar liberty library license life lift light like limb limit link lion liquid list " + + "little live lizard load loan lobster local lock logic lonely long loop lottery loud lounge love loyal lucky luggage lumber lunar lunch " + + "luxury lyrics machine mad magic magnet maid mail main major make mammal man manage mandate mango mansion manual maple marble march " + + "margin marine market marriage mask mass master match material math matrix matter maximum maze meadow mean measure meat mechanic medal " + + "media melody melt member memory mention menu mercy merge merit merry mesh message metal method middle midnight milk million mimic mind " + + "minimum minor minute miracle mirror misery miss mistake mix mixed mixture mobile model modify mom moment monitor monkey monster month " + + "moon moral more morning mosquito mother motion motor mountain mouse move movie much muffin mule multiply muscle museum mushroom music " + + "must mutual myself mystery myth naive name napkin narrow nasty nation nature near neck need negative neglect neither nephew nerve nest " + + "net network neutral never news next nice night noble noise nominee noodle normal north nose notable note nothing notice novel now " + + "nuclear number nurse nut oak obey object oblige obscure observe obtain obvious occur ocean october odor off offer office often oil okay " + + "old olive olympic omit once one onion online only open opera opinion oppose option orange orbit orchard order ordinary organ orient " + + "original orphan ostrich other outdoor outer output outside oval oven over own owner oxygen oyster ozone pact paddle page pair palace " + + "palm panda panel panic panther paper parade parent park parrot party pass patch path patient patrol pattern pause pave payment peace " + + "peanut pear peasant pelican pen penalty pencil people pepper perfect permit person pet phone photo phrase physical piano picnic picture " + + "piece pig pigeon pill pilot pink pioneer pipe pistol pitch pizza place planet plastic plate play please pledge pluck plug plunge poem " + + "poet point polar pole police pond pony pool popular portion position possible post potato pottery poverty powder power practice praise " + + "predict prefer prepare present pretty prevent price pride primary print priority prison private prize problem process produce profit " + + "program project promote proof property prosper protect proud provide public pudding pull pulp pulse pumpkin punch pupil puppy purchase " + + "purity purpose purse push put puzzle pyramid quality quantum quarter question quick quit quiz quote rabbit raccoon race rack radar radio " + + "rail rain raise rally ramp ranch random range rapid rare rate rather raven raw razor ready real reason rebel rebuild recall receive " + + "recipe record recycle reduce reflect reform refuse region regret regular reject relax release relief rely remain remember remind remove " + + "render renew rent reopen repair repeat replace report require rescue resemble resist resource response result retire retreat return " + + "reunion reveal review reward rhythm rib ribbon rice rich ride ridge rifle right rigid ring riot ripple risk ritual rival river road " + + "roast robot robust rocket romance roof rookie room rose rotate rough round route royal rubber rude rug rule run runway rural sad saddle " + + "sadness safe sail salad salmon salon salt salute same sample sand satisfy satoshi sauce sausage save say scale scan scare scatter scene " + + "scheme school science scissors scorpion scout scrap screen script scrub sea search season seat second secret section security seed seek " + + "segment select sell seminar senior sense sentence series service session settle setup seven shadow shaft shallow share shed shell " + + "sheriff shield shift shine ship shiver shock shoe shoot shop short shoulder shove shrimp shrug shuffle shy sibling sick side siege sight " + + "sign silent silk silly silver similar simple since sing siren sister situate six size skate sketch ski skill skin skirt skull slab slam " + + "sleep slender slice slide slight slim slogan slot slow slush small smart smile smoke smooth snack snake snap sniff snow soap soccer " + + "social sock soda soft solar soldier solid solution solve someone song soon sorry sort soul sound soup source south space spare spatial " + + "spawn speak special speed spell spend sphere spice spider spike spin spirit split spoil sponsor spoon sport spot spray spread spring spy " + + "square squeeze squirrel stable stadium staff stage stairs stamp stand start state stay steak steel stem step stereo stick still sting " + + "stock stomach stone stool story stove strategy street strike strong struggle student stuff stumble style subject submit subway success " + + "such sudden suffer sugar suggest suit summer sun sunny sunset super supply supreme sure surface surge surprise surround survey suspect " + + "sustain swallow swamp swap swarm swear sweet swift swim swing switch sword symbol symptom syrup system table tackle tag tail talent talk " + + "tank tape target task taste tattoo taxi teach team tell ten tenant tennis tent term test text thank that theme then theory there they " + + "thing this thought three thrive throw thumb thunder ticket tide tiger tilt timber time tiny tip tired tissue title toast tobacco today " + + "toddler toe together toilet token tomato tomorrow tone tongue tonight tool tooth top topic topple torch tornado tortoise toss total " + + "tourist toward tower town toy track trade traffic tragic train transfer trap trash travel tray treat tree trend trial tribe trick " + + "trigger trim trip trophy trouble truck true truly trumpet trust truth try tube tuition tumble tuna tunnel turkey turn turtle twelve " + + "twenty twice twin twist two type typical ugly umbrella unable unaware uncle uncover under undo unfair unfold unhappy uniform unique unit " + + "universe unknown unlock until unusual unveil update upgrade uphold upon upper upset urban urge usage use used useful useless usual " + + "utility vacant vacuum vague valid valley valve van vanish vapor various vast vault vehicle velvet vendor venture venue verb verify " + + "version very vessel veteran viable vibrant vicious victory video view village vintage violin virtual virus visa visit visual vital vivid " + + "vocal voice void volcano volume vote voyage wage wagon wait walk wall walnut want warfare warm warrior wash wasp waste water wave way " + + "wealth weapon wear weasel weather web wedding weekend weird welcome west wet whale what wheat wheel when where whip whisper wide width " + + "wife wild will win window wine wing wink winner winter wire wisdom wise wish witness wolf woman wonder wood wool word work world worry " + + "worth wrap wreck wrestle wrist write wrong yard year yellow you young youth zebra zero zone zoo"); +}); - // Mnemonic +const mnemonicWordCount = [12, 15, 18, 21, 24]; +const mnemonicDictionary = [0, 1, 2, 3, 4, 5, 6, 7, 8]; - const mnemonicWords = await crypto.mnemonic_words({}); - expect(mnemonicWords.words.split(" ").length).toEqual(2048); +test("crypto: mnemonic_from_random", async () => { + const crypto = runner.getClient().crypto; + + let phrase = await crypto.mnemonic_from_random({}); + expect(phrase.phrase.split(" ").length).toEqual(12); for (const dictionary of mnemonicDictionary) { for (const word_count of mnemonicWordCount) { @@ -216,13 +501,24 @@ test("crypto", async () => { })).phrase.split(" ").length).toEqual(word_count); } } - expect((await crypto.mnemonic_from_entropy({ +}); + +test("crypto: mnemonic_from_entropy", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.mnemonic_from_entropy({ entropy: "00112233445566778899AABBCCDDEEFF", dictionary: 1, word_count: 12, - })).phrase) + }); + + expect(result.phrase) .toEqual("abandon math mimic master filter design carbon crystal rookie group knife young"); +}); +test("crypto: mnemonic_verify", async () => { + const crypto = runner.getClient().crypto; + for (const dictionary of mnemonicDictionary) { for (const word_count of mnemonicWordCount) { expect((await crypto.mnemonic_verify({ @@ -236,108 +532,172 @@ test("crypto", async () => { } } - expect((await crypto.mnemonic_verify({phrase: "one two"})).valid).toBeFalsy(); + expect((await crypto.mnemonic_verify({ phrase: "one two" })).valid).toBeFalsy(); +}); +test("crypto: mnemonic_derive_sign_keys", async () => { + const crypto = runner.getClient().crypto; + const keys = await crypto.mnemonic_derive_sign_keys({ phrase: "unit follow zone decline glare flower crisp vocal adapt magic much mesh cherry teach mechanic rain float vicious solution assume hedgehog rail sort chuckle", dictionary: 0, word_count: 24, }); - const ton_public = await crypto.convert_public_key_to_ton_safe_format({public_key: keys.public}); - expect(ton_public.ton_public_key).toEqual("PuYTvCuf__YXhp-4jv3TXTHL0iK65ImwxG0RGrYc1sP3H4KS"); - let phrase = await crypto.mnemonic_from_random({}); - expect(phrase.phrase.split(" ").length).toEqual(12); - phrase = await crypto.mnemonic_from_random({ - dictionary: 0, - word_count: 12, - }); - expect(phrase.phrase.split(" ").length).toEqual(12); - phrase = await crypto.mnemonic_from_random({ - dictionary: 1, - word_count: 12, + + expect(keys).toEqual({ + public: "13bc2b9ffff617869fb88efdd35d31cbd222bae489b0c46d111ab61cd6c3f71f", + secret: "a32820391c3fc73ad9d145f9b269f7d93c93dd91ec70f1930b616e63db0ae7ff" }); - expect(phrase.phrase.split(" ").length).toEqual(12); +}); + +test("crypto: entropy->mnemonic->ton_public_key test", async () => { + const crypto = runner.getClient().crypto; + const entropy = "2199ebe996f14d9e4e2595113ad1e627"; - const phrase2 = await crypto.mnemonic_from_entropy({entropy}); - const public2 = (await crypto.mnemonic_derive_sign_keys({phrase: phrase2.phrase})).public; - const ton_public2 = await crypto.convert_public_key_to_ton_safe_format({public_key: public2}); - expect(ton_public2.ton_public_key).toEqual("PuZdw_KyXIzo8IksTrERN3_WoAoYTyK7OvM-yaLk711sUIB3"); + const phrase = await crypto.mnemonic_from_entropy({ entropy }); + const public2 = (await crypto.mnemonic_derive_sign_keys({ phrase: phrase.phrase })).public; + const ton_public = await crypto.convert_public_key_to_ton_safe_format({ public_key: public2 }); + + expect(ton_public.ton_public_key).toEqual("PuZdw_KyXIzo8IksTrERN3_WoAoYTyK7OvM-yaLk711sUIB3"); +}); + - // HDKeys +// ------------------------- hdkey ------------------------- - const master = await crypto.hdkey_xprv_from_mnemonic({ +test("crypto: hdkey_xprv_from_mnemonic", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.hdkey_xprv_from_mnemonic({ dictionary: 1, word_count: 12, phrase: "abuse boss fly battle rubber wasp afraid hamster guide essence vibrant tattoo", }); - expect(master.xprv) + + expect(result.xprv) .toEqual( "xprv9s21ZrQH143K25JhKqEwvJW7QAiVvkmi4WRenBZanA6kxHKtKAQQKwZG65kCyW5jWJ8NY9e3GkRoistUjjcpHNsGBUv94istDPXvqGNuWpC"); - expect((await crypto.hdkey_secret_from_xprv({xprv: master.xprv})).secret) - .toEqual("0c91e53128fa4d67589d63a6c44049c1068ec28a63069a55ca3de30c57f8b365"); - expect((await crypto.hdkey_public_from_xprv({xprv: master.xprv})).public) - .toEqual("7b70008d0c40992283d488b1046739cf827afeabf647a5f07c4ad1e7e45a6f89"); +}); + +test("crypto: hdkey_secret_from_xprv", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.hdkey_secret_from_xprv({ + xprv: "xprv9s21ZrQH143K25JhKqEwvJW7QAiVvkmi4WRenBZanA6kxHKtKAQQKwZG65kCyW5jWJ8NY9e3GkRoistUjjcpHNsGBUv94istDPXvqGNuWpC", + }); - const child = await crypto.hdkey_derive_from_xprv({ - xprv: master.xprv, + expect(result.secret).toEqual("0c91e53128fa4d67589d63a6c44049c1068ec28a63069a55ca3de30c57f8b365"); +}); + +test("crypto: hdkey_public_from_xprv", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.hdkey_public_from_xprv({ + xprv: "xprv9s21ZrQH143K25JhKqEwvJW7QAiVvkmi4WRenBZanA6kxHKtKAQQKwZG65kCyW5jWJ8NY9e3GkRoistUjjcpHNsGBUv94istDPXvqGNuWpC", + }); + + expect(result.public).toEqual("7b70008d0c40992283d488b1046739cf827afeabf647a5f07c4ad1e7e45a6f89"); +}); + + +test("crypto: hdkey_derive_from_xprv", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.hdkey_derive_from_xprv({ + xprv: "xprv9s21ZrQH143K25JhKqEwvJW7QAiVvkmi4WRenBZanA6kxHKtKAQQKwZG65kCyW5jWJ8NY9e3GkRoistUjjcpHNsGBUv94istDPXvqGNuWpC", child_index: 0, hardened: false, }); - expect(child.xprv) + + expect(result.xprv) .toEqual( "xprv9uZwtSeoKf1swgAkVVCEUmC2at6t7MCJoHnBbn1MWJZyxQ4cySkVXPyNh7zjf9VjsP4vEHDDD2a6R35cHubg4WpzXRzniYiy8aJh1gNnBKv"); - expect((await crypto.hdkey_secret_from_xprv({xprv: child.xprv})).secret) + + expect((await crypto.hdkey_secret_from_xprv({xprv: result.xprv})).secret) .toEqual("518afc6489b61d4b738ee9ad9092815fa014ffa6e9a280fa17f84d95f31adb91"); - expect((await crypto.hdkey_public_from_xprv({xprv: child.xprv})).public) + expect((await crypto.hdkey_public_from_xprv({xprv: result.xprv})).public) .toEqual("b45e1297a5e767341a6eaaac9e20f8ccd7556a0106298316f1272e461b6fbe98"); - const second = await crypto.hdkey_derive_from_xprv_path({ - xprv: master.xprv, +}); + +test("crypto: hdkey_derive_from_xprv_path", async () => { + const crypto = runner.getClient().crypto; + + const result = await crypto.hdkey_derive_from_xprv_path({ + xprv: "xprv9s21ZrQH143K25JhKqEwvJW7QAiVvkmi4WRenBZanA6kxHKtKAQQKwZG65kCyW5jWJ8NY9e3GkRoistUjjcpHNsGBUv94istDPXvqGNuWpC", path: "m/44'/60'/0'/0'", }); - expect(second.xprv) + + expect(result.xprv) .toEqual( "xprvA1KNMo63UcGjmDF1bX39Cw2BXGUwrwMjeD5qvQ3tA3qS3mZQkGtpf4DHq8FDLKAvAjXsYGLHDP2dVzLu9ycta8PXLuSYib2T3vzLf3brVgZ"); - expect((await crypto.hdkey_secret_from_xprv({xprv: second.xprv})).secret) + + expect((await crypto.hdkey_secret_from_xprv({xprv: result.xprv})).secret) .toEqual("1c566ade41169763b155761406d3cef08b29b31cf8014f51be08c0cb4e67c5e1"); - expect((await crypto.hdkey_public_from_xprv({xprv: second.xprv})).public) + expect((await crypto.hdkey_public_from_xprv({xprv: result.xprv})).public) .toEqual("302a832bad9e5c9906422a82c28b39ae465dcd60178480f7309e183ee34b5e83"); - // NaCl ex +}); - const A = await crypto.generate_random_sign_keys(); - const B = await crypto.generate_random_sign_keys(); - const AP = (await crypto.nacl_box_keypair_from_secret_key({secret: A.secret})).public; - const BP = (await crypto.nacl_box_keypair_from_secret_key({secret: B.secret})).public; +// ------------------------- encryption ------------------------- - const encrypted = await crypto.nacl_box({ - secret: A.secret, - their_public: BP, - nonce: "cd7f99924bf422544046e83595dd5803f17536f5c9a11746", - decrypted: "AQID", - }); +test("crypto: chacha20", async () => { + const crypto = runner.getClient().crypto; + const params = { + key: "01".repeat(32), + nonce: "ff".repeat(12), + data: text2base64("Message"), + }; - const decryptedA = await crypto.nacl_box_open({ - secret: A.secret, - their_public: BP, - nonce: "cd7f99924bf422544046e83595dd5803f17536f5c9a11746", - encrypted: encrypted.encrypted, - }); + const encrypted = await crypto.chacha20(params); + const decrypted = await crypto.chacha20({ ...params, data: encrypted.data }); - const decryptedB = await crypto.nacl_box_open({ - secret: B.secret, - their_public: AP, - nonce: "cd7f99924bf422544046e83595dd5803f17536f5c9a11746", - encrypted: encrypted.encrypted, + expect(encrypted.data).toEqual("w5QOGsJodQ=="); + expect(decrypted.data).toEqual(text2base64("Message")); +}); + + +// ------------------------- boxes ------------------------- + +test("crypto: signing_box default", async () => { + const crypto = runner.getClient().crypto; + const keys = { + public: "0335e912a6dc50b5727d332aa389d2aeff86c7b7ae5b6483bb0e425f41ee42c0", + secret: "6d33449a8b5aeff942789ea69574e8254a52688a3f570590933177b8cbe2b82c", + }; + + const signing_box = await crypto.get_signing_box(keys); + + const getPublicKeyResult = await crypto.signing_box_get_public_key(signing_box); + expect(getPublicKeyResult.pubkey).toEqual(keys.public); + + const signResult = await crypto.signing_box_sign({ signing_box: signing_box.handle, unsigned: "" }); + expect(signResult.signature) + .toEqual( + "b254a6c54f48790b039bb5621950a11ca9cfc681d685ed019e6826fdbea3ad21c0e92f667d8270b2ba27fcb5fb57991a2c3bb9ced69fc6893aa1e22bd694fa0c"); +}); + +test("crypto: signing_box custom", async () => { + const crypto = runner.getClient().crypto; + const keys = { + public: "0335e912a6dc50b5727d332aa389d2aeff86c7b7ae5b6483bb0e425f41ee42c0", + secret: "6d33449a8b5aeff942789ea69574e8254a52688a3f570590933177b8cbe2b82c", + }; + + const signing_box = await crypto.register_signing_box({ + get_public_key: () => Promise.resolve({ public_key: keys.public }), + sign: async (params) => await crypto.sign({ keys, unsigned: params.unsigned }), }); - expect(decryptedA.decrypted).toEqual("AQID"); - expect(decryptedB.decrypted).toEqual("AQID"); + const getPublicKeyResult = await crypto.signing_box_get_public_key(signing_box); + expect(getPublicKeyResult.pubkey).toEqual(keys.public); + + const signResult = await crypto.signing_box_sign({ signing_box: signing_box.handle, unsigned: text2base64("abc") }); + expect(signResult.signature).toEqual((await crypto.sign({keys, unsigned: text2base64("abc") })).signature); }); -test("external encryption box", async () => { - const client = runner.getClient(); +test("crypto: external encryption box (register_encryption_box, encryption_box_get_info, encryption_box_encrypt, encryption_box_decrypt)", async () => { + const crypto = runner.getClient().crypto; const encryption_box: AppEncryptionBox = { get_info: async () => { @@ -357,28 +717,56 @@ test("external encryption box", async () => { data: params.data.substr(0, params.data.length / 2), } } - }; - const handle: EncryptionBoxHandle = (await client.crypto.register_encryption_box(encryption_box)).handle; + const handle: EncryptionBoxHandle = (await crypto.register_encryption_box(encryption_box)).handle; - const info: EncryptionBoxInfo = (await client.crypto.encryption_box_get_info({ + const info: EncryptionBoxInfo = (await crypto.encryption_box_get_info({ encryption_box: handle, })).info; expect(info.algorithm).toEqual("duplicator"); - const encrypted: string = (await client.crypto.encryption_box_encrypt({ + const encrypted: string = (await crypto.encryption_box_encrypt({ encryption_box: handle, data: "12345", })).data; expect(encrypted).toEqual("1234512345"); - const decrypted: string = (await client.crypto.encryption_box_decrypt({ + const decrypted: string = (await crypto.encryption_box_decrypt({ encryption_box: handle, data: encrypted, })).data; expect(decrypted).toEqual("12345"); -}); \ No newline at end of file +}); + +// ------------------------- ---- ------------------------- + +// Intentionally disabled (was created for react-native, shouldn't go to the master branch) +test.skip("crypto: encrypt large blocks", async () => { + const client = runner.getClient(); + const ourKeys = await client.crypto.nacl_box_keypair(); + const theirKeys = await client.crypto.nacl_box_keypair(); + + async function testBuffer() { + const nonce = Buffer.from((await client.crypto.generate_random_bytes({length: 24})).bytes, "base64").toString("hex"); + const decrypted = (await client.crypto.generate_random_bytes({length: 100000000})).bytes; + const encrypted = (await client.crypto.nacl_box({ + decrypted: decrypted, + secret: ourKeys.secret, + their_public: theirKeys.public, + nonce, + })).encrypted; + const decrypted2 = (await client.crypto.nacl_box_open({ + encrypted, + secret: theirKeys.secret, + their_public: ourKeys.public, + nonce, + })).decrypted; + expect(decrypted2).toEqual(decrypted); + } + + await Promise.all([0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(_ => testBuffer())); +}); diff --git a/packages/tests/src/tests/net.ts b/packages/tests/src/tests/net.ts index 7b8f6e47..190c6e54 100644 --- a/packages/tests/src/tests/net.ts +++ b/packages/tests/src/tests/net.ts @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 TON DEV SOLUTIONS LTD. + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. * * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the @@ -14,9 +14,11 @@ * limitations under the License. */ -import {ABIVersions, runner} from "../runner"; -import {expect, jest, test} from "../jest"; -import {contracts} from "../contracts"; +import { AggregationFn, SortDirection } from "@tonclient/core"; + +import { ABIVersions, runner } from "../runner"; +import { expect, jest, test } from "../jest"; +import { contracts } from "../contracts"; test("net", async () => { const net = runner.getClient().net; @@ -31,51 +33,143 @@ enum Collection { blocks = "blocks", } -test("Block signatures", async () => { +test("net: query_collection - Block signatures", async () => { const net = runner.getClient().net; - const signatures = await net.query_collection({ + + const signaturesQuery = await net.query_collection({ collection: Collection.blocks_signatures, filter: {}, result: "id", limit: 1, }); - expect(signatures.result.length).toBeGreaterThanOrEqual(0); + + expect(signaturesQuery.result.length).toBeGreaterThanOrEqual(0); }); -test("All Accounts", async () => { +test("net: query_collection - Accounts", async () => { const net = runner.getClient().net; - const docs = await net.query_collection({ + + const accountsQuery = await net.query_collection({ collection: Collection.accounts, filter: {}, result: "id balance", + limit: 1, }); - expect(docs.result.length).toBeGreaterThan(0); + + expect(accountsQuery.result.length).toBeGreaterThan(0); }); -// Skipped because of missing id -test.skip("Message", async () => { +test("net: query_collection - Messages", async () => { const net = runner.getClient().net; - const messages = await net.query_collection({ + + const messagesQuery = await net.query_collection({ collection: Collection.messages, - filter: { - id: {eq: "3a8e38b419a452fe7a0073e71c083f926055d0f249485ab9f8ca6e9825c20b8c"}, - }, - result: "body created_at", + filter: {}, + result: "id", + limit: 1, + }); + + expect(messagesQuery.result.length).toBeGreaterThan(0); +}); + +test("net: query_collection - Transactions", async () => { + const net = runner.getClient().net; + + const transactionsQuery = await net.query_collection({ + collection: Collection.transactions, + filter: {}, + result: "id", + limit: 1, + }); + + expect(transactionsQuery.result.length).toBeGreaterThan(0); +}); + +test("net: query_collection - Blocks", async () => { + const net = runner.getClient().net; + + const blocksQuery = await net.query_collection({ + collection: Collection.blocks, + filter: {}, + result: "id", + limit: 1, }); - expect(messages.result[0].header.ExtOutMsgInfo.created_at).toEqual(1562342740); + + expect(blocksQuery.result.length).toBeGreaterThan(0); }); -test("Ranges", async () => { +test("net: query_collection - Ranges", async () => { const net = runner.getClient().net; + const messages = await net.query_collection({ collection: Collection.messages, filter: {created_at: {gt: 1562342740}}, result: "body created_at", }); + expect(messages.result[0].created_at).toBeGreaterThan(1562342740); }); -test("Wait For", async () => { +const testCountAggregation = async (collection: string, count: number) => { + const net = runner.getClient().net; + + const result = await net.aggregate_collection({ + collection, + filter: {}, + fields: [{ + field: "id", + fn: AggregationFn.COUNT, + }], + }); + + expect(Number(result.values[0])).toBeGreaterThanOrEqual(count); +}; + +test("net: aggregate_collection - count", async () => { + await testCountAggregation(Collection.blocks_signatures, 0); + await testCountAggregation(Collection.accounts, 1); + await testCountAggregation(Collection.blocks, 1); + await testCountAggregation(Collection.messages, 1); + await testCountAggregation(Collection.transactions, 1); +}); + +const testAggregateFunctions = async (collection: string, field: string) => { + const net = runner.getClient().net; + + const result = await net.aggregate_collection({ + collection, + filter: {}, + fields: [ + { + field, + fn: AggregationFn.MIN, + }, + { + field, + fn: AggregationFn.MAX, + }, + { + field, + fn: AggregationFn.SUM, + }, + { + field, + fn: AggregationFn.AVERAGE, + }, + ], + }); + + expect(result.values[0]).toBeDefined(); + expect(result.values[1]).toBeDefined(); + expect(result.values[2]).toBeDefined(); + expect(result.values[3]).toBeDefined(); +}; + +test("net: aggregate_collection - Account balance", async () => { + await testAggregateFunctions(Collection.accounts, "balance"); +}); + +test("net: wait_for_collection", async () => { const net = runner.getClient().net; const data = await net.wait_for_collection({ collection: Collection.transactions, @@ -93,8 +187,8 @@ const transactionWithAddresses = ` in_message { dst src value } `; -test.each(ABIVersions)("Subscribe for transactions with addresses (ABIv%i)", async (abiVersion) => { - const {net} = runner.getClient(); +test.each(ABIVersions)("net: Subscribe (subscribe_collection) for transactions with addresses (ABIv%i)", async (abiVersion) => { + const net = runner.getClient().net; const wallet = await runner.getAccount(contracts.WalletContract, abiVersion); await runner.sendGramsTo(await wallet.getAddress()); @@ -110,18 +204,21 @@ test.each(ABIVersions)("Subscribe for transactions with addresses (ABIv%i)", asy transactions.push(d.result); })).handle; - //hack for Windows-assembled TON NODE SE - //issue: https://github.com/tonlabs/tonos-se/issues/13 + // hack for Windows-assembled TON NODE SE + // issue: https://github.com/tonlabs/tonos-se/issues/13 await new Promise((resolve=>setTimeout(resolve, 5_000))); await wallet.deploy(); await new Promise(resolve => setTimeout(resolve, 1_000)); await net.unsubscribe({handle: subscription}); + expect(transactions.length).toBeGreaterThan(0); }); -test.each(ABIVersions)("Subscribe for messages (ABI v%i)", async (abiVersion) => { +// This is a filter test +test.each(ABIVersions)("net: Subscribe (subscribe_collection) for messages (ABI v%i)", async (abiVersion) => { const {net} = runner.getClient(); + const docs = []; const subscription = (await net.subscribe_collection({ collection: Collection.messages, @@ -136,40 +233,43 @@ test.each(ABIVersions)("Subscribe for messages (ABI v%i)", async (abiVersion) => )).handle; const wallet = await runner.getAccount(contracts.WalletContract, abiVersion); - await runner.sendGramsTo(await wallet.getAddress()); - await wallet.deploy(); + await runner.deploy(wallet); await net.unsubscribe({handle: subscription}); + expect(docs.length).toEqual(0); }); -test("Transactions with addresses", async () => { +test("net: Query (query_collection) transactions with addresses", async () => { const net = runner.getClient().net; - const tr = (await net.query_collection({ + + const queryResult = await net.query_collection({ collection: Collection.transactions, filter: {}, result: transactionWithAddresses, - })).result[0]; - expect(tr).toBeTruthy(); + }); + + expect(queryResult.result[0]).toBeTruthy(); }); // Skipped explicitly as disabled test.skip("Subscribe for failed server", async () => { // const net = runner.getClient().net; - // console.log('>>>', 'Subscribed'); + // console.log(">>>", "Subscribed"); // tests.client.queries.accounts.subscribe( // { // id: { eq: "-1:3333333333333333333333333333333333333333333333333333333333333333" } // }, - // 'id balance', + // "id balance", // (e, d) => { - // console.log('>>>', e, d); + // console.log("">>>", e, d); // }); // await new Promise((resolve) => { // setTimeout(resolve, 100_000); // }) }); + const shardHashesQuery = ` workchain_id master { @@ -181,13 +281,15 @@ const shardHashesQuery = ` } `; -test("Check shard_hashes greater then 0", async () => { +test("net: Check (query_collection) shard_hashes greater then 0", async () => { const net = runner.getClient().net; + const queryResult = await net.query_collection({ collection: Collection.blocks, filter: {}, result: shardHashesQuery, }); + expect(queryResult.result.length).toBeGreaterThan(0); }); @@ -196,7 +298,7 @@ test.skip("Subscribe for accounts", async () => { // const net = runner.getClient().net; // const { queries } = tests.client; // const subscriptions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - // .map(i => queries.accounts.subscribe({}, 'id code data', (e, doc) => { + // .map(i => queries.accounts.subscribe({}, "id code data", (e, doc) => { // console.log(i, doc.id); // })); // await new Promise(resolve => setTimeout(resolve, 1000_000)); @@ -218,3 +320,89 @@ test.skip("Long time subscription", async () => { await new Promise(resolve => setTimeout(resolve, 1_000_000)); await net.unsubscribe({handle: subscription.handle}); }); + +test("net: Validator set", async () => { + if (runner.config.network?.endpoints?.[0].startsWith("http://localhost")) { + console.log("Skipping validator set test on localhost"); + return; + } + + const net = runner.getClient().net; + + // test https://docs.ton.dev/86757ecb2/p/978847-get-config + const result = await net.query_collection({ + collection: Collection.blocks, + filter: {}, + order: [{ + path: "seq_no", + direction: SortDirection.DESC, + }], + limit: 1, + result: "prev_key_block_seqno", + }); + expect(result.result.length) + .toEqual(1); + const seq_no = result.result[0].prev_key_block_seqno; + expect(seq_no) + .toBeGreaterThanOrEqual(0); + + // no masterblock before first election and seq_no = 0 + if (seq_no > 0) { + const config = await net.query_collection({ + collection: Collection.blocks, + filter: { + seq_no: { eq: seq_no }, + workchain_id: { eq: -1 }, + }, + result: "master { config { p15 { validators_elected_for elections_start_before elections_end_before stake_held_for } p16 { max_validators max_main_validators min_validators } p17 { min_stake max_stake min_total_stake max_stake_factor } p34 { utime_since utime_until total total_weight list { public_key adnl_addr weight } } } }", + }); + expect(config.result.length) + .toEqual(1); + const p15ConfigParams = config.result[0].master.config.p15; + expect(p15ConfigParams.validators_elected_for) + .toBeGreaterThan(0); + expect(p15ConfigParams.elections_start_before) + .toBeGreaterThan(0); + expect(p15ConfigParams.elections_end_before) + .toBeGreaterThan(0); + expect(p15ConfigParams.stake_held_for) + .toBeGreaterThan(0); + + const p16ConfigParams = config.result[0].master.config.p16; + expect(BigInt(p16ConfigParams.max_validators)) + .toBeGreaterThan(BigInt(p16ConfigParams.min_validators)); + expect(BigInt(p16ConfigParams.max_validators)) + .toBeGreaterThanOrEqual(p16ConfigParams.max_main_validators); + + const p17ConfigParams = config.result[0].master.config.p17; + expect(p17ConfigParams.min_stake) + .toBeDefined(); + expect(p17ConfigParams.max_stake) + .toBeDefined(); + expect(BigInt(p17ConfigParams.min_stake)) + .toBeLessThanOrEqual(BigInt(p17ConfigParams.max_stake)); + expect(BigInt(p17ConfigParams.min_total_stake)) + .toBeLessThanOrEqual(BigInt(p17ConfigParams.max_stake)); + expect(p17ConfigParams.min_total_stake) + .toBeDefined(); + expect(p17ConfigParams.max_stake_factor) + .toBeDefined(); + + const validatorSetList = config.result[0].master.config.p34.list; + const p34ConfigParams = config.result[0].master.config.p34; + expect(p34ConfigParams.total) + .toEqual(validatorSetList.length); + let weight = BigInt(0); + for (let i = 0; i < validatorSetList.length; i++) { + expect(validatorSetList[i].adnl_addr) + .toBeDefined(); + expect(validatorSetList[i].public_key) + .toBeDefined(); + expect(validatorSetList[i].public_key.length) + .toEqual(64); + weight += BigInt(validatorSetList[i].weight); + } + expect(BigInt(p34ConfigParams.total_weight)) + .toEqual(weight); + } +}); diff --git a/packages/tests/src/tests/tvm.ts b/packages/tests/src/tests/tvm.ts new file mode 100644 index 00000000..1ced15e2 --- /dev/null +++ b/packages/tests/src/tests/tvm.ts @@ -0,0 +1,206 @@ +/* + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. + * + * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at: + * + * http://www.ton.dev/licenses + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific TON DEV software governing permissions and + * limitations under the License. + */ + +import { ABIVersions, runner } from "../runner"; +import { expect, test } from "../jest"; +import { contracts } from "../contracts"; + +import { + ELECTOR_ADDRESS, + ELECTOR_ACCOUNT, + ELECTOR_PARTICIPANT_LIST_AS_PLAIN_LIST, + ELECTOR_PARTICIPANT_LIST_AS_TUPLE_LIST, +} from "./tvm_consts"; + +test("tvm: run_get", async () => { + const tvm = runner.getClient().tvm; + + const tuple_participant_list = await tvm.run_get({ + account: ELECTOR_ACCOUNT, + function_name: "participant_list", + }); + + expect(JSON.stringify(tuple_participant_list.output)) + .toEqual(ELECTOR_PARTICIPANT_LIST_AS_TUPLE_LIST); + + const plain_participant_list = await tvm.run_get({ + account: ELECTOR_ACCOUNT, + function_name: "participant_list", + tuple_list_as_array: true, + }); + + expect(JSON.stringify(plain_participant_list.output)) + .toEqual(ELECTOR_PARTICIPANT_LIST_AS_PLAIN_LIST); + + const result_with_input = await tvm.run_get({ + account: ELECTOR_ACCOUNT, + input: `0x${ELECTOR_ADDRESS.split(':')[1]}`, + function_name: "compute_returned_stake", + }); + + expect(result_with_input.output[0]).toEqual("0"); + + const resultPastElectionsId = await tvm.run_get({ + account: ELECTOR_ACCOUNT, + function_name: "past_elections", + }); + + const pastElectionsId = resultPastElectionsId.output[0][0][0]; + expect(pastElectionsId).toEqual("1588268660"); +}); + +function replaceBigIntsWithNonZeroFlags(fees: { [key: string]: any }) { + Array.from(Object.entries(fees)) + .forEach(([key, value]) => { + const s = (value || "").toString(); + fees[key] = s !== "" && s !== "0" && s !== "0x0"; + }); +} + +test.each(ABIVersions)("tvm: run_tvm and run_executor (ABIv%i)", async (abiVersion) => { + const { + abi, + processing, + tvm, + } = runner.getClient(); + + const walletAddress = "0:2222222222222222222222222222222222222222222222222222222222222222"; + const subscriptionAccount = await runner.getAccount(contracts.Subscription, abiVersion, undefined, { wallet: walletAddress }); + const accountAddress = await subscriptionAccount.getAddress(); + await runner.deploy(subscriptionAccount); + + const getWalletMessage = await abi.encode_message({ + address: accountAddress, + abi: subscriptionAccount.abi, + call_set: { + function_name: "getWallet", + input: {}, + }, + signer: subscriptionAccount.signer, + }); + + const getWalletResult = await tvm.run_tvm({ + account: await subscriptionAccount.boc(), + message: getWalletMessage.message, + abi: subscriptionAccount.abi, + }); + + expect(getWalletResult.decoded?.output).toEqual({ + value0: "0:2222222222222222222222222222222222222222222222222222222222222222", + }); + + const subscriptionParams = { + subscriptionId: "0x1111111111111111111111111111111111111111111111111111111111111111", + pubkey: "0x2222222222222222222222222222222222222222222222222222222222222222", + to: "0:3333333333333333333333333333333333333333333333333333333333333333", + value: "0x123", + period: "0x456", + }; + + const subscribeMessage = await abi.encode_message({ + address: accountAddress, + abi: subscriptionAccount.abi, + call_set: { + function_name: "subscribe", + input: subscriptionParams, + }, + signer: subscriptionAccount.signer, + }); + + const subscribeResult = await tvm.run_executor({ + account: { + type: "Account", + boc: await subscriptionAccount.boc(), + }, + abi: subscriptionAccount.abi, + message: subscribeMessage.message, + return_updated_account: true, + }); + + replaceBigIntsWithNonZeroFlags(subscribeResult.fees); + expect(subscribeResult).toEqual(expect.objectContaining({ + decoded: expect.objectContaining({ + output: null, + }), + fees: expect.objectContaining({ + in_msg_fwd_fee: true, + gas_fee: true, + out_msgs_fwd_fee: false, + total_account_fees: true, + total_output: false, + }), + transaction: expect.objectContaining({ + account_addr: accountAddress, + }), + })); + + const getSubscriptionMessage = await abi.encode_message({ + address: accountAddress, + abi: subscriptionAccount.abi, + call_set: { + function_name: "getSubscription", + input: { + subscriptionId: subscriptionParams.subscriptionId, + }, + }, + signer: subscriptionAccount.signer, + }); + + const getSubscriptionResult = await tvm.run_tvm({ + account: subscribeResult.account, + abi: subscriptionAccount.abi, + message: getSubscriptionMessage.message, + }); + + expect(getSubscriptionResult.decoded?.output?.value0?.pubkey) + .toEqual(subscriptionParams.pubkey); + + const pubkey2 = "0x3333333333333333333333333333333333333333333333333333333333333333"; + await processing.process_message({ + message_encode_params: { + address: accountAddress, + abi: subscriptionAccount.abi, + signer: subscriptionAccount.signer, + call_set: { + function_name: "subscribe", + input: { ...subscriptionParams, pubkey: pubkey2 }, + }, + }, + send_events: false, + }); + + const getSubscriptionMessage2 = await abi.encode_message({ + address: accountAddress, + abi: subscriptionAccount.abi, + call_set: { + function_name: "getSubscription", + input: { + subscriptionId: subscriptionParams.subscriptionId, + }, + }, + signer: subscriptionAccount.signer, + }); + + subscriptionAccount.dropCachedData(); + const getSubscriptionResult2 = await tvm.run_tvm({ + account: await subscriptionAccount.boc(), + abi: subscriptionAccount.abi, + message: getSubscriptionMessage2.message, + }); + + expect(getSubscriptionResult2.decoded?.output?.value0?.pubkey) + .toEqual(pubkey2); +}); diff --git a/packages/tests/src/tests/tvm_consts.ts b/packages/tests/src/tests/tvm_consts.ts new file mode 100644 index 00000000..f14c0f9a --- /dev/null +++ b/packages/tests/src/tests/tvm_consts.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. + * + * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at: + * + * http://www.ton.dev/licenses + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific TON DEV software governing permissions and + * limitations under the License. + */ + +export const ELECTOR_ADDRESS = "-1:3333333333333333333333333333333333333333333333333333333333333333"; + +export const ELECTOR_ACCOUNT = "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"; + +export const ELECTOR_PARTICIPANT_LIST_AS_TUPLE_LIST = '[[["0x0101b6d65a384b9c70deb49fd6c43ffc0f60ed22fcc3a4966f7043794a749228","60138000000000"],[["0x03de5d8590fe6ad191bf94d4136dfb630e9b3447bb2f1a6ae2d8e3e4cbee1d9f","61000000000000"],[["0x0558f90c0682d677b46005ce2e04206c255ea9a05bfac0ff5aea9d7182a28913","60138000000000"],[["0x07698228973a595751d79e1fafd5a4145b3d35349bf0b43322afb61b138f01eb","60138000000000"],[["0x09d1ef8a40a9fbf1ca505f072258048ec15e0637baa085649d77b9a90220003e","60138000000000"],[["0x0ac21ef27c8ed4487270f1c45e99dac091ca4007951217ece344452df7047e5a","60138000000000"],[["0x0aed529418ab67a31a4b98c224f8fdb2fec11f0100c23751e67b312cba11fb23","60138000000000"],[["0x0bd14cdade9067c523f44fd208dee5daa7d852151725d713b92c840a031018a6","60138000000000"],[["0x0bddff0d98f42a3155e577a5579623a911e3b03401835166553f88375cbd9657","60138000000000"],[["0x12893bbd649bf2e1e79cb084025638cdd7906eebca40efeee7fdbd548cc96391","60138000000000"],[["0x15546bc7b5124f6d83d6c5a62b8890a48b933168e141c01229431a6c0c499780","60138000000000"],[["0x1cbea6a399ba200958db255579cda2195006f3a3108b2d6ef7e258e42c101479","60138000000000"],[["0x1f8ee6ba2902715804c769c3845b6b3a37802e462e8df63ba19f827a92dbbda0","60138000000000"],[["0x1fdd556d84d1d9f24a739c2600ec72256cc00920d85ad3a2edb3e0d72146789d","60138000000000"],[["0x20f20c2cfa4d72afb9c5f64d4735070962b3323b3629892c75f56427f175ebe4","60138000000000"],[["0x219d32737b0f3769869b8fa750ba8e3cd9f19b21a4d669c7c79d420d7f7cdb24","60138000000000"],[["0x2615b4aeb69140531228248a9d84593117b64e22d462e3968e39c1840a260523","60138000000000"],[["0x26984c9f04bc1889061e98bd9caf6955f750219d8e8dbc0986feb9d770e5a15b","60138000000000"],[["0x28bb07d80e20aa624ae47dfe53f915b23a666bf825ff283bac06c14bea1eaa74","60138000000000"],[["0x2a23566008fd4f87105b09d02c739452e45187fbada5e7e52ada356264cd6751","60138000000000"],[["0x2dcc70859876106b21b598ba9a10c9932259c36f44adeb95a178e67f6afd2f7a","60138000000000"],[["0x30b854226ef943d738d2dfcc72ede3b39d08604ca7211abf3c76f488441c77fc","60138000000000"],[["0x31bb74a5a53769d3db789d961375ea569d4da0bd6ac2b12f830dd6be81968ef1","60138000000000"],[["0x334f22e0de2e24a070fec7c1d77d7a988a79d66b79e2d654310a963964edd337","60138000000000"],[["0x36c44eddf773390cdb42f93f8454ea9c7ca45aa8948346df8f642a59ce44c442","60138000000000"],[["0x3d29d2b5ceef46703255ce8cfe3aad3c4fefd3a2025e5a48ce78a63f20887eff","60138000000000"],[["0x41b047a20ed691e9376f7f2f60d6571290e34ef4e1b85467dcc3d7c0cf7fae90","60138000000000"],[["0x41e7541c377b58a0cfc4ca954731e971f6dc9fa6806eaa1709d011d3d32593ce","60138000000000"],[["0x426a52d3b3d016451c46b3a0eacb382fbbb38739e00d041d4038f795a54e25b5","60138000000000"],[["0x42f89915ca540af691f623f201b616caa7f5e104f8293698f8b46c4e7bb5b292","60138000000000"],[["0x4449521e793b02b036ce698c3af951e9548cd5b862b704fa5cc9e80b171a3c61","60138000000000"],[["0x492f4fee6a035a09e9ff09d65a65768899b04797cff08dc2c64ae11cd94d1968","60138000000000"],[["0x4947018f9c0c9302b2783eb5edacb76ccae3b5c5a2f6355b5b51afd1a18075f8","60138000000000"],[["0x4c27708e4ce81a0bbeb315ece024ba495f3e3fab5f83a2941b7731a58ad32160","60138000000000"],[["0x5059d40f80f578c3c384239415f54af35ab4dbdea0251618d4c3c7b4937e7e69","60138000000000"],[["0x5191f8cbfe1ce25a68c337ede75638321374112b868584092a335f83caad59a4","60138000000000"],[["0x531296c32ea64d09dcb44ff0b99843dc9855143c70b9fad42deb33881525fb84","60138000000000"],[["0x54c9860aa34ddba2a16e4f4271e1771f61f1e8a7a116fbbfa62f0e535b95559e","60138000000000"],[["0x54ce2d6b35d0d670e37fcd533ff17c2116e0acead719194e46d478944b33108e","60138000000000"],[["0x576af5e4af963a0caa957629d009906119e418e7f7778f5a55d41c0905b73a4f","60138000000000"],[["0x59905476f4781f6a79359079bfa3fe295c65d6b918afadbc352edcdb558ad094","60138000000000"],[["0x5a4e95cdf94bed240ebabded084b70b2548601686d94a751f240aedd2032e4f2","60138000000000"],[["0x5a7500f11becf6741fe5624d2298f6b830ead261871a48a81f80bf9be09ed866","180000000000000"],[["0x5c26942bf33c49485db3b2693e5d582708b44705f712c4e24af1ee84744c079e","60138000000000"],[["0x5fcdcc107e81ef4399c9d603a25fcba75cb78f1fa1bafd3acb39e3521d7fc9ce","60138000000000"],[["0x6107f5b2974fabf6f0aa1a7898340b3f76c4ba272b95a3e4bb809c1d529b6997","60138000000000"],[["0x647b9a476f733ec5ee9cbc0bfb021335cd3166b9aaa8ad27ed0f88d9f6bf9dbe","60138000000000"],[["0x658c461d8dad54a5a9cbbbb2711920a541ba58003e7029cc228dfdfbc17ede3f","60138000000000"],[["0x661336351b889e0124fbc19f9f35f6a0f6e8c4fa9b89e9ef527718bd6aa254be","60138000000000"],[["0x6852746bbfb41e556daae99d375b2839ad62b35355c3b9fbbd54b4946ae2050f","60138000000000"],[["0x68ad3d98642913848b605dbad3f1df971f21908d360c37af4a493e9b4646b45e","60138000000000"],[["0x6c07c6be93940a83b30514b21531fd3dd204bb89e7f77b5a2421a41d4e85c74d","60138000000000"],[["0x6d4ad504054f292b7f66c7ed32f3b123bfa5c7be9c45faf26d77ad85efa64a38","60138000000000"],[["0x6e77d45d07651565be5cdb11b80c91fa18def0a434f246c0b25bb50fc4877dd4","60138000000000"],[["0x738600c570c19ef1b91bf2cd83709d71899c246bfabb5b08dc70fe32b5c81f7d","60138000000000"],[["0x7469b663b9fa7be185aa1819bfb48a4eda6e4d8af33e1955d95fa5e156d50f12","60138000000000"],[["0x77410e09363239b0999198a701e37f75775cc55049ba541497967f5d8ba74ef4","60138000000000"],[["0x781c96175cf45b791142326964347095fd0fbdd3c8579c42cea108798e025152","60138000000000"],[["0x79b43e9c18241636ae7c554097bb4bc5da03249bee67abe5366a5b093b708cab","60138000000000"],[["0x7ad807b91790868497768476e8c8e6b53ff9b1a91fbfe6a7edae8de0307a8157","60138000000000"],[["0x8308ff2b214d509d3781d7361a7ccb5f4fb976f8e386ce3c9082bcad8805d13d","60138000000000"],[["0x845a0fff44669c941475eb3f3ffd6e065ee94cfbfdcb820877744d6f9647a5d0","49899000000000"],[["0x88700f083f3bc7971c348de8357ee36b2551d8cdb7ea4b4e4e8aae558d67a231","60138000000000"],[["0x8b08c457cac18642f49ab7de0ef7551b93e11dbc2979062f22b271b890e8d2f0","60138000000000"],[["0x8bc840e0c5a98e608e70307ada41aa94a745a51f6065111942021a4e601dc328","60138000000000"],[["0x93e518529faa2244ee1bdc24a5459d4b3d2047f8756b12636e2cba3b766ec201","60138000000000"],[["0x993d90fac526bdad11549104105452e9198da8d485dbb4af17b044a721fa8b82","60138000000000"],[["0x9997880b1dcc011ce4fefeb587eca16c027c81aafeb4305d3a1755182c269b5e","60138000000000"],[["0x9d998de650f13c85da4ab08de0fa7960771d4269081fa1ed1f9940c5cd8bb57c","60138000000000"],[["0x9fab138505d28c3c2d68509c5414abe933ab7de90610d8cc84edaf380e739f48","60138000000000"],[["0x9fd585f4d71c50ff54b69255ddbaa4a30eae31cde2d02ba6d4c0f87faf288f9a","60138000000000"],[["0xa42d598e3d6c051880488bfd139705c9853ff2e93046c6e096eba5f5b8dd714e","60138000000000"],[["0xa6e3ff7b1f340f7d02a1b64ade185c9039cd2751ef47ac5d7950b527b377d566","60138000000000"],[["0xa79d52472a9343b4f91c61b7e065cd736844064c11188fb86fef32447b163462","60138000000000"],[["0xa82bdb918a99f7192b0ebe745f04217991d2077dc43ffe75956782f55c7c9805","60138000000000"],[["0xa87f60cfad2f10ec420d4660d98a43a1105a867aac63a2724075f155b991fd35","60138000000000"],[["0xad1e503c43f7f62bb672b234eb1510b8ccef4d23b6de1f53a8a0d738c961cee2","60138000000000"],[["0xad8dd15447ac5c3b0ac9ed9ebae3b32cfe3cda5442bfce7843443a353701eb34","60138000000000"],[["0xade82619842d2257fb19097c990b77818f2352e3809b9c179b3b66989b8e01c3","60138000000000"],[["0xb739a017b3b9c9577eeba0d3b94fe2027333ccdad378f0aa67b441eb8cbd675a","60138000000000"],[["0xb85ed5c5a48abadc5bf4a85185f781aa60eebe7ef20642f660c7e90d481984cf","60138000000000"],[["0xbcb7406f71b46a5171b822f609d50df1a485bbae832f76db0a356b243616ca8e","60138000000000"],[["0xbedc1da66f906866cf8af7e57cda645018c39d1b028e9ed4682643941c940348","60138000000000"],[["0xc2660177ec158c05676b396baab45f8f8a63f74a0eaf1a7cfe011c7eea0cd8a4","57180000000000"],[["0xc536058376e87f6481ce31b0e088235b4be9df00145c97081c45a28cce64c684","60138000000000"],[["0xc8fd550fcf32a9ea6aef295e788e4394e744f0939ab5fa8b9009577e274a477e","60138000000000"],[["0xcbfe056a9e9fafd246a8fa3025c3d870dac6b01f68adc847f03277eac906452f","60138000000000"],[["0xd023735d89cb9d29c5301b87f00d3f7a42aa6f0320086473e50b7e3b8b9acb12","60138000000000"],[["0xd17002d5872d62876fc4cae771c472ececae0b50820d760718a753acf431f31c","60138000000000"],[["0xd847bac558e925bd87b15a9e8c077df36537e6fc52d5c2019004c0c570fd0266","60138000000000"],[["0xdab17536c875995ce144f17771c79e7e9d6adcaaa66cce64947c8d17a363a2dd","60138000000000"],[["0xdf0b5c031ece9dadfd23c63a41e4e7f1ae4138b157ff7588c21083853d585789","60138000000000"],[["0xe087dae3faaf4748c8bcd237ca7ece5f8bcddf6b60db216252c5e29a7f6a33a4","60138000000000"],[["0xe09755d90d62160409b67ff28584f2800087f9063d76b3240c4d3f94f0880c41","60138000000000"],[["0xe23c47f9c2e9d2d87d9f1fcc0352ef28d13f322f8003d4210bb33692e77fd988","60138000000000"],[["0xeb2269b0ea934046a59399bd824f6a7fae4c7d696bb163e1bd235cbc21aa2b55","60138000000000"],[["0xedf8d203bebac7d629840ca0a704cbff92607d6bf538bc99ab65fada6a7b3c65","60138000000000"],[["0xf179ca30b1a9c8c33e33863b04d8d0078dddbf974a1e8666d072e0403c997f21","60138000000000"],[["0xf199c75a842c96c459272502b7010a78f29e7d00ad649f7756dd11c8d321a97c","60138000000000"],[["0xf19a880d58384bfaf5c839e7b6502ff7e6dc11cc38a77f651c948ea8475a37f7","60138000000000"],[["0xf25841168bb223f03cc01f5934474e56ae3ac0307a048ab167326c7d655c25db","86829000000000"],[["0xf25e305cc44404dae89a8f3b577cf94c367ab28a8ebc81a5c551d39303e254c2","60138000000000"],[["0xf3c0eed928d059ec9c99fd55ef4df9ccdaa30626e14b833f064936099b8088e1","60138000000000"],[["0xf6fe4424c9df211b1d2e92f7a91889aac643c605de458fa8f2af90534b885654","154754000000000"],[["0xf8a26375e76f1f8ff763787507fe2e01ed257b1a6b1772e48338606862a80da4","60138000000000"],[["0xfb471071aa87f25465da8c98bdeb1b24165b4a1694c5a6ab9f59eb57ce9e451d","60138000000000"],[["0xfb66f351a3b27e1702a21bfd189f3db5053f9f0089b26e7f05218fa87b925e2e","60138000000000"],[["0xfbcd15956e466a3c945c8bee6e6bc6bdab6b1b2ec0c07a3ba431091795751bef","60138000000000"],[["0xfced4379f1cb13157b34d50301a65ab47dc3452f4cd0e2a2d8e0b33a07350f43","60138000000000"],null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'; + +export const ELECTOR_PARTICIPANT_LIST_AS_PLAIN_LIST = '[{"type":"List","value":[["0x0101b6d65a384b9c70deb49fd6c43ffc0f60ed22fcc3a4966f7043794a749228","60138000000000"],["0x03de5d8590fe6ad191bf94d4136dfb630e9b3447bb2f1a6ae2d8e3e4cbee1d9f","61000000000000"],["0x0558f90c0682d677b46005ce2e04206c255ea9a05bfac0ff5aea9d7182a28913","60138000000000"],["0x07698228973a595751d79e1fafd5a4145b3d35349bf0b43322afb61b138f01eb","60138000000000"],["0x09d1ef8a40a9fbf1ca505f072258048ec15e0637baa085649d77b9a90220003e","60138000000000"],["0x0ac21ef27c8ed4487270f1c45e99dac091ca4007951217ece344452df7047e5a","60138000000000"],["0x0aed529418ab67a31a4b98c224f8fdb2fec11f0100c23751e67b312cba11fb23","60138000000000"],["0x0bd14cdade9067c523f44fd208dee5daa7d852151725d713b92c840a031018a6","60138000000000"],["0x0bddff0d98f42a3155e577a5579623a911e3b03401835166553f88375cbd9657","60138000000000"],["0x12893bbd649bf2e1e79cb084025638cdd7906eebca40efeee7fdbd548cc96391","60138000000000"],["0x15546bc7b5124f6d83d6c5a62b8890a48b933168e141c01229431a6c0c499780","60138000000000"],["0x1cbea6a399ba200958db255579cda2195006f3a3108b2d6ef7e258e42c101479","60138000000000"],["0x1f8ee6ba2902715804c769c3845b6b3a37802e462e8df63ba19f827a92dbbda0","60138000000000"],["0x1fdd556d84d1d9f24a739c2600ec72256cc00920d85ad3a2edb3e0d72146789d","60138000000000"],["0x20f20c2cfa4d72afb9c5f64d4735070962b3323b3629892c75f56427f175ebe4","60138000000000"],["0x219d32737b0f3769869b8fa750ba8e3cd9f19b21a4d669c7c79d420d7f7cdb24","60138000000000"],["0x2615b4aeb69140531228248a9d84593117b64e22d462e3968e39c1840a260523","60138000000000"],["0x26984c9f04bc1889061e98bd9caf6955f750219d8e8dbc0986feb9d770e5a15b","60138000000000"],["0x28bb07d80e20aa624ae47dfe53f915b23a666bf825ff283bac06c14bea1eaa74","60138000000000"],["0x2a23566008fd4f87105b09d02c739452e45187fbada5e7e52ada356264cd6751","60138000000000"],["0x2dcc70859876106b21b598ba9a10c9932259c36f44adeb95a178e67f6afd2f7a","60138000000000"],["0x30b854226ef943d738d2dfcc72ede3b39d08604ca7211abf3c76f488441c77fc","60138000000000"],["0x31bb74a5a53769d3db789d961375ea569d4da0bd6ac2b12f830dd6be81968ef1","60138000000000"],["0x334f22e0de2e24a070fec7c1d77d7a988a79d66b79e2d654310a963964edd337","60138000000000"],["0x36c44eddf773390cdb42f93f8454ea9c7ca45aa8948346df8f642a59ce44c442","60138000000000"],["0x3d29d2b5ceef46703255ce8cfe3aad3c4fefd3a2025e5a48ce78a63f20887eff","60138000000000"],["0x41b047a20ed691e9376f7f2f60d6571290e34ef4e1b85467dcc3d7c0cf7fae90","60138000000000"],["0x41e7541c377b58a0cfc4ca954731e971f6dc9fa6806eaa1709d011d3d32593ce","60138000000000"],["0x426a52d3b3d016451c46b3a0eacb382fbbb38739e00d041d4038f795a54e25b5","60138000000000"],["0x42f89915ca540af691f623f201b616caa7f5e104f8293698f8b46c4e7bb5b292","60138000000000"],["0x4449521e793b02b036ce698c3af951e9548cd5b862b704fa5cc9e80b171a3c61","60138000000000"],["0x492f4fee6a035a09e9ff09d65a65768899b04797cff08dc2c64ae11cd94d1968","60138000000000"],["0x4947018f9c0c9302b2783eb5edacb76ccae3b5c5a2f6355b5b51afd1a18075f8","60138000000000"],["0x4c27708e4ce81a0bbeb315ece024ba495f3e3fab5f83a2941b7731a58ad32160","60138000000000"],["0x5059d40f80f578c3c384239415f54af35ab4dbdea0251618d4c3c7b4937e7e69","60138000000000"],["0x5191f8cbfe1ce25a68c337ede75638321374112b868584092a335f83caad59a4","60138000000000"],["0x531296c32ea64d09dcb44ff0b99843dc9855143c70b9fad42deb33881525fb84","60138000000000"],["0x54c9860aa34ddba2a16e4f4271e1771f61f1e8a7a116fbbfa62f0e535b95559e","60138000000000"],["0x54ce2d6b35d0d670e37fcd533ff17c2116e0acead719194e46d478944b33108e","60138000000000"],["0x576af5e4af963a0caa957629d009906119e418e7f7778f5a55d41c0905b73a4f","60138000000000"],["0x59905476f4781f6a79359079bfa3fe295c65d6b918afadbc352edcdb558ad094","60138000000000"],["0x5a4e95cdf94bed240ebabded084b70b2548601686d94a751f240aedd2032e4f2","60138000000000"],["0x5a7500f11becf6741fe5624d2298f6b830ead261871a48a81f80bf9be09ed866","180000000000000"],["0x5c26942bf33c49485db3b2693e5d582708b44705f712c4e24af1ee84744c079e","60138000000000"],["0x5fcdcc107e81ef4399c9d603a25fcba75cb78f1fa1bafd3acb39e3521d7fc9ce","60138000000000"],["0x6107f5b2974fabf6f0aa1a7898340b3f76c4ba272b95a3e4bb809c1d529b6997","60138000000000"],["0x647b9a476f733ec5ee9cbc0bfb021335cd3166b9aaa8ad27ed0f88d9f6bf9dbe","60138000000000"],["0x658c461d8dad54a5a9cbbbb2711920a541ba58003e7029cc228dfdfbc17ede3f","60138000000000"],["0x661336351b889e0124fbc19f9f35f6a0f6e8c4fa9b89e9ef527718bd6aa254be","60138000000000"],["0x6852746bbfb41e556daae99d375b2839ad62b35355c3b9fbbd54b4946ae2050f","60138000000000"],["0x68ad3d98642913848b605dbad3f1df971f21908d360c37af4a493e9b4646b45e","60138000000000"],["0x6c07c6be93940a83b30514b21531fd3dd204bb89e7f77b5a2421a41d4e85c74d","60138000000000"],["0x6d4ad504054f292b7f66c7ed32f3b123bfa5c7be9c45faf26d77ad85efa64a38","60138000000000"],["0x6e77d45d07651565be5cdb11b80c91fa18def0a434f246c0b25bb50fc4877dd4","60138000000000"],["0x738600c570c19ef1b91bf2cd83709d71899c246bfabb5b08dc70fe32b5c81f7d","60138000000000"],["0x7469b663b9fa7be185aa1819bfb48a4eda6e4d8af33e1955d95fa5e156d50f12","60138000000000"],["0x77410e09363239b0999198a701e37f75775cc55049ba541497967f5d8ba74ef4","60138000000000"],["0x781c96175cf45b791142326964347095fd0fbdd3c8579c42cea108798e025152","60138000000000"],["0x79b43e9c18241636ae7c554097bb4bc5da03249bee67abe5366a5b093b708cab","60138000000000"],["0x7ad807b91790868497768476e8c8e6b53ff9b1a91fbfe6a7edae8de0307a8157","60138000000000"],["0x8308ff2b214d509d3781d7361a7ccb5f4fb976f8e386ce3c9082bcad8805d13d","60138000000000"],["0x845a0fff44669c941475eb3f3ffd6e065ee94cfbfdcb820877744d6f9647a5d0","49899000000000"],["0x88700f083f3bc7971c348de8357ee36b2551d8cdb7ea4b4e4e8aae558d67a231","60138000000000"],["0x8b08c457cac18642f49ab7de0ef7551b93e11dbc2979062f22b271b890e8d2f0","60138000000000"],["0x8bc840e0c5a98e608e70307ada41aa94a745a51f6065111942021a4e601dc328","60138000000000"],["0x93e518529faa2244ee1bdc24a5459d4b3d2047f8756b12636e2cba3b766ec201","60138000000000"],["0x993d90fac526bdad11549104105452e9198da8d485dbb4af17b044a721fa8b82","60138000000000"],["0x9997880b1dcc011ce4fefeb587eca16c027c81aafeb4305d3a1755182c269b5e","60138000000000"],["0x9d998de650f13c85da4ab08de0fa7960771d4269081fa1ed1f9940c5cd8bb57c","60138000000000"],["0x9fab138505d28c3c2d68509c5414abe933ab7de90610d8cc84edaf380e739f48","60138000000000"],["0x9fd585f4d71c50ff54b69255ddbaa4a30eae31cde2d02ba6d4c0f87faf288f9a","60138000000000"],["0xa42d598e3d6c051880488bfd139705c9853ff2e93046c6e096eba5f5b8dd714e","60138000000000"],["0xa6e3ff7b1f340f7d02a1b64ade185c9039cd2751ef47ac5d7950b527b377d566","60138000000000"],["0xa79d52472a9343b4f91c61b7e065cd736844064c11188fb86fef32447b163462","60138000000000"],["0xa82bdb918a99f7192b0ebe745f04217991d2077dc43ffe75956782f55c7c9805","60138000000000"],["0xa87f60cfad2f10ec420d4660d98a43a1105a867aac63a2724075f155b991fd35","60138000000000"],["0xad1e503c43f7f62bb672b234eb1510b8ccef4d23b6de1f53a8a0d738c961cee2","60138000000000"],["0xad8dd15447ac5c3b0ac9ed9ebae3b32cfe3cda5442bfce7843443a353701eb34","60138000000000"],["0xade82619842d2257fb19097c990b77818f2352e3809b9c179b3b66989b8e01c3","60138000000000"],["0xb739a017b3b9c9577eeba0d3b94fe2027333ccdad378f0aa67b441eb8cbd675a","60138000000000"],["0xb85ed5c5a48abadc5bf4a85185f781aa60eebe7ef20642f660c7e90d481984cf","60138000000000"],["0xbcb7406f71b46a5171b822f609d50df1a485bbae832f76db0a356b243616ca8e","60138000000000"],["0xbedc1da66f906866cf8af7e57cda645018c39d1b028e9ed4682643941c940348","60138000000000"],["0xc2660177ec158c05676b396baab45f8f8a63f74a0eaf1a7cfe011c7eea0cd8a4","57180000000000"],["0xc536058376e87f6481ce31b0e088235b4be9df00145c97081c45a28cce64c684","60138000000000"],["0xc8fd550fcf32a9ea6aef295e788e4394e744f0939ab5fa8b9009577e274a477e","60138000000000"],["0xcbfe056a9e9fafd246a8fa3025c3d870dac6b01f68adc847f03277eac906452f","60138000000000"],["0xd023735d89cb9d29c5301b87f00d3f7a42aa6f0320086473e50b7e3b8b9acb12","60138000000000"],["0xd17002d5872d62876fc4cae771c472ececae0b50820d760718a753acf431f31c","60138000000000"],["0xd847bac558e925bd87b15a9e8c077df36537e6fc52d5c2019004c0c570fd0266","60138000000000"],["0xdab17536c875995ce144f17771c79e7e9d6adcaaa66cce64947c8d17a363a2dd","60138000000000"],["0xdf0b5c031ece9dadfd23c63a41e4e7f1ae4138b157ff7588c21083853d585789","60138000000000"],["0xe087dae3faaf4748c8bcd237ca7ece5f8bcddf6b60db216252c5e29a7f6a33a4","60138000000000"],["0xe09755d90d62160409b67ff28584f2800087f9063d76b3240c4d3f94f0880c41","60138000000000"],["0xe23c47f9c2e9d2d87d9f1fcc0352ef28d13f322f8003d4210bb33692e77fd988","60138000000000"],["0xeb2269b0ea934046a59399bd824f6a7fae4c7d696bb163e1bd235cbc21aa2b55","60138000000000"],["0xedf8d203bebac7d629840ca0a704cbff92607d6bf538bc99ab65fada6a7b3c65","60138000000000"],["0xf179ca30b1a9c8c33e33863b04d8d0078dddbf974a1e8666d072e0403c997f21","60138000000000"],["0xf199c75a842c96c459272502b7010a78f29e7d00ad649f7756dd11c8d321a97c","60138000000000"],["0xf19a880d58384bfaf5c839e7b6502ff7e6dc11cc38a77f651c948ea8475a37f7","60138000000000"],["0xf25841168bb223f03cc01f5934474e56ae3ac0307a048ab167326c7d655c25db","86829000000000"],["0xf25e305cc44404dae89a8f3b577cf94c367ab28a8ebc81a5c551d39303e254c2","60138000000000"],["0xf3c0eed928d059ec9c99fd55ef4df9ccdaa30626e14b833f064936099b8088e1","60138000000000"],["0xf6fe4424c9df211b1d2e92f7a91889aac643c605de458fa8f2af90534b885654","154754000000000"],["0xf8a26375e76f1f8ff763787507fe2e01ed257b1a6b1772e48338606862a80da4","60138000000000"],["0xfb471071aa87f25465da8c98bdeb1b24165b4a1694c5a6ab9f59eb57ce9e451d","60138000000000"],["0xfb66f351a3b27e1702a21bfd189f3db5053f9f0089b26e7f05218fa87b925e2e","60138000000000"],["0xfbcd15956e466a3c945c8bee6e6bc6bdab6b1b2ec0c07a3ba431091795751bef","60138000000000"],["0xfced4379f1cb13157b34d50301a65ab47dc3452f4cd0e2a2d8e0b33a07350f43","60138000000000"]]}]'; diff --git a/packages/tests/src/tests/utils.ts b/packages/tests/src/tests/utils.ts new file mode 100644 index 00000000..f457ba2a --- /dev/null +++ b/packages/tests/src/tests/utils.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2018-2021 TON DEV SOLUTIONS LTD. + * + * Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at: + * + * http://www.ton.dev/licenses + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific TON DEV software governing permissions and + * limitations under the License. + */ + +import { runner } from "../runner"; +import { test, expect } from "../jest"; +import { + addressStringFormatAccountId, + addressStringFormatBase64, + addressStringFormatHex +} from "@tonclient/core"; + +test("utils: convert_address", async () => { + const { + utils, + } = runner.getClient(); + + const accountId = "fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260"; + const hex = "-1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260"; + const hexWorkchain0 = "0:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260"; + const base64 = "Uf/8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15+KsQHFLbKSMiYG+9"; + const base64Url = "kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny"; + + let convertedAddress = await utils.convert_address({ + address: accountId, + output_format: addressStringFormatHex(), + }); + expect(convertedAddress.address).toEqual(hexWorkchain0); + + convertedAddress = await utils.convert_address({ + address: hex, + output_format: addressStringFormatAccountId(), + }); + expect(convertedAddress.address).toEqual(accountId); + + convertedAddress = await utils.convert_address({ + address: hex, + output_format: addressStringFormatBase64(false, false, false), + }); + expect(convertedAddress.address).toEqual(base64); + + convertedAddress = await utils.convert_address({ + address: base64, + output_format: addressStringFormatBase64(true, true, true), + }); + expect(convertedAddress.address).toEqual(base64Url); + + convertedAddress = await utils.convert_address({ + address: base64Url, + output_format: addressStringFormatHex(), + }); + expect(convertedAddress.address).toEqual(hex); +});