diff --git a/CHANGELOG.md b/CHANGELOG.md index abf462171..626175265 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. +## [1.12.0] – 2021-04-01 + +### New +- [`utils.compress_zstd`](docs/mod_utils.md#compress_zstd) compresses data using Facebook's Zstandard algorithm. +- [`utils.decompress_zstd`](docs/mod_utils.md#decompress_zstd) decompresses data using Facebook's Zstandard algorithm. +- **Debot module**: + - `init` function that creates an instance of DeBot and returns DeBot metadata. + - Dengine fetches metadata form DeBot by calling 2 mandatory functions: `getRequiredInterfaces` and `getDebotInfo`. This data is returned by `fetch` and `init` functions. + - `approve` DeBot Browser callback which is called by DEngine to request permission for DeBot activities. + +### Changed +- **Debot Module**: + - [breaking] `fetch` function does't create an instance of debot. It returns DeBot metadata (`DebotInfo`). + - [breaking] `start` function does't create an instance of debot. It accepts DeBot handle created in `init` function. + ## [1.11.2] – 2021-03-19 ### Refactor diff --git a/api/derive/Cargo.toml b/api/derive/Cargo.toml index 4f28d3811..1ea390133 100644 --- a/api/derive/Cargo.toml +++ b/api/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "api_derive" -version = "1.11.2" +version = "1.12.0" authors = ["TON DEV SOLUTIONS LTD "] edition = "2018" diff --git a/api/info/Cargo.toml b/api/info/Cargo.toml index 5fd1d6110..1eed371cd 100644 --- a/api/info/Cargo.toml +++ b/api/info/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "api_info" -version = "1.11.2" +version = "1.12.0" authors = ["TON DEV SOLUTIONS LTD "] edition = "2018" diff --git a/api/test/Cargo.toml b/api/test/Cargo.toml index f51d055a7..0289b7ab5 100644 --- a/api/test/Cargo.toml +++ b/api/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "api_test" -version = "1.11.2" +version = "1.12.0" authors = ["TON DEV SOLUTIONS LTD "] edition = "2018" diff --git a/docs/mod_debot.md b/docs/mod_debot.md index 78e581ccf..75469fe5a 100644 --- a/docs/mod_debot.md +++ b/docs/mod_debot.md @@ -4,9 +4,11 @@ ## Functions -[start](#start) – [UNSTABLE](UNSTABLE.md) Starts an instance of debot. +[init](#init) – [UNSTABLE](UNSTABLE.md) Creates and instance of DeBot. -[fetch](#fetch) – [UNSTABLE](UNSTABLE.md) Fetches debot from blockchain. +[start](#start) – [UNSTABLE](UNSTABLE.md) Starts the DeBot. + +[fetch](#fetch) – [UNSTABLE](UNSTABLE.md) Fetches DeBot metadata from blockchain. [execute](#execute) – [UNSTABLE](UNSTABLE.md) Executes debot action. @@ -21,50 +23,59 @@ [DebotAction](#DebotAction) – [UNSTABLE](UNSTABLE.md) Describes a debot action in a Debot Context. -[ParamsOfStart](#ParamsOfStart) – [UNSTABLE](UNSTABLE.md) Parameters to start debot. +[DebotInfo](#DebotInfo) – [UNSTABLE](UNSTABLE.md) Describes DeBot metadata. + +[DebotActivity](#DebotActivity) – [UNSTABLE](UNSTABLE.md) Describes the operation that the DeBot wants to perform. + +[Spending](#Spending) – [UNSTABLE](UNSTABLE.md) Describes how much funds will be debited from the target contract balance as a result of the transaction. -[RegisteredDebot](#RegisteredDebot) – [UNSTABLE](UNSTABLE.md) Structure for storing debot handle returned from `start` and `fetch` functions. +[ParamsOfInit](#ParamsOfInit) – [UNSTABLE](UNSTABLE.md) Parameters to init DeBot. + +[RegisteredDebot](#RegisteredDebot) – [UNSTABLE](UNSTABLE.md) Structure for storing debot handle returned from `init` function. [ParamsOfAppDebotBrowser](#ParamsOfAppDebotBrowser) – [UNSTABLE](UNSTABLE.md) Debot Browser callbacks [ResultOfAppDebotBrowser](#ResultOfAppDebotBrowser) – [UNSTABLE](UNSTABLE.md) Returning values from Debot Browser callbacks. -[ParamsOfFetch](#ParamsOfFetch) – [UNSTABLE](UNSTABLE.md) Parameters to fetch debot. +[ParamsOfStart](#ParamsOfStart) – [UNSTABLE](UNSTABLE.md) Parameters to start DeBot. DeBot must be already initialized with init() function. + +[ParamsOfFetch](#ParamsOfFetch) – [UNSTABLE](UNSTABLE.md) Parameters to fetch DeBot metadata. + +[ResultOfFetch](#ResultOfFetch) – [UNSTABLE](UNSTABLE.md) [ParamsOfExecute](#ParamsOfExecute) – [UNSTABLE](UNSTABLE.md) Parameters for executing debot action. [ParamsOfSend](#ParamsOfSend) – [UNSTABLE](UNSTABLE.md) Parameters of `send` function. +[ParamsOfRemove](#ParamsOfRemove) – [UNSTABLE](UNSTABLE.md) + [AppDebotBrowser](#AppDebotBrowser) # Functions -## start +## init -[UNSTABLE](UNSTABLE.md) Starts an instance of debot. +[UNSTABLE](UNSTABLE.md) Creates and instance of DeBot. -Downloads debot smart contract from blockchain and switches it to -context zero. -Returns a debot handle which can be used later in `execute` function. -This function must be used by Debot Browser to start a dialog with debot. -While the function is executing, several Browser Callbacks can be called, -since the debot tries to display all actions from the context 0 to the user. +Downloads debot smart contract (code and data) from blockchain and creates +an instance of Debot Engine for it. # Remarks -`start` is equivalent to `fetch` + switch to context 0. +It does not switch debot to context 0. Browser Callbacks are not called. ```ts -type ParamsOfStart = { +type ParamsOfInit = { address: string } type RegisteredDebot = { debot_handle: DebotHandle, - debot_abi: string + debot_abi: string, + info: DebotInfo } -function start( - params: ParamsOfStart, +function init( + params: ParamsOfInit, obj: AppDebotBrowser, ): Promise; ``` @@ -76,41 +87,63 @@ function start( - `debot_handle`: _[DebotHandle](mod_debot.md#DebotHandle)_ – Debot handle which references an instance of debot engine. - `debot_abi`: _string_ – Debot abi as json string. +- `info`: _[DebotInfo](mod_debot.md#DebotInfo)_ – Debot metadata. -## fetch +## start -[UNSTABLE](UNSTABLE.md) Fetches debot from blockchain. +[UNSTABLE](UNSTABLE.md) Starts the DeBot. -Downloads debot smart contract (code and data) from blockchain and creates -an instance of Debot Engine for it. +Downloads debot smart contract from blockchain and switches it to +context zero. -# Remarks -It does not switch debot to context 0. Browser Callbacks are not called. +This function must be used by Debot Browser to start a dialog with debot. +While the function is executing, several Browser Callbacks can be called, +since the debot tries to display all actions from the context 0 to the user. + +When the debot starts SDK registers `BrowserCallbacks` AppObject. +Therefore when `debote.remove` is called the debot is being deleted and the callback is called +with `finish`=`true` which indicates that it will never be used again. + +```ts +type ParamsOfStart = { + debot_handle: DebotHandle +} + +function start( + params: ParamsOfStart, +): Promise; +``` +### Parameters +- `debot_handle`: _[DebotHandle](mod_debot.md#DebotHandle)_ – Debot handle which references an instance of debot engine. + + +## fetch + +[UNSTABLE](UNSTABLE.md) Fetches DeBot metadata from blockchain. + +Downloads DeBot from blockchain and creates and fetches its metadata. ```ts type ParamsOfFetch = { address: string } -type RegisteredDebot = { - debot_handle: DebotHandle, - debot_abi: string +type ResultOfFetch = { + info: DebotInfo } function fetch( params: ParamsOfFetch, - obj: AppDebotBrowser, -): Promise; +): Promise; ``` ### Parameters -- `address`: _string_ – Debot smart contract address +- `address`: _string_ – Debot smart contract address. ### Result -- `debot_handle`: _[DebotHandle](mod_debot.md#DebotHandle)_ – Debot handle which references an instance of debot engine. -- `debot_abi`: _string_ – Debot abi as json string. +- `info`: _[DebotInfo](mod_debot.md#DebotInfo)_ – Debot metadata. ## execute @@ -166,18 +199,16 @@ function send( Removes handle from Client Context and drops debot engine referenced by that handle. ```ts -type RegisteredDebot = { - debot_handle: DebotHandle, - debot_abi: string +type ParamsOfRemove = { + debot_handle: DebotHandle } function remove( - params: RegisteredDebot, + params: ParamsOfRemove, ): Promise; ``` ### Parameters - `debot_handle`: _[DebotHandle](mod_debot.md#DebotHandle)_ – Debot handle which references an instance of debot engine. -- `debot_abi`: _string_ – Debot abi as json string. # Types @@ -193,7 +224,9 @@ enum DebotErrorCode { DebotInvalidAbi = 807, DebotGetMethodFailed = 808, DebotInvalidMsg = 809, - DebotExternalCallFailed = 810 + DebotExternalCallFailed = 810, + DebotBrowserCallbackFailed = 811, + DebotOperationRejected = 812 } ``` One of the following value: @@ -208,6 +241,8 @@ One of the following value: - `DebotGetMethodFailed = 808` - `DebotInvalidMsg = 809` - `DebotExternalCallFailed = 810` +- `DebotBrowserCallbackFailed = 811` +- `DebotOperationRejected = 812` ## DebotHandle @@ -243,11 +278,90 @@ type DebotAction = {
Used by debot only. -## ParamsOfStart -[UNSTABLE](UNSTABLE.md) Parameters to start debot. +## DebotInfo +[UNSTABLE](UNSTABLE.md) Describes DeBot metadata. ```ts -type ParamsOfStart = { +type DebotInfo = { + name?: string, + version?: string, + publisher?: string, + key?: string, + author?: string, + support?: string, + hello?: string, + language?: string, + dabi?: string, + icon?: string, + interfaces: string[] +} +``` +- `name`?: _string_ – DeBot short name. +- `version`?: _string_ – DeBot semantic version. +- `publisher`?: _string_ – The name of DeBot deployer. +- `key`?: _string_ – Short info about DeBot. +- `author`?: _string_ – The name of DeBot developer. +- `support`?: _string_ – TON address of author for questions and donations. +- `hello`?: _string_ – String with the first messsage from DeBot. +- `language`?: _string_ – String with DeBot interface language (ISO-639). +- `dabi`?: _string_ – String with DeBot ABI. +- `icon`?: _string_ – DeBot icon. +- `interfaces`: _string[]_ – Vector with IDs of DInterfaces used by DeBot. + + +## DebotActivity +[UNSTABLE](UNSTABLE.md) Describes the operation that the DeBot wants to perform. + +```ts +type DebotActivity = { + type: 'Transaction' + msg: string, + dst: string, + out: Spending[], + fee: bigint, + setcode: boolean, + signkey: string +} +``` +Depends on value of the `type` field. + +When _type_ is _'Transaction'_ + +DeBot wants to create new transaction in blockchain. + + +- `msg`: _string_ – External inbound message BOC. +- `dst`: _string_ – Target smart contract address. +- `out`: _[Spending](mod_debot.md#Spending)[]_ – List of spendings as a result of transaction. +- `fee`: _bigint_ – Transaction total fee. +- `setcode`: _boolean_ – Indicates if target smart contract updates its code. +- `signkey`: _string_ – Public key from keypair that was used to sign external message. + + +Variant constructors: + +```ts +function debotActivityTransaction(msg: string, dst: string, out: Spending[], fee: bigint, setcode: boolean, signkey: string): DebotActivity; +``` + +## Spending +[UNSTABLE](UNSTABLE.md) Describes how much funds will be debited from the target contract balance as a result of the transaction. + +```ts +type Spending = { + amount: bigint, + dst: string +} +``` +- `amount`: _bigint_ – Amount of nanotokens that will be sent to `dst` address. +- `dst`: _string_ – Destination address of recipient of funds. + + +## ParamsOfInit +[UNSTABLE](UNSTABLE.md) Parameters to init DeBot. + +```ts +type ParamsOfInit = { address: string } ``` @@ -255,16 +369,18 @@ type ParamsOfStart = { ## RegisteredDebot -[UNSTABLE](UNSTABLE.md) Structure for storing debot handle returned from `start` and `fetch` functions. +[UNSTABLE](UNSTABLE.md) Structure for storing debot handle returned from `init` function. ```ts type RegisteredDebot = { debot_handle: DebotHandle, - debot_abi: string + debot_abi: string, + info: DebotInfo } ``` - `debot_handle`: _[DebotHandle](mod_debot.md#DebotHandle)_ – Debot handle which references an instance of debot engine. - `debot_abi`: _string_ – Debot abi as json string. +- `info`: _[DebotInfo](mod_debot.md#DebotInfo)_ – Debot metadata. ## ParamsOfAppDebotBrowser @@ -296,6 +412,9 @@ type ParamsOfAppDebotBrowser = { } | { type: 'Send' message: string +} | { + type: 'Approve' + activity: DebotActivity } ``` Depends on value of the `type` field. @@ -356,6 +475,13 @@ Used by Debot to call DInterface implemented by Debot Browser. - `message`: _string_ – Internal message to DInterface address.
Message body contains interface function and parameters. +When _type_ is _'Approve'_ + +Requests permission from DeBot Browser to execute DeBot operation. + + +- `activity`: _[DebotActivity](mod_debot.md#DebotActivity)_ – DeBot activity details. + Variant constructors: @@ -368,6 +494,7 @@ function paramsOfAppDebotBrowserInput(prompt: string): ParamsOfAppDebotBrowser; function paramsOfAppDebotBrowserGetSigningBox(): ParamsOfAppDebotBrowser; function paramsOfAppDebotBrowserInvokeDebot(debot_addr: string, action: DebotAction): ParamsOfAppDebotBrowser; function paramsOfAppDebotBrowserSend(message: string): ParamsOfAppDebotBrowser; +function paramsOfAppDebotBrowserApprove(activity: DebotActivity): ParamsOfAppDebotBrowser; ``` ## ResultOfAppDebotBrowser @@ -382,6 +509,9 @@ type ResultOfAppDebotBrowser = { signing_box: SigningBoxHandle } | { type: 'InvokeDebot' +} | { + type: 'Approve' + approved: boolean } ``` Depends on value of the `type` field. @@ -406,6 +536,13 @@ When _type_ is _'InvokeDebot'_ Result of debot invoking. +When _type_ is _'Approve'_ + +Result of `approve` callback. + + +- `approved`: _boolean_ – Indicates whether the DeBot is allowed to perform the specified operation. + Variant constructors: @@ -413,17 +550,40 @@ Variant constructors: function resultOfAppDebotBrowserInput(value: string): ResultOfAppDebotBrowser; function resultOfAppDebotBrowserGetSigningBox(signing_box: SigningBoxHandle): ResultOfAppDebotBrowser; function resultOfAppDebotBrowserInvokeDebot(): ResultOfAppDebotBrowser; +function resultOfAppDebotBrowserApprove(approved: boolean): ResultOfAppDebotBrowser; ``` +## ParamsOfStart +[UNSTABLE](UNSTABLE.md) Parameters to start DeBot. DeBot must be already initialized with init() function. + +```ts +type ParamsOfStart = { + debot_handle: DebotHandle +} +``` +- `debot_handle`: _[DebotHandle](mod_debot.md#DebotHandle)_ – Debot handle which references an instance of debot engine. + + ## ParamsOfFetch -[UNSTABLE](UNSTABLE.md) Parameters to fetch debot. +[UNSTABLE](UNSTABLE.md) Parameters to fetch DeBot metadata. ```ts type ParamsOfFetch = { address: string } ``` -- `address`: _string_ – Debot smart contract address +- `address`: _string_ – Debot smart contract address. + + +## ResultOfFetch +[UNSTABLE](UNSTABLE.md) + +```ts +type ResultOfFetch = { + info: DebotInfo +} +``` +- `info`: _[DebotInfo](mod_debot.md#DebotInfo)_ – Debot metadata. ## ParamsOfExecute @@ -452,6 +612,17 @@ type ParamsOfSend = { - `message`: _string_ – BOC of internal message to debot encoded in base64 format. +## ParamsOfRemove +[UNSTABLE](UNSTABLE.md) + +```ts +type ParamsOfRemove = { + debot_handle: DebotHandle +} +``` +- `debot_handle`: _[DebotHandle](mod_debot.md#DebotHandle)_ – Debot handle which references an instance of debot engine. + + ## AppDebotBrowser ```ts @@ -489,6 +660,14 @@ type ParamsOfAppDebotBrowserSend = { message: string } +type ParamsOfAppDebotBrowserApprove = { + activity: DebotActivity +} + +type ResultOfAppDebotBrowserApprove = { + approved: boolean +} + export interface AppDebotBrowser { log(params: ParamsOfAppDebotBrowserLog): void, switch(params: ParamsOfAppDebotBrowserSwitch): void, @@ -498,6 +677,7 @@ export interface AppDebotBrowser { get_signing_box(): Promise, invoke_debot(params: ParamsOfAppDebotBrowserInvokeDebot): Promise, send(params: ParamsOfAppDebotBrowserSend): void, + approve(params: ParamsOfAppDebotBrowserApprove): Promise, } ``` @@ -645,3 +825,29 @@ function send(
Message body contains interface function and parameters. +## approve + +Requests permission from DeBot Browser to execute DeBot operation. + +```ts +type ParamsOfAppDebotBrowserApprove = { + activity: DebotActivity +} + +type ResultOfAppDebotBrowserApprove = { + approved: boolean +} + +function approve( + params: ParamsOfAppDebotBrowserApprove, +): Promise; +``` +### Parameters +- `activity`: _[DebotActivity](mod_debot.md#DebotActivity)_ – DeBot activity details. + + +### Result + +- `approved`: _boolean_ – Indicates whether the DeBot is allowed to perform the specified operation. + + diff --git a/docs/mod_utils.md b/docs/mod_utils.md index ce64bb739..d7d955ee2 100644 --- a/docs/mod_utils.md +++ b/docs/mod_utils.md @@ -8,6 +8,10 @@ Misc utility Functions. [calc_storage_fee](#calc_storage_fee) – Calculates storage fee for an account over a specified time period +[compress_zstd](#compress_zstd) – Compresses data using Zstandard algorithm + +[decompress_zstd](#decompress_zstd) – Decompresses data using Zstandard algorithm + ## Types [AddressStringFormat](#AddressStringFormat) @@ -19,6 +23,14 @@ Misc utility Functions. [ResultOfCalcStorageFee](#ResultOfCalcStorageFee) +[ParamsOfCompressZstd](#ParamsOfCompressZstd) + +[ResultOfCompressZstd](#ResultOfCompressZstd) + +[ParamsOfDecompressZstd](#ParamsOfDecompressZstd) + +[ResultOfDecompressZstd](#ResultOfDecompressZstd) + # Functions ## convert_address @@ -77,6 +89,64 @@ function calc_storage_fee( - `fee`: _string_ +## compress_zstd + +Compresses data using Zstandard algorithm + +```ts +type ParamsOfCompressZstd = { + uncompressed: string, + level?: number +} + +type ResultOfCompressZstd = { + compressed: string +} + +function compress_zstd( + params: ParamsOfCompressZstd, +): Promise; +``` +### Parameters +- `uncompressed`: _string_ – Uncompressed data. +
Must be encoded as base64. +- `level`?: _number_ – Compression level, from 1 to 21. Where: 1 - lowest compression level (fastest compression); 21 - highest compression level (slowest compression). If level is omitted, the default compression level is used (currently `3`). + + +### Result + +- `compressed`: _string_ – Compressed data. +
Must be encoded as base64. + + +## decompress_zstd + +Decompresses data using Zstandard algorithm + +```ts +type ParamsOfDecompressZstd = { + compressed: string +} + +type ResultOfDecompressZstd = { + decompressed: string +} + +function decompress_zstd( + params: ParamsOfDecompressZstd, +): Promise; +``` +### Parameters +- `compressed`: _string_ – Compressed data. +
Must be encoded as base64. + + +### Result + +- `decompressed`: _string_ – Decompressed data. +
Must be encoded as base64. + + # Types ## AddressStringFormat ```ts @@ -155,3 +225,45 @@ type ResultOfCalcStorageFee = { - `fee`: _string_ +## ParamsOfCompressZstd +```ts +type ParamsOfCompressZstd = { + uncompressed: string, + level?: number +} +``` +- `uncompressed`: _string_ – Uncompressed data. +
Must be encoded as base64. +- `level`?: _number_ – Compression level, from 1 to 21. Where: 1 - lowest compression level (fastest compression); 21 - highest compression level (slowest compression). If level is omitted, the default compression level is used (currently `3`). + + +## ResultOfCompressZstd +```ts +type ResultOfCompressZstd = { + compressed: string +} +``` +- `compressed`: _string_ – Compressed data. +
Must be encoded as base64. + + +## ParamsOfDecompressZstd +```ts +type ParamsOfDecompressZstd = { + compressed: string +} +``` +- `compressed`: _string_ – Compressed data. +
Must be encoded as base64. + + +## ResultOfDecompressZstd +```ts +type ResultOfDecompressZstd = { + decompressed: string +} +``` +- `decompressed`: _string_ – Decompressed data. +
Must be encoded as base64. + + diff --git a/docs/modules.md b/docs/modules.md index db4fd4a1f..e4af11d85 100644 --- a/docs/modules.md +++ b/docs/modules.md @@ -157,6 +157,10 @@ Where: [calc_storage_fee](mod_utils.md#calc_storage_fee) – Calculates storage fee for an account over a specified time period +[compress_zstd](mod_utils.md#compress_zstd) – Compresses data using Zstandard algorithm + +[decompress_zstd](mod_utils.md#decompress_zstd) – Decompresses data using Zstandard algorithm + ## [tvm](mod_tvm.md) [run_executor](mod_tvm.md#run_executor) – Emulates all the phases of contract execution locally @@ -193,9 +197,11 @@ Where: ## [debot](mod_debot.md) – [UNSTABLE](UNSTABLE.md) Module for working with debot. -[start](mod_debot.md#start) – [UNSTABLE](UNSTABLE.md) Starts an instance of debot. +[init](mod_debot.md#init) – [UNSTABLE](UNSTABLE.md) Creates and instance of DeBot. + +[start](mod_debot.md#start) – [UNSTABLE](UNSTABLE.md) Starts the DeBot. -[fetch](mod_debot.md#fetch) – [UNSTABLE](UNSTABLE.md) Fetches debot from blockchain. +[fetch](mod_debot.md#fetch) – [UNSTABLE](UNSTABLE.md) Fetches DeBot metadata from blockchain. [execute](mod_debot.md#execute) – [UNSTABLE](UNSTABLE.md) Executes debot action. diff --git a/ton_client/Cargo.toml b/ton_client/Cargo.toml index 0f6d89bdf..dfc343172 100644 --- a/ton_client/Cargo.toml +++ b/ton_client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ton_client" -version = "1.11.2" +version = "1.12.0" authors = ["TON DEV SOLUTIONS LTD "] edition = "2018" license = "Apache-2.0" @@ -57,16 +57,21 @@ futures = "0.3.4" tokio = { version = "0.2.13", features = ["sync", "stream"], default-features = false } lru = "0.6.3" lockfree = { git = "https://github.com/tonlabs/lockfree.git", package = "lockfree" } +zstd = { git = "https://github.com/gyscos/zstd-rs.git", default-features = false } # optional for std reqwest = { version = "0.10.4", optional = true } tokio-tungstenite = { version = "0.11.0", features = ["tls"], optional = true } # optional for wasm -wasm-bindgen = { version = "=0.2.65", features = ["serde-serialize"], optional = true } +wasm-bindgen = { version = "0.2.73", features = ["serde-serialize"], optional = true } wasm-bindgen-futures = { version = "0.4.15", optional = true } -js-sys = { version = "0.3", optional = true } -web-sys = { version = "0.3.42", optional = true, features = [ +js-sys = { version = "0.3.50", optional = true } + +[dependencies.web-sys] +version = "0.3.42" +optional = true +features = [ "ErrorEvent", "FileReader", "Headers", @@ -77,7 +82,7 @@ web-sys = { version = "0.3.42", optional = true, features = [ "Response", "Window", "WebSocket", - ] } +] [dev-dependencies] log4rs = "^0" @@ -88,4 +93,13 @@ pretty_assertions = "0.6.1" [features] default = ["std"] std = ["tokio/rt-threaded", "tokio/macros", "reqwest", "tokio-tungstenite"] -wasm = ["wasm-bindgen", "wasm-bindgen-futures", "js-sys", "web-sys", "rand/wasm-bindgen", "chrono/wasmbind"] +wasm = [ + "wasm-bindgen", + "wasm-bindgen-futures", + "js-sys", + "web-sys", + "rand/wasm-bindgen", + "chrono/wasmbind", + "zstd/wasm", + "zstd/thin", +] diff --git a/ton_client/src/debot/activity.rs b/ton_client/src/debot/activity.rs new file mode 100644 index 000000000..4923a4fbc --- /dev/null +++ b/ton_client/src/debot/activity.rs @@ -0,0 +1,31 @@ + +/// [UNSTABLE](UNSTABLE.md) Describes how much funds will be debited from the target +/// contract balance as a result of the transaction. +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, PartialEq)] +pub struct Spending { + /// Amount of nanotokens that will be sent to `dst` address. + pub amount: u64, + /// Destination address of recipient of funds. + pub dst: String, +} + +/// [UNSTABLE](UNSTABLE.md) Describes the operation that the DeBot wants to perform. +#[derive(Serialize, Deserialize, Clone, Debug, ApiType)] +#[serde(tag="type")] +pub enum DebotActivity { + /// DeBot wants to create new transaction in blockchain. + Transaction { + /// External inbound message BOC. + msg: String, + /// Target smart contract address. + dst: String, + ///List of spendings as a result of transaction. + out: Vec, + /// Transaction total fee. + fee: u64, + /// Indicates if target smart contract updates its code. + setcode: bool, + /// Public key from keypair that was used to sign external message. + signkey: String, + } +} \ No newline at end of file diff --git a/ton_client/src/debot/browser.rs b/ton_client/src/debot/browser.rs index 98baa5a1a..f1ac6960e 100644 --- a/ton_client/src/debot/browser.rs +++ b/ton_client/src/debot/browser.rs @@ -1,5 +1,7 @@ +use super::DebotActivity; use super::action::DAction; use crate::crypto::SigningBoxHandle; +use crate::error::ClientResult; /// Callbacks that are called by debot engine to communicate with Debot Browser. #[async_trait::async_trait] @@ -23,4 +25,8 @@ pub trait BrowserCallbacks { /// Sends message with debot interface call to Browser. /// Message parameter is a BoC encoded as Base64. async fn send(&self, message: String); + + /// Requests permission to execute DeBot operation + /// (e.g. sending messages to blockchain). + async fn approve(&self, activity: DebotActivity) -> ClientResult; } diff --git a/ton_client/src/debot/calltype.rs b/ton_client/src/debot/calltype.rs index 486167bc9..ea42047e4 100644 --- a/ton_client/src/debot/calltype.rs +++ b/ton_client/src/debot/calltype.rs @@ -1,20 +1,22 @@ use super::errors::Error; use super::helpers::build_internal_message; -use super::{BrowserCallbacks, TonClient}; +use super::{BrowserCallbacks, DebotActivity, Spending, TonClient}; use crate::abi::Signer; use crate::boc::internal::{deserialize_object_from_base64, serialize_object_to_base64}; -use crate::crypto::SigningBoxHandle; +use crate::boc::{parse_message, ParamsOfParse}; +use crate::crypto::{signing_box_get_public_key, RegisteredSigningBox, SigningBoxHandle}; +use crate::encoding::decode_abi_number; use crate::error::{ClientError, ClientResult}; use crate::processing::{ send_message, wait_for_transaction, ParamsOfSendMessage, ParamsOfWaitForTransaction, ProcessingEvent, }; -use crate::tvm::{run_tvm, ParamsOfRunTvm}; +use crate::tvm::{run_executor, run_tvm, AccountForExecutor, ParamsOfRunExecutor, ParamsOfRunTvm}; use std::convert::TryFrom; use std::fmt::Display; use std::sync::Arc; use ton_block::{Message, MsgAddressExt}; -use ton_types::{BuilderData, Cell, IBitstring}; +use ton_types::{BuilderData, Cell, IBitstring, SliceData}; const SUPPORTED_ABI_VERSION: u8 = 2; @@ -104,6 +106,7 @@ pub async fn run_get_method( } let out_msg = result.out_messages.pop().unwrap(); build_answer_msg(&out_msg, answer_id, func_id, &dest_addr, debot_addr) + .ok_or(Error::get_method_failed("failed to build answer message")) } pub async fn send_ext_msg<'a>( @@ -111,11 +114,11 @@ pub async fn send_ext_msg<'a>( ton: TonClient, msg: String, signing_box: SigningBoxHandle, - _target_state: String, + target_state: String, debot_addr: &'a String, ) -> ClientResult { let signer = Signer::SigningBox { - handle: signing_box, + handle: signing_box.clone(), }; let (answer_id, onerror_id, func_id, dest_addr, fixed_msg) = @@ -123,6 +126,34 @@ pub async fn send_ext_msg<'a>( .await .map_err(|e| Error::external_call_failed(e))?; + let pubkey = signing_box_get_public_key( + ton.clone(), + RegisteredSigningBox { + handle: signing_box, + }, + ) + .await? + .pubkey; + let activity = emulate_transaction( + ton.clone(), + dest_addr.clone(), + fixed_msg.clone(), + target_state, + pubkey + ).await; + match activity { + Ok(activity) => { + if !browser.approve(activity).await? { + let error_body = build_onerror_body(onerror_id, Error::operation_rejected())?; + return build_internal_message(&dest_addr, debot_addr, error_body); + } + }, + Err(e) => { + let error_body = build_onerror_body(onerror_id, e)?; + return build_internal_message(&dest_addr, debot_addr, error_body); + }, + } + let browser = browser.clone(); let callback = move |event| { debug!("{:?}", event); @@ -169,10 +200,10 @@ pub async fn send_ext_msg<'a>( Ok(res) => { for out_msg in &res.out_messages { let res = build_answer_msg(out_msg, answer_id, func_id, &dest_addr, debot_addr); - if let Ok(answer_msg) = res { + if let Some(answer_msg) = res { return Ok(answer_msg); } - debug!("Skip outbound message :{}", res.unwrap_err()); + debug!("Skip outbound message"); } debug!("Build empty body"); // answer message not found, build empty answer. @@ -182,21 +213,26 @@ pub async fn send_ext_msg<'a>( } Err(e) => { debug!("Transaction failed: {:?}", e); - let mut new_body = BuilderData::new(); - new_body.append_u32(onerror_id).map_err(msg_err)?; - new_body.append_u32(e.code).map_err(msg_err)?; - let error_code = e - .data - .pointer("/local_error/data/exit_code") - .or(e.data.pointer("/exit_code")) - .and_then(|val| val.as_i64()) - .unwrap_or(0); - new_body.append_u32(error_code as u32).map_err(msg_err)?; - build_internal_message(&dest_addr, debot_addr, new_body.into()) + let error_body = build_onerror_body(onerror_id, e)?; + build_internal_message(&dest_addr, debot_addr, error_body) } } } +fn build_onerror_body(onerror_id: u32, e: ClientError) -> ClientResult { + let mut new_body = BuilderData::new(); + new_body.append_u32(onerror_id).map_err(msg_err)?; + new_body.append_u32(e.code).map_err(msg_err)?; + let error_code = e + .data + .pointer("/local_error/data/exit_code") + .or(e.data.pointer("/exit_code")) + .and_then(|val| val.as_i64()) + .unwrap_or(0); + new_body.append_u32(error_code as u32).map_err(msg_err)?; + Ok(new_body.into()) +} + async fn decode_and_fix_ext_msg( ton: TonClient, msg: String, @@ -308,21 +344,69 @@ fn build_answer_msg( func_id: u32, dest_addr: &String, debot_addr: &String, -) -> ClientResult { - let out_message: Message = deserialize_object_from_base64(out_msg, "message")?.object; +) -> Option { + let out_message: Message = deserialize_object_from_base64(out_msg, "message").ok()?.object; + if out_message.is_internal() { + return None; + } let mut new_body = BuilderData::new(); - new_body.append_u32(answer_id).map_err(msg_err)?; + new_body.append_u32(answer_id).ok()?; if let Some(body_slice) = out_message.body().as_mut() { - let response_id = body_slice.get_next_u32().map_err(msg_err)?; + let response_id = body_slice.get_next_u32().ok()?; let request_id = response_id & !(1u32 << 31); if func_id != request_id { - return Err(msg_err("incorrect response id")); + return None; } new_body .append_builder(&BuilderData::from_slice(&body_slice)) - .map_err(msg_err)?; + .ok()?; } - build_internal_message(dest_addr, debot_addr, new_body.into()) + build_internal_message(dest_addr, debot_addr, new_body.into()).ok() +} + +async fn emulate_transaction( + client: TonClient, + dst: String, + msg: String, + target_state: String, + signkey: String, +) -> ClientResult { + let result = run_executor( + client.clone(), + ParamsOfRunExecutor { + message: msg.clone(), + account: AccountForExecutor::Account { + boc: target_state, + unlimited_balance: None, + }, + ..Default::default() + }, + ) + .await?; + let mut out = vec![]; + for out_msg in result.out_messages { + let parsed = parse_message(client.clone(), ParamsOfParse { boc: out_msg }) + .await? + .parsed; + let msg_type = parsed["msg_type"].as_u64().unwrap(); + // if internal message + if msg_type == 0 { + let out_dst = parsed["dst"].as_str().unwrap().to_owned(); + let out_amount = decode_abi_number(parsed["value"].as_str().unwrap())?; + out.push(Spending { + dst: out_dst, + amount: out_amount, + }); + } + } + Ok(DebotActivity::Transaction { + msg: msg.clone(), + dst: dst.clone(), + out, + fee: result.fees.total_account_fees, + setcode: false, + signkey, + }) } diff --git a/ton_client/src/debot/debot_abi.rs b/ton_client/src/debot/debot_abi.rs index b48c422b2..df4f2d574 100644 --- a/ton_client/src/debot/debot_abi.rs +++ b/ton_client/src/debot/debot_abi.rs @@ -53,13 +53,33 @@ pub const DEBOT_ABI: &'static str = r#"{ {"name":"targetAddr","type":"address"} ] }, - { - "name": "constructor", + { + "name": "getDebotInfo", + "id": "0xDEB", "inputs": [ ], "outputs": [ + {"name":"name","type":"bytes"}, + {"name":"version","type":"bytes"}, + {"name":"publisher","type":"bytes"}, + {"name":"key","type":"bytes"}, + {"name":"author","type":"bytes"}, + {"name":"support","type":"address"}, + {"name":"hello","type":"bytes"}, + {"name":"language","type":"bytes"}, + {"name":"dabi","type":"bytes"}, + {"name":"icon","type":"bytes"} + ] - } + }, + { + "name": "getRequiredInterfaces", + "inputs": [ + ], + "outputs": [ + {"name":"interfaces","type":"uint256[]"} + ] + } ], "data": [ ], diff --git a/ton_client/src/debot/dengine.rs b/ton_client/src/debot/dengine.rs index 6c6b49c33..6102b5a97 100644 --- a/ton_client/src/debot/dengine.rs +++ b/ton_client/src/debot/dengine.rs @@ -9,7 +9,7 @@ use super::calltype; use super::calltype::DebotCallType; use super::routines; use super::run_output::RunOutput; -use super::{JsonValue, TonClient}; +use super::{JsonValue, TonClient, DInfo}; use crate::abi::{ decode_message_body, encode_message, encode_message_body, Abi, CallSet, DeploySet, ErrorCode, ParamsOfDecodeMessageBody, ParamsOfEncodeMessage, ParamsOfEncodeMessageBody, Signer, @@ -76,6 +76,7 @@ pub struct DEngine { target_abi: Option, browser: Arc, builtin_interfaces: BuiltinInterfaces, + info: DInfo, } impl DEngine { @@ -111,35 +112,85 @@ impl DEngine { target_abi: None, browser: browser.clone(), builtin_interfaces: BuiltinInterfaces::new(ton), + info: Default::default(), } } - pub async fn fetch(&mut self) -> Result { + pub async fn fetch(ton: TonClient, addr: String) -> Result { + let state = Self::load_state(ton.clone(), addr.clone()).await?; + Self::fetch_info(ton, addr, state).await + } + + pub async fn init(&mut self) -> Result { self.fetch_state().await?; self.prev_state = STATE_EXIT; - Ok(self.raw_abi.clone()) + Ok(self.info.clone()) } - async fn fetch_state(&mut self) -> Result<(), String> { - self.state = self.load_state(self.addr.clone()).await?; - let result = self.run_debot_get("getVersion", None).await?; - - let name_hex = result["name"].as_str().unwrap(); - let ver_str = result["semver"].as_str().unwrap(); - let name = str_hex_to_utf8(name_hex).unwrap(); - let ver = decode_abi_number::(ver_str).unwrap(); - self.browser.log(format!( - "{}, version {}.{}.{}", - name, - (ver >> 16) as u8, - (ver >> 8) as u8, - ver as u8 - )).await; + pub async fn start(&mut self) -> Result<(), String> { + self.fetch_state().await?; + self.switch_state(STATE_ZERO, true).await?; + Ok(()) + } + + async fn fetch_info(ton: TonClient, addr: String, state: String) -> Result { + let abi = load_abi(DEBOT_ABI).unwrap(); + let result = Self::run( + ton.clone(), + state.clone(), + addr.clone(), + abi.clone(), + "getRequiredInterfaces", + None + ).await; + let interfaces: Vec = match result { + Ok(r) => { + let mut output = r.return_value.unwrap_or(json!({})); + serde_json::from_value(output["interfaces"].take()) + .map_err(|e| format!( + "failed to parse \"interfaces\" returned from \"getRequiredInterfaces\": {}", e + ))? + }, + Err(_) => vec![], + }; + let result = Self::run(ton.clone(), state.clone(), addr.clone(), abi.clone(), "getDebotInfo", None).await; + let mut info: DInfo = match result { + Ok(r) => { + let output = r.return_value.unwrap_or(json!({})); + serde_json::from_value(output) + .map_err(|e| format!("failed to parse \"getDebotInfo\": {}", e) )? + }, + Err(_) => Default::default(), + }; + info.interfaces = interfaces; + + // TODO: for compatibility with previous debots that returns abi in + // getDebotOptions. Remove later. + if info.dabi.is_none() { + let params = Self::run(ton, state, addr, abi, "getDebotOptions", None).await; + if let Ok(params) = params { + let params = params.return_value.unwrap_or(json!({})); + let opt_str = params["options"].as_str().unwrap_or("0"); + let options = decode_abi_number::(opt_str).unwrap(); + if options & OPTION_ABI != 0 { + let abi_str = str_hex_to_utf8(params["debotAbi"].as_str().unwrap()) + .ok_or("cannot convert hex string to debot abi")?; + info.dabi = Some(abi_str); + } + } + } + + Ok(info) + } + async fn fetch_state(&mut self) -> Result<(), String> { + self.state = Self::load_state(self.ton.clone(), self.addr.clone()).await?; + self.info = Self::fetch_info(self.ton.clone(), self.addr.clone(), self.state.clone()).await?; self.update_options().await?; - let result = self.run_debot_get("fetch", None).await; - let mut context_vec: Vec = if let Ok(mut res) = result { - serde_json::from_value(res["contexts"].take()) + let result = self.run_debot_external("fetch", None).await; + let mut context_vec: Vec = if let Ok(res) = result { + let mut output = res.return_value.unwrap_or(json!({})); + serde_json::from_value(output["contexts"].take()) .map_err(|e| { format!("failed to parse \"contexts\" returned from \"fetch\": {}", e) })? @@ -162,19 +213,6 @@ impl DEngine { Ok(()) } - pub async fn start(&mut self) -> Result { - self.fetch_state().await?; - self.switch_state(STATE_ZERO, true).await?; - Ok(self.raw_abi.clone()) - } - - #[allow(dead_code)] - pub async fn version(&mut self) -> Result { - self.run_debot_get("getVersion", None) - .await - .map(|res| res.to_string()) - } - pub async fn execute_action(&mut self, act: &DAction) -> Result<(), String> { match self.handle_action(&act).await { Ok(acts) => { @@ -458,54 +496,27 @@ impl DEngine { Ok(false) } - async fn run_debot_get( - &self, - func: &str, - args: Option, - ) -> Result { - self.run( - self.state.clone(), - self.addr.clone(), - self.abi.clone(), - func, - args, - ) - .await - .map(|res| res.return_value.unwrap_or(json!({}))) - .map_err(|e| format!("{}", e)) - } - - async fn run_get( - &self, - addr: String, - abi: Abi, - name: &str, - params: Option, - ) -> Result { - let state = self.load_state(addr.clone()).await?; - match self.run(state, addr, abi, name, params).await { - Ok(res) => Ok(res.return_value.unwrap_or(json!({}))), - Err(e) => { - error!("{:?}", e); - Err(self.handle_sdk_err(e).await) - }, - } - } - async fn run_debot_external( &mut self, name: &str, args: Option, ) -> Result { debug!("run_debot_external {}, args: {}", name, args.as_ref().unwrap_or(&json!({}))); - let res = self.run(self.state.clone(), self.addr.clone(), self.abi.clone(), name, args).await; + let res = Self::run( + self.ton.clone(), + self.state.clone(), + self.addr.clone(), + self.abi.clone(), + name, + args + ).await; match res { Ok(res) => { self.state = res.account.clone(); Ok(res) - } + }, Err(e) => { - error!("{:?}", e); + error!("{}", e); Err(self.handle_sdk_err(e).await) }, } @@ -572,14 +583,19 @@ impl DEngine { return Err(format!("target address is undefined")); } let (addr, abi) = self.get_target()?; - let result = self.run_get(addr, abi, getmethod, args).await?; - let result = self.run_debot_external(result_handler, Some(result)).await?; + let state = Self::load_state(self.ton.clone(), addr.clone()).await?; + let result = Self::run(self.ton.clone(), state, addr, abi, getmethod, args).await; + let result = match result { + Ok(r) => Ok(r.return_value), + Err(e) => Err(self.handle_sdk_err(e).await), + }?; + let result = self.run_debot_external(result_handler, result).await?; Ok(result.return_value) } - async fn load_state(&self, addr: String) -> Result { + pub(crate) async fn load_state(ton: TonClient, addr: String) -> Result { let account_request = query_collection( - self.ton.clone(), + ton, ParamsOfQueryCollection { collection: "accounts".to_owned(), filter: Some(serde_json::json!({ @@ -603,7 +619,8 @@ impl DEngine { } async fn update_options(&mut self) -> Result<(), String> { - let params = self.run_debot_get("getDebotOptions", None).await?; + let params = self.run_debot_external("getDebotOptions", None).await?.return_value; + let params = params.ok_or(format!("no return value"))?; let opt_str = params["options"].as_str().unwrap(); let options = decode_abi_number::(opt_str).unwrap(); if options & OPTION_ABI != 0 { @@ -675,7 +692,7 @@ impl DEngine { } async fn run( - &self, + ton: TonClient, state: String, addr: String, abi: Abi, @@ -686,7 +703,7 @@ impl DEngine { let msg_params = ParamsOfEncodeMessage { abi: abi.clone(), - address: Some(addr), + address: Some(addr.clone()), deploy_set: None, call_set: if args.is_none() { CallSet::some_with_function(func) @@ -697,10 +714,10 @@ impl DEngine { processing_try_index: None, }; - let result = encode_message(self.ton.clone(), msg_params).await?; + let result = encode_message(ton.clone(), msg_params).await?; let result = run_tvm( - self.ton.clone(), + ton.clone(), ParamsOfRunTvm { account: state, message: result.message, @@ -708,14 +725,19 @@ impl DEngine { return_updated_account: Some(true), ..Default::default() }, - ).await?; + ).await; - RunOutput::new( - result.account, - self.addr.clone(), - result.decoded.and_then(|x| x.output), - result.out_messages, - ) + match result { + Ok(res) => { + RunOutput::new( + res.account, + addr, + res.decoded.and_then(|x| x.output), + res.out_messages, + ) + }, + Err(e) => Err(e), + } } async fn call_target( @@ -806,7 +828,7 @@ impl DEngine { }, DebotCallType::GetMethod{msg, dest} => { debug!("GetMethod call"); - let target_state = self.load_state(dest.clone()).await + let target_state = Self::load_state(self.ton.clone(), dest.clone()).await .map_err(|e| Error::execute_failed(e))?; let answer_msg = calltype::run_get_method( self.ton.clone(), @@ -820,7 +842,7 @@ impl DEngine { }, DebotCallType::External{msg, dest} => { debug!("External call"); - let target_state = self.load_state(dest.clone()).await + let target_state = Self::load_state(self.ton.clone(), dest.clone()).await .map_err(|e| Error::execute_failed(e))?; let signing_box = self.browser.get_signing_box().await .map_err(|e| Error::external_call_failed(e))?; @@ -854,17 +876,23 @@ impl DEngine { && err.code < (ClientError::PROCESSING as u32) { // when debot function throws an exception if let Some(e) = err.data["exit_code"].as_i64() { - self.run_debot_get("getErrorDescription", Some(json!({ "error": e }))) - .await - .ok() - .and_then(|res| { - res["desc"].as_str().and_then(|hex| { - hex::decode(&hex) - .ok() - .and_then(|vec| String::from_utf8(vec).ok()) + Self::run( + self.ton.clone(), + self.state.clone(), + self.addr.clone(), + self.abi.clone(), + "getErrorDescription", + Some(json!({ "error": e })) + ).await.ok().and_then(|res| { + res.return_value.and_then(|v| + v["desc"].as_str().and_then(|hex| { + hex::decode(&hex) + .ok() + .and_then(|vec| String::from_utf8(vec).ok()) }) - }) - .unwrap_or(err.message) + ) + }) + .unwrap_or(err.message) } else { err.message } diff --git a/ton_client/src/debot/errors.rs b/ton_client/src/debot/errors.rs index 07cc5e4e7..953f1135d 100644 --- a/ton_client/src/debot/errors.rs +++ b/ton_client/src/debot/errors.rs @@ -26,6 +26,8 @@ pub enum ErrorCode { DebotGetMethodFailed = 808, DebotInvalidMsg = 809, DebotExternalCallFailed = 810, + DebotBrowserCallbackFailed = 811, + DebotOperationRejected = 812, } pub struct Error; @@ -103,4 +105,18 @@ impl Error { format!("external call failed: ({})", err), ) } + + pub fn operation_rejected() -> ClientError { + error( + ErrorCode::DebotOperationRejected, + format!("Debot operation was rejected by user"), + ) + } + + pub fn browser_callback_failed(err: impl Display) -> ClientError { + error( + ErrorCode::DebotBrowserCallbackFailed, + format!("Debot browser callback failed: {}", err), + ) + } } diff --git a/ton_client/src/debot/info.rs b/ton_client/src/debot/info.rs new file mode 100644 index 000000000..4a29f02c5 --- /dev/null +++ b/ton_client/src/debot/info.rs @@ -0,0 +1,62 @@ +use super::context::{str_hex_to_utf8}; +use serde::{Deserialize, Deserializer}; +use crate::encoding::account_decode; + +#[derive(Deserialize, Default, Debug, Clone)] +pub struct DInfo { + #[serde(deserialize_with = "from_opt_hex_to_str")] + pub name: Option, + #[serde(deserialize_with = "from_opt_hex_to_str")] + pub version: Option, + #[serde(deserialize_with = "from_opt_hex_to_str")] + pub publisher: Option, + #[serde(deserialize_with = "from_opt_hex_to_str")] + pub key: Option, + #[serde(deserialize_with = "from_opt_hex_to_str")] + pub author: Option, + #[serde(deserialize_with = "validate_ton_address")] + pub support: Option, + #[serde(deserialize_with = "from_opt_hex_to_str")] + pub hello: Option, + #[serde(deserialize_with = "from_opt_hex_to_str")] + pub language: Option, + #[serde(deserialize_with = "from_opt_hex_to_str")] + pub dabi: Option, + pub icon: Option, + #[serde(default)] + pub interfaces: Vec, +} + +impl DInfo { + pub fn validate(&self) -> Result<(), String> { + Ok(()) + } +} + +fn validate_ton_address<'de, D>(des: D) -> Result, D::Error> +where + D: Deserializer<'de> +{ + let s: Option = Deserialize::deserialize(des)?; + if let Some(s) = s { + let _ = account_decode(&s) + .map_err(serde::de::Error::custom)?; + Ok(Some(s)) + } else { + Ok(None) + } +} + +fn from_opt_hex_to_str<'de, D>(des: D) -> Result, D::Error> +where + D: Deserializer<'de> +{ + let s: Option = Deserialize::deserialize(des)?; + if let Some(s) = s { + let utf8_str = str_hex_to_utf8(&s) + .ok_or(format!("failed to convert bytes to utf8 string")).unwrap(); + Ok(Some(utf8_str)) + } else { + Ok(None) + } +} \ No newline at end of file diff --git a/ton_client/src/debot/mod.rs b/ton_client/src/debot/mod.rs index b0f43c743..88376b5e9 100644 --- a/ton_client/src/debot/mod.rs +++ b/ton_client/src/debot/mod.rs @@ -12,6 +12,7 @@ */ mod action; +mod activity; mod base64_interface; mod hex_interface; mod browser; @@ -22,6 +23,7 @@ mod dengine; mod dinterface; mod errors; mod helpers; +mod info; mod msg_interface; mod routines; mod run_output; @@ -30,12 +32,13 @@ mod sdk_interface; mod tests; pub use action::DAction; +pub use activity::{DebotActivity, Spending}; pub use browser::BrowserCallbacks; pub use context::{DContext, STATE_EXIT, STATE_ZERO}; pub use dengine::DEngine; pub use dinterface::{DebotInterface, DebotInterfaceExecutor, InterfaceResult}; pub use errors::{Error, ErrorCode}; - +use info::DInfo; use crate::error::ClientResult; use crate::ClientContext; use std::sync::Arc; @@ -96,79 +99,150 @@ impl Into for DebotAction { } } -/// [UNSTABLE](UNSTABLE.md) Parameters to start debot. -#[derive(Serialize, Deserialize, Default, ApiType)] -pub struct ParamsOfStart { - /// Debot smart contract address - address: String, +/// [UNSTABLE](UNSTABLE.md) Describes DeBot metadata. +#[derive(Serialize, Deserialize, Clone, Debug, ApiType, Default, PartialEq)] +pub struct DebotInfo { + /// DeBot short name. + pub name: Option, + /// DeBot semantic version. + pub version: Option, + /// The name of DeBot deployer. + pub publisher: Option, + /// Short info about DeBot. + pub key: Option, + /// The name of DeBot developer. + pub author: Option, + /// TON address of author for questions and donations. + pub support: Option, + /// String with the first messsage from DeBot. + pub hello: Option, + /// String with DeBot interface language (ISO-639). + pub language: Option, + /// String with DeBot ABI. + pub dabi: Option, + /// DeBot icon. + pub icon: Option, + /// Vector with IDs of DInterfaces used by DeBot. + pub interfaces: Vec, } -/// [UNSTABLE](UNSTABLE.md) Structure for storing debot handle returned from `start` and `fetch` functions. -#[derive(Serialize, Deserialize, ApiType, Default)] -pub struct RegisteredDebot { +impl From for DebotInfo { + fn from(info: DInfo) -> Self { + Self { + name: info.name, + version: info.version, + publisher: info.publisher, + key: info.key, + author: info.author, + support: info.support, + hello: info.hello, + language: info.language, + dabi: info.dabi, + icon : info.icon, + interfaces: info.interfaces, + } + } +} + +/// [UNSTABLE](UNSTABLE.md) Parameters to start DeBot. +/// DeBot must be already initialized with init() function. +#[derive(Serialize, Deserialize, Default, ApiType)] +pub struct ParamsOfStart { /// Debot handle which references an instance of debot engine. - pub debot_handle: DebotHandle, - /// Debot abi as json string. - pub debot_abi: String, + debot_handle: DebotHandle, } -/// [UNSTABLE](UNSTABLE.md) Starts an instance of debot. +/// [UNSTABLE](UNSTABLE.md) Starts the DeBot. /// /// Downloads debot smart contract from blockchain and switches it to /// context zero. -/// Returns a debot handle which can be used later in `execute` function. +/// /// This function must be used by Debot Browser to start a dialog with debot. /// While the function is executing, several Browser Callbacks can be called, /// since the debot tries to display all actions from the context 0 to the user. /// -/// # Remarks -/// `start` is equivalent to `fetch` + switch to context 0. -/// /// When the debot starts SDK registers `BrowserCallbacks` AppObject. /// Therefore when `debote.remove` is called the debot is being deleted and the callback is called /// with `finish`=`true` which indicates that it will never be used again. +#[api_function] pub async fn start( context: Arc, params: ParamsOfStart, - callbacks: impl BrowserCallbacks + Send + Sync + 'static, -) -> ClientResult { - let mut dengine = - DEngine::new_with_client(params.address, None, context.clone(), Arc::new(callbacks)); - let debot_abi = dengine.start().await.map_err(Error::start_failed)?; +) -> ClientResult<()> { + let mutex = context + .debots + .get(¶ms.debot_handle.0) + .ok_or(Error::invalid_handle(params.debot_handle.0))?; + let mut dengine = mutex.1.lock().await; + dengine.start().await.map_err(Error::start_failed) +} - let handle = context.get_next_id(); - context.debots.insert(handle, Mutex::new(dengine)); +/// [UNSTABLE](UNSTABLE.md) Parameters to fetch DeBot metadata. +#[derive(Serialize, Deserialize, Default, ApiType)] +pub struct ParamsOfFetch { + /// Debot smart contract address. + pub address: String, +} + +/// [UNSTABLE](UNSTABLE.md) +#[derive(Serialize, Deserialize, Default, ApiType)] +pub struct ResultOfFetch { + /// Debot metadata. + pub info: DebotInfo, +} - Ok(RegisteredDebot { debot_handle: DebotHandle(handle), debot_abi }) +/// [UNSTABLE](UNSTABLE.md) Fetches DeBot metadata from blockchain. +/// +/// Downloads DeBot from blockchain and creates and fetches its metadata. +#[api_function] +pub async fn fetch( + context: Arc, + params: ParamsOfFetch, +) -> ClientResult { + Ok(ResultOfFetch { + info : DEngine::fetch(context, params.address).await.map_err(Error::fetch_failed)?.into() + }) } -/// [UNSTABLE](UNSTABLE.md) Parameters to fetch debot. +/// [UNSTABLE](UNSTABLE.md) Parameters to init DeBot. #[derive(Serialize, Deserialize, Default, ApiType)] -pub struct ParamsOfFetch { +pub struct ParamsOfInit { /// Debot smart contract address pub address: String, } -/// [UNSTABLE](UNSTABLE.md) Fetches debot from blockchain. +/// [UNSTABLE](UNSTABLE.md) Structure for storing debot handle returned from `init` function. +#[derive(Serialize, Deserialize, ApiType, Default)] +pub struct RegisteredDebot { + /// Debot handle which references an instance of debot engine. + pub debot_handle: DebotHandle, + /// Debot abi as json string. + pub debot_abi: String, + /// Debot metadata. + pub info: DebotInfo, +} + +/// [UNSTABLE](UNSTABLE.md) Creates an instance of DeBot. /// -/// Downloads debot smart contract (code and data) from blockchain and creates +/// Downloads DeBot smart contract (code and data) from blockchain and creates /// an instance of Debot Engine for it. -/// +/// Returns a debot handle which can be used later in `start`, `execute` or `send` functions. /// # Remarks /// It does not switch debot to context 0. Browser Callbacks are not called. -pub async fn fetch( +/// Can be used to invoke DeBot without starting. +pub async fn init( context: Arc, - params: ParamsOfFetch, + params: ParamsOfInit, callbacks: impl BrowserCallbacks + Send + Sync + 'static, ) -> ClientResult { let mut dengine = DEngine::new_with_client(params.address, None, context.clone(), Arc::new(callbacks)); - let debot_abi = dengine.fetch().await.map_err(Error::fetch_failed)?; + let info: DebotInfo = dengine.init().await.map_err(Error::fetch_failed)?.into(); let handle = context.get_next_id(); context.debots.insert(handle, Mutex::new(dengine)); - - Ok(RegisteredDebot { debot_handle: DebotHandle(handle), debot_abi }) + let debot_abi = info.dabi.clone().unwrap_or(String::new()); + Ok(RegisteredDebot { debot_handle: DebotHandle(handle), info, debot_abi }) } /// [UNSTABLE](UNSTABLE.md) Parameters for executing debot action. @@ -200,11 +274,18 @@ pub async fn execute(context: Arc, params: ParamsOfExecute) -> Cl .map_err(Error::execute_failed) } +/// [UNSTABLE](UNSTABLE.md) +#[derive(Serialize, Deserialize, ApiType, Default)] +pub struct ParamsOfRemove { + /// Debot handle which references an instance of debot engine. + pub debot_handle: DebotHandle, +} + /// [UNSTABLE](UNSTABLE.md) Destroys debot handle. /// /// Removes handle from Client Context and drops debot engine referenced by that handle. #[api_function] -pub fn remove(context: Arc, params: RegisteredDebot) -> ClientResult<()> { +pub fn remove(context: Arc, params: ParamsOfRemove) -> ClientResult<()> { context.debots.remove(¶ms.debot_handle.0); Ok(()) } diff --git a/ton_client/src/debot/msg_interface.rs b/ton_client/src/debot/msg_interface.rs index c4a9448ee..6fd080648 100644 --- a/ton_client/src/debot/msg_interface.rs +++ b/ton_client/src/debot/msg_interface.rs @@ -8,6 +8,8 @@ use crate::encoding::decode_abi_bigint; use serde_json::Value; use std::sync::Arc; use ton_abi::Contract; +use crate::boc::{parse_message, ParamsOfParse}; +use crate::debot::DEngine; const ABI: &str = r#" { @@ -68,12 +70,20 @@ impl MsgInterface { .await .map_err(|e| format!("{}", e))? .handle; + let parsed_msg = parse_message(self.ton.clone(), ParamsOfParse { boc: message.clone() }) + .await + .map_err(|e| format!("{}", e))? + .parsed; + let dest = parsed_msg["dst"].as_str().ok_or(format!("failed to parse dst address"))?.to_owned(); + let target_state = DEngine::load_state(self.ton.clone(), dest) + .await + .map_err(|e| format!("{}", e))?; let msg = send_ext_msg( self.browser.clone(), self.ton.clone(), message, signing_box, - String::new(), + target_state, &self.debot_addr, ) .await diff --git a/ton_client/src/debot/tests.rs b/ton_client/src/debot/tests.rs index 0538f2414..618c37667 100644 --- a/ton_client/src/debot/tests.rs +++ b/ton_client/src/debot/tests.rs @@ -43,6 +43,13 @@ const TEST_DEBOT5: &'static str = "testDebot5"; const TEST_DEBOTA: &'static str = "tda"; const TEST_DEBOTB: &'static str = "tdb"; +struct ExpectedTransaction { + dst: String, + out: Vec, + setcode: bool, + signkey: String, + approved: bool, +} const SUPPORTED_INTERFACES: &[&str] = &[ "f6927c0d4bdb69e1b52d27f018d156ff04152f00558042ff674f0fec32e4369d", // echo @@ -91,6 +98,17 @@ const TERMINAL_ABI: &str = r#" "outputs": [ {"name":"value","type":"int256"} ] + }, + { + "name": "input", + "inputs": [ + {"name":"answerId","type":"uint32"}, + {"name":"prompt","type":"bytes"}, + {"name":"multiline","type":"bool"} + ], + "outputs": [ + {"name":"value","type":"bytes"} + ] } ], "data": [], @@ -151,7 +169,16 @@ impl Terminal { let _ = self.print(answer_id, prompt); // use test return value here. (answer_id, json!({"value": 1})) - } + }, + "input" => { + let answer_id = decode_abi_number::(args["answerId"].as_str().unwrap()).unwrap(); + let prompt = hex::decode(args["prompt"].as_str().unwrap()).unwrap(); + let message = std::str::from_utf8(&prompt).unwrap(); + let _ = args["multiline"].as_bool().unwrap(); + self.print(answer_id, message); + let value = "testinput"; + (answer_id, json!({ "value": hex::encode(value.as_bytes()) }) ) + }, _ => panic!("interface function not found"), } } @@ -187,10 +214,16 @@ struct BrowserData { pub terminal: Mutex, pub echo: Echo, pub bots: Mutex>, + pub info: DebotInfo, + pub activity: Mutex>, } impl TestBrowser { - async fn fetch_debot(client: Arc, state: Arc, address: String, start_function: &str) -> RegisteredDebot { + async fn fetch_debot( + client: Arc, + state: Arc, + address: String, + ) -> RegisteredDebot { let state_copy = state.clone(); let client_copy = client.clone(); let callback = move |params, response_type| { @@ -219,21 +252,38 @@ impl TestBrowser { }; let handle: RegisteredDebot = client.request_async_callback( - start_function, - ParamsOfStart { address: address.clone() }, + "debot.init", + ParamsOfInit { address: address.clone() }, callback ).await.unwrap(); let handle_copy = RegisteredDebot { debot_handle: handle.debot_handle.clone(), debot_abi: handle.debot_abi.clone(), + info: handle.info.clone(), }; state.bots.lock().await.insert(address.clone(), handle_copy); handle } - pub async fn execute_from_state(client: Arc, state: Arc, start_function: &str) { - let handle = Self::fetch_debot(client.clone(), state.clone(), state.address.clone(), start_function).await; + pub async fn execute_from_state(client: Arc, state: Arc, call_start: bool) { + if call_start { + let res: ResultOfFetch = client.request_async( + "debot.fetch", + ParamsOfFetch { address: state.address.clone() }, + ).await.unwrap(); + assert_eq!(res.info, state.info); + } + let handle = Self::fetch_debot(client.clone(), state.clone(), state.address.clone()).await; + + if call_start { + let _: () = client.request_async( + "debot.start", + ParamsOfStart { + debot_handle: handle.debot_handle.clone(), + } + ).await.unwrap(); + } while !state.finished.load(Ordering::Relaxed) { Self::handle_message_queue(client.clone(), state.clone()).await; @@ -284,17 +334,37 @@ impl TestBrowser { keys: KeyPair, steps: Vec, terminal_outputs: Vec, + abi: String, ) { - Self::execute_with_func(client, address, keys, steps, terminal_outputs, "debot.start").await; + let mut info = DebotInfo::default(); + info.dabi = Some(abi); + let state = Arc::new(BrowserData { + current: Mutex::new(Default::default()), + next: Mutex::new(steps), + client: client.clone(), + keys, + address: address.clone(), + finished: AtomicBool::new(false), + switch_started: AtomicBool::new(false), + msg_queue: Mutex::new(Default::default()), + terminal: Mutex::new(Terminal::new(terminal_outputs)), + echo: Echo::new(), + bots: Mutex::new(HashMap::new()), + info, + activity: Mutex::new(vec![]), + }); + + Self::execute_from_state(client, state, true).await } - pub async fn execute_with_func( + pub async fn execute_with_details( client: Arc, address: String, keys: KeyPair, steps: Vec, terminal_outputs: Vec, - entry_function: &str, + info: DebotInfo, + activity: Vec, ) { let state = Arc::new(BrowserData { current: Mutex::new(Default::default()), @@ -308,9 +378,11 @@ impl TestBrowser { terminal: Mutex::new(Terminal::new(terminal_outputs)), echo: Echo::new(), bots: Mutex::new(HashMap::new()), + info, + activity: Mutex::new(activity), }); - Self::execute_from_state(client, state, entry_function).await + Self::execute_from_state(client, state, true).await } async fn process_notification(state: &BrowserData, params: ParamsOfAppDebotBrowser) { @@ -339,9 +411,11 @@ impl TestBrowser { } fn call_execute_boxed( - client: Arc, state: Arc, start_function: &'static str + client: Arc, + state: Arc, + call_start: bool, ) -> BoxFuture<'static, ()> { - Self::execute_from_state(client, state, start_function).boxed() + Self::execute_from_state(client, state, call_start).boxed() } async fn process_call(client: Arc, state: &BrowserData, params: ParamsOfAppDebotBrowser) -> ResultOfAppDebotBrowser { @@ -378,10 +452,28 @@ impl TestBrowser { terminal: Mutex::new(Terminal::new(vec![])), echo: Echo::new(), bots: Mutex::new(HashMap::new()), + info: Default::default(), + activity: Mutex::new(vec![]), }); - Self::call_execute_boxed(client, state, "debot.fetch").await; + Self::call_execute_boxed(client, state, false).await; ResultOfAppDebotBrowser::InvokeDebot }, + ParamsOfAppDebotBrowser::Approve {activity} => { + let mut approved = true; + if let Some(expected) = state.activity.lock().await.pop() { + approved = expected.approved; + match activity { + DebotActivity::Transaction{msg: _, dst, out, fee, setcode, signkey} => { + assert_eq!(expected.dst, dst); + assert_eq!(expected.out, out); + assert_eq!(expected.setcode, setcode); + assert_eq!(expected.signkey, signkey); + assert!(fee > 0); + }, + } + } + ResultOfAppDebotBrowser::Approve{ approved } + }, _ => panic!("invalid call {:#?}", params) } } @@ -443,7 +535,6 @@ impl TestBrowser { client.clone(), state.clone(), dest_addr.to_owned(), - "debot.fetch", ).await; } @@ -488,6 +579,7 @@ struct DebotData { debot_addr: String, target_addr: String, keys: KeyPair, + abi: String, } async fn init_debot(client: Arc) -> DebotData { @@ -568,7 +660,8 @@ async fn init_debot(client: Arc) -> DebotData { let data = DebotData { debot_addr, target_addr, - keys + keys, + abi: debot_abi.json_string().unwrap(), }; *debot = Some(data.clone()); data @@ -602,7 +695,7 @@ async fn init_debot2(client: Arc) -> DebotData { Signer::Keys { keys: keys.clone() }, ).await.unwrap(); let target_addr = String::new(); - DebotData { debot_addr, target_addr, keys } + DebotData { debot_addr, target_addr, keys, abi: debot_abi.json_string().unwrap(), } } async fn init_debot4(client: Arc) -> DebotData { @@ -658,24 +751,29 @@ async fn init_debot4(client: Arc) -> DebotData { DebotData { debot_addr, target_addr, - keys + keys, + abi: debot_abi.json_string().unwrap(), } } async fn init_debot3(client: Arc) -> DebotData { + init_simple_debot(client, TEST_DEBOT3).await +} + +async fn init_simple_debot(client: Arc, name: &str) -> DebotData { let keys = client.generate_sign_keys(); - let debot_abi = TestClient::abi(TEST_DEBOT3, Some(2)); + let debot_abi = TestClient::abi(name, Some(2)); let call_set = CallSet::some_with_function("constructor"); let deploy_debot_params = ParamsOfEncodeMessage { abi: debot_abi.clone(), - deploy_set: DeploySet::some_with_tvc(TestClient::tvc(TEST_DEBOT3, Some(2))), + deploy_set: DeploySet::some_with_tvc(TestClient::tvc(name, Some(2))), signer: Signer::Keys { keys: keys.clone() }, processing_try_index: None, address: None, call_set, }; - let debot_addr = client.deploy_with_giver_async(deploy_debot_params, Some(1_000_000_000u64)).await; + let debot_addr = client.deploy_with_giver_async(deploy_debot_params, Some(100_000_000_000u64)).await; let _ = client.net_process_function( debot_addr.clone(), debot_abi.clone(), @@ -687,26 +785,16 @@ async fn init_debot3(client: Arc) -> DebotData { debot_addr, target_addr: String::new(), keys, + abi: debot_abi.json_string().unwrap(), } } -async fn init_debot5(client: Arc, count: u32) -> String { +async fn init_debot5(client: Arc, count: u32) -> (String, String) { let debot_abi = TestClient::abi(TEST_DEBOT5, Some(2)); - let debot5_tvc = TestClient::tvc(TEST_DEBOT5, Some(2)); - - let result: ResultOfGetCodeFromTvc = client.request_async( - "boc.get_code_from_tvc", - ParamsOfGetCodeFromTvc { tvc: debot5_tvc } - ).await.unwrap(); - - let result: ResultOfGetBocHash = client.request_async( - "boc.get_boc_hash", - ParamsOfGetBocHash { boc: result.code } - ).await.unwrap(); - + let hash_str = get_code_hash_from_tvc(client.clone(), TEST_DEBOT5).await; let call_set = CallSet::some_with_function_and_input( "constructor", - json!({ "codeHash": format!("0x{}", result.hash) }), + json!({ "codeHash": format!("0x{}", hash_str) }), ); let mut deploy_debot_params = ParamsOfEncodeMessage { abi: debot_abi.clone(), @@ -731,10 +819,10 @@ async fn init_debot5(client: Arc, count: u32) -> String { ).await.unwrap(); } } - addrs[0].clone() + (addrs[0].clone(), debot_abi.json_string().unwrap()) } -async fn init_debot_pair(client: Arc, debot1: &str, debot2: &str) -> (String, String) { +async fn init_debot_pair(client: Arc, debot1: &str, debot2: &str) -> (String, String, String) { let keys = client.generate_sign_keys(); let debot1_abi = TestClient::abi(debot1, Some(2)); let debot2_abi = TestClient::abi(debot2, Some(2)); @@ -785,8 +873,55 @@ async fn init_debot_pair(client: Arc, debot1: &str, debot2: &str) -> let (_, _) = futures::join!(future1, future2); - (debot1_addr, debot2_addr) + (debot1_addr, debot2_addr, debot1_abi.json_string().unwrap()) +} +async fn init_hello_debot(client: Arc) -> DebotData { + let data = init_simple_debot(client.clone(), "helloDebot").await; + let abi = Abi::Contract(serde_json::from_str(&data.abi).unwrap()); + let _ = client.net_process_function( + data.debot_addr.clone(), + abi, + "setIcon", + json!({ "icon": TestClient::icon("helloDebot", Some(2)) }), + Signer::Keys { keys: data.keys.clone() }, + ).await.unwrap(); + data +} + +async fn count_accounts_by_codehash(client: Arc, code_hash: String) -> u32 { + let res: ResultOfQueryCollection = client + .request_async( + "net.query_collection", + ParamsOfQueryCollection { + collection: "accounts".to_owned(), + filter: Some(json!({ + "code_hash": { "eq": code_hash} + })), + result: "id".to_owned(), + limit: None, + order: None, + }, + ) + .await + .unwrap(); + + res.result.len() as u32 +} + +async fn get_code_hash_from_tvc(client: Arc, name: &str) -> String { + let debot_tvc = TestClient::tvc(name, Some(2)); + let result: ResultOfGetCodeFromTvc = client.request_async( + "boc.get_code_from_tvc", + ParamsOfGetCodeFromTvc { tvc: debot_tvc } + ).await.unwrap(); + + let result: ResultOfGetBocHash = client.request_async( + "boc.get_boc_hash", + ParamsOfGetBocHash { boc: result.code } + ).await.unwrap(); + + result.hash } const EXIT_CHOICE: u8 = 9; @@ -794,7 +929,7 @@ const EXIT_CHOICE: u8 = 9; #[tokio::test(core_threads = 2)] async fn test_debot_goto() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot(client.clone()).await; let steps = json!([ { "choice": 1, "inputs": [], "outputs": ["Test Goto Action"] }, @@ -807,13 +942,14 @@ async fn test_debot_goto() { keys.clone(), serde_json::from_value(steps).unwrap(), vec![], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_print() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr, keys } = init_debot(client.clone()).await; + let DebotData { debot_addr, target_addr, keys, abi } = init_debot(client.clone()).await; let steps = json!([ { "choice": 2, "inputs": [], "outputs": ["Test Print Action", "test2: instant print", "test instant print"] }, @@ -828,13 +964,14 @@ async fn test_debot_print() { keys.clone(), serde_json::from_value(steps).unwrap(), vec![], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_runact() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot(client.clone()).await; let steps = json!([ { "choice": 3, "inputs": [], "outputs": ["Test Run Action"] }, @@ -850,14 +987,15 @@ async fn test_debot_runact() { debot_addr, keys, serde_json::from_value(steps).unwrap(), - vec![] + vec![], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_run_method() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot(client.clone()).await; let steps = json!([ { "choice": 4, "inputs": [], "outputs": ["Test Run Method Action"] }, @@ -871,14 +1009,15 @@ async fn test_debot_run_method() { debot_addr, keys, serde_json::from_value(steps).unwrap(), - vec![] + vec![], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_send_msg() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot(client.clone()).await; let steps = json!([ { "choice": 5, "inputs": [], "outputs": ["Test Send Msg Action"] }, @@ -894,13 +1033,14 @@ async fn test_debot_send_msg() { keys, serde_json::from_value(steps).unwrap(), vec![], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_invoke_debot() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot(client.clone()).await; let steps = json!([ { "choice": 6, "inputs": [debot_addr.clone()], "outputs": ["Test Invoke Debot Action", "enter debot address:"] }, @@ -920,14 +1060,15 @@ async fn test_debot_invoke_debot() { debot_addr, keys, serde_json::from_value(steps).unwrap(), - vec![] + vec![], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_engine_calls() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot(client.clone()).await; let steps = json!([ { "choice": 7, "inputs": [], "outputs": ["Test Engine Calls"] }, @@ -944,14 +1085,15 @@ async fn test_debot_engine_calls() { debot_addr, keys, serde_json::from_value(steps).unwrap(), - vec![] + vec![], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_interface_call() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot(client.clone()).await; let steps = json!([ { "choice": 8, "inputs": [], "outputs": ["", "test1 - call interface"] }, @@ -964,13 +1106,14 @@ async fn test_debot_interface_call() { keys, serde_json::from_value(steps).unwrap(), vec![], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_inner_interfaces() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot3(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot3(client.clone()).await; let steps = serde_json::from_value(json!([])).unwrap(); TestBrowser::execute( @@ -994,13 +1137,14 @@ async fn test_debot_inner_interfaces() { format!("test hex decode passed"), format!("test base64 decode passed"), ], + abi ).await; } #[tokio::test(core_threads = 2)] async fn test_debot_4() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr, keys } = init_debot4(client.clone()).await; + let DebotData { debot_addr, target_addr, keys, abi } = init_debot4(client.clone()).await; let target_abi = TestClient::abi(TEST_DEBOT_TARGET, Some(2)); let target_boc = download_account(&client, &target_addr).await.expect("account must exist"); @@ -1027,6 +1171,7 @@ async fn test_debot_4() { format!("Transaction succeeded"), format!("setData2(129)"), ], + abi ).await; let target_boc = download_account(&client, &target_addr).await.expect("account must exist"); @@ -1052,7 +1197,7 @@ async fn test_debot_4() { #[tokio::test(core_threads = 2)] async fn test_debot_msg_interface() { let client = std::sync::Arc::new(TestClient::new()); - let DebotData { debot_addr, target_addr: _, keys } = init_debot2(client.clone()).await; + let DebotData { debot_addr, target_addr: _, keys, abi } = init_debot2(client.clone()).await; let debot_abi = TestClient::abi(TEST_DEBOT2, Some(2)); let counter = 10; let counter_after = 15; @@ -1077,6 +1222,7 @@ async fn test_debot_msg_interface() { format!("Increment succeeded"), format!("counter={}", counter_after), ], + abi ).await; assert_get_method( @@ -1092,7 +1238,7 @@ async fn test_debot_msg_interface() { #[tokio::test(core_threads = 2)] async fn test_debot_invoke_msgs() { let client = std::sync::Arc::new(TestClient::new()); - let (debot1, _) = init_debot_pair(client.clone(), TEST_DEBOTA, TEST_DEBOTB).await; + let (debot1, _, abi) = init_debot_pair(client.clone(), TEST_DEBOTA, TEST_DEBOTB).await; let keys = client.generate_sign_keys(); let steps = serde_json::from_value(json!([])).unwrap(); @@ -1106,24 +1252,106 @@ async fn test_debot_invoke_msgs() { format!("DebotB receives question: What is your name?"), format!("DebotA receives answer: My name is DebotB"), ], + abi ).await; } -// TODO: make test runnable not only once -#[ignore] #[tokio::test(core_threads = 2)] async fn test_debot_sdk_get_accounts_by_hash() { let client = std::sync::Arc::new(TestClient::new()); - let count = 6; - let debot = init_debot5(client.clone(), count).await; - + let deploy_count = 8; + let (debot, abi) = init_debot5(client.clone(), deploy_count).await; + let code_hash = get_code_hash_from_tvc(client.clone(), TEST_DEBOT5).await; + let total_count = count_accounts_by_codehash(client.clone(), code_hash).await; let steps = serde_json::from_value(json!([])).unwrap(); TestBrowser::execute( client.clone(), debot.clone(), KeyPair::default(), steps, - vec![ format!("{} contracts.", count) ], + vec![ format!("{} contracts.", total_count) ], + abi + ).await; +} + +#[tokio::test(core_threads = 2)] +async fn test_debot_getinfo() { + let client = std::sync::Arc::new(TestClient::new()); + let DebotData { debot_addr, target_addr: _, keys, abi } = init_hello_debot(client.clone()).await; + let icon = TestClient::icon("helloDebot", Some(2)); + let steps = serde_json::from_value(json!([])).unwrap(); + TestBrowser::execute_with_details( + client.clone(), + debot_addr.clone(), + keys, + steps, + vec![ + format!("Hello, World!"), + format!("How is it going?"), + format!("You have entered \"testinput\""), + ], + DebotInfo { + name: Some("HelloWorld".to_owned()), + version: Some("0.2.0".to_owned()), + publisher: Some("TON Labs".to_owned()), + key: Some("Start develop DeBot from here".to_owned()), + author: Some("TON Labs".to_owned()), + support: Some("0:841288ed3b55d9cdafa806807f02a0ae0c169aa5edfe88a789a6482429756a94".to_owned()), + hello: Some("Hello, i am a HelloWorld DeBot.".to_owned()), + language: Some("en".to_owned()), + dabi: Some(abi), + icon: Some(icon), + interfaces: vec!["0x8796536366ee21852db56dccb60bc564598b618c865fc50c8b1ab740bba128e3".to_owned()], + }, + vec![], + ).await; +} + +#[tokio::test(core_threads = 2)] +async fn test_debot_approve() { + let client = std::sync::Arc::new(TestClient::new()); + let DebotData { debot_addr, target_addr: _, keys, abi } = init_simple_debot(client.clone(), "testDebot6").await; + let mut info = DebotInfo::default(); + info.dabi = Some(abi); + let steps = serde_json::from_value(json!([])).unwrap(); + TestBrowser::execute_with_details( + client.clone(), + debot_addr.clone(), + keys.clone(), + steps, + vec![ + format!("Send1 succeeded"), + format!("Send2 rejected"), + ], + info, + vec![ + ExpectedTransaction { + dst: debot_addr.clone(), + out: vec![], + setcode: false, + signkey: keys.public.clone(), + approved: true, + }, + ExpectedTransaction { + dst: debot_addr.clone(), + out: vec![ + Spending{amount: 10000000000, dst: debot_addr.clone()}, + ], + setcode: false, + signkey: keys.public.clone(), + approved: false, + }, + ExpectedTransaction { + dst: debot_addr.clone(), + out: vec![ + Spending{amount: 2200000000, dst: debot_addr.clone()}, + Spending{amount: 3500000000, dst: format!("0:{:064}", 0)}, + ], + setcode: false, + signkey: keys.public.clone(), + approved: true, + }, + ], ).await; } @@ -1148,7 +1376,14 @@ async fn download_account(client: &Arc, addr: &str) -> Option, addr: &String, abi: &Abi, func: &str, params: Value, returns: Value) { +async fn assert_get_method( + client: &Arc, + addr: &String, + abi: &Abi, + func: &str, + params: Value, + returns: Value +) { let client = client.clone(); let acc_boc = download_account(&client, &addr).await.expect("Account not found"); diff --git a/ton_client/src/json_interface/debot.rs b/ton_client/src/json_interface/debot.rs index 7d8c73fb3..9b37fdcd5 100644 --- a/ton_client/src/json_interface/debot.rs +++ b/ton_client/src/json_interface/debot.rs @@ -12,11 +12,11 @@ * */ - use crate::client::{AppObject, ClientContext}; - use crate::error::ClientResult; - use crate::debot::{DAction, DebotAction, BrowserCallbacks, ParamsOfFetch, - ParamsOfStart, RegisteredDebot}; - use crate::crypto::SigningBoxHandle; +use crate::client::{AppObject, ClientContext}; +use crate::error::ClientResult; +use crate::debot::Error; +use crate::debot::{DAction, DebotAction, BrowserCallbacks, ParamsOfInit, RegisteredDebot, DebotActivity}; +use crate::crypto::SigningBoxHandle; /// [UNSTABLE](UNSTABLE.md) Returning values from Debot Browser callbacks. #[derive(Serialize, Deserialize, Clone, Debug, ApiType)] @@ -28,21 +28,26 @@ pub enum ResultOfAppDebotBrowser { value: String }, /// Result of getting signing box. - GetSigningBox { + GetSigningBox { /// Signing box for signing data requested by debot engine. Signing box is owned and disposed by debot engine signing_box: SigningBoxHandle }, /// Result of debot invoking. InvokeDebot, + /// Result of `approve` callback. + Approve { + /// Indicates whether the DeBot is allowed to perform the specified operation. + approved: bool, + } } /// [UNSTABLE](UNSTABLE.md) Debot Browser callbacks -/// +/// /// Called by debot engine to communicate with debot browser. #[derive(Serialize, Deserialize, Clone, Debug, ApiType)] #[serde(tag="type")] pub enum ParamsOfAppDebotBrowser { - /// Print message to user. + /// Print message to user. Log { /// A string that must be printed to user. msg: String @@ -61,7 +66,7 @@ pub enum ParamsOfAppDebotBrowser { /// At least `description` property must be shown from [DebotAction] structure. action: DebotAction }, - /// Request user input. + /// Request user input. Input { /// A prompt string that must be printed to user before input request. prompt: String @@ -77,32 +82,37 @@ pub enum ParamsOfAppDebotBrowser { }, /// Used by Debot to call DInterface implemented by Debot Browser. Send { - /// Internal message to DInterface address. Message body contains + /// Internal message to DInterface address. Message body contains /// interface function and parameters. message: String, - } + }, + /// Requests permission from DeBot Browser to execute DeBot operation. + Approve { + /// DeBot activity details. + activity: DebotActivity, + }, } - + /// Wrapper for native Debot Browser callbacks. -/// +/// /// Adapter between SDK application and low level debot interface. pub(crate) struct DebotBrowserAdapter { app_object: AppObject, } - + impl DebotBrowserAdapter { pub fn new(app_object: AppObject) -> Self { Self { app_object } } } - + #[async_trait::async_trait] impl BrowserCallbacks for DebotBrowserAdapter { - + async fn log(&self, msg: String) { self.app_object.notify(ParamsOfAppDebotBrowser::Log { msg }); } - + async fn switch(&self, ctx_id: u8) { self.app_object.notify(ParamsOfAppDebotBrowser::Switch { context_id: ctx_id }); } @@ -110,11 +120,11 @@ impl DebotBrowserAdapter { async fn switch_completed(&self) { self.app_object.notify(ParamsOfAppDebotBrowser::SwitchCompleted); } - + async fn show_action(&self, act: DAction) { self.app_object.notify(ParamsOfAppDebotBrowser::ShowAction { action: act.into() }); } - + async fn input(&self, prompt: &str, value: &mut String) { let response = self.app_object.call(ParamsOfAppDebotBrowser::Input { prompt: prompt.to_owned(), @@ -128,19 +138,19 @@ impl DebotBrowserAdapter { Err(e) => error!("debot browser failed to show action: {}", e), } } - + async fn get_signing_box(&self) -> Result { let response = self.app_object.call(ParamsOfAppDebotBrowser::GetSigningBox) .await .map_err(|err| format!("debot browser failed to load keys: {}", err))?; - + match response { ResultOfAppDebotBrowser::GetSigningBox { signing_box } => Ok(signing_box), _ => Err(crate::client::Error::unexpected_callback_response( "GetSigningBox", response).to_string()), } } - + async fn invoke_debot(&self, debot: String, action: DAction) -> Result<(), String> { let response = self.app_object.call(ParamsOfAppDebotBrowser::InvokeDebot { debot_addr: debot, @@ -148,10 +158,9 @@ impl DebotBrowserAdapter { }) .await .map_err(|e| { - error!("debot browser failed to invoke debot: {}", e); format!("debot browser failed to invoke debot: {}", e) })?; - + match response { ResultOfAppDebotBrowser::InvokeDebot => Ok(()), _ => { @@ -164,42 +173,30 @@ impl DebotBrowserAdapter { async fn send(&self, message: String) { self.app_object.notify(ParamsOfAppDebotBrowser::Send { message }); } - } - -/// [UNSTABLE](UNSTABLE.md) Starts an instance of debot. -/// -/// Downloads debot smart contract from blockchain and switches it to -/// context zero. -/// Returns a debot handle which can be used later in `execute` function. -/// This function must be used by Debot Browser to start a dialog with debot. -/// While the function is executing, several Browser Callbacks can be called, -/// since the debot tries to display all actions from the context 0 to the user. -/// -/// # Remarks -/// `start` is equivalent to `fetch` + switch to context 0. -#[api_function] -pub(crate) async fn start( - context: std::sync::Arc, - params: ParamsOfStart, - app_object: AppObject, -) -> ClientResult { - let browser_callbacks = DebotBrowserAdapter::new(app_object); - crate::debot::start(context, params, browser_callbacks).await + + async fn approve(&self, activity: DebotActivity) -> ClientResult { + let response = self.app_object.call(ParamsOfAppDebotBrowser::Approve { activity }).await?; + + match response { + ResultOfAppDebotBrowser::Approve{approved} => Ok(approved), + _ => Err(Error::browser_callback_failed("unexpected response")), + } + } } -/// [UNSTABLE](UNSTABLE.md) Fetches debot from blockchain. -/// -/// Downloads debot smart contract (code and data) from blockchain and creates +/// [UNSTABLE](UNSTABLE.md) Creates and instance of DeBot. +/// +/// Downloads debot smart contract (code and data) from blockchain and creates /// an instance of Debot Engine for it. -/// +/// /// # Remarks /// It does not switch debot to context 0. Browser Callbacks are not called. #[api_function] -pub(crate) async fn fetch( +pub(crate) async fn init( context: std::sync::Arc, - params: ParamsOfFetch, + params: ParamsOfInit, app_object: AppObject, ) -> ClientResult { let browser_callbacks = DebotBrowserAdapter::new(app_object); - crate::debot::fetch(context, params, browser_callbacks).await + crate::debot::init(context, params, browser_callbacks).await } \ No newline at end of file diff --git a/ton_client/src/json_interface/mod.rs b/ton_client/src/json_interface/mod.rs index 0ff93f81f..fe3b6b507 100644 --- a/ton_client/src/json_interface/mod.rs +++ b/ton_client/src/json_interface/mod.rs @@ -18,6 +18,7 @@ pub(crate) mod handlers; pub(crate) mod interop; pub(crate) mod net; pub(crate) mod processing; +pub(crate) mod utils; pub(crate) mod modules; mod registrar; diff --git a/ton_client/src/json_interface/modules.rs b/ton_client/src/json_interface/modules.rs index 38f14b88e..43e7bdd7e 100644 --- a/ton_client/src/json_interface/modules.rs +++ b/ton_client/src/json_interface/modules.rs @@ -454,6 +454,14 @@ fn register_utils(handlers: &mut RuntimeHandlers) { crate::utils::calc_storage_fee, crate::utils::calc_storage_fee::calc_storage_fee_api, ); + module.register_sync_fn( + super::utils::compress_zstd, + super::utils::compress_zstd_api + ); + module.register_sync_fn( + super::utils::decompress_zstd, + super::utils::decompress_zstd_api + ); module.register(); } @@ -469,14 +477,15 @@ fn register_debot(handlers: &mut RuntimeHandlers) { module.register_type::(); module.register_type::(); module.register_type::(); + module.register_type::(); + module.register_type::(); + module.register_type::(); module.register_async_fn_with_app_object( - crate::json_interface::debot::start, - crate::json_interface::debot::start_api, - ); - module.register_async_fn_with_app_object( - crate::json_interface::debot::fetch, - crate::json_interface::debot::fetch_api, + crate::json_interface::debot::init, + crate::json_interface::debot::init_api, ); + module.register_async_fn(crate::debot::start, crate::debot::start_api); + module.register_async_fn(crate::debot::fetch, crate::debot::fetch_api); module.register_async_fn(crate::debot::execute, crate::debot::execute_api); module.register_async_fn(crate::debot::send, crate::debot::send_api); module.register_sync_fn(crate::debot::remove, crate::debot::remove_api); diff --git a/ton_client/src/json_interface/utils.rs b/ton_client/src/json_interface/utils.rs new file mode 100644 index 000000000..5cab9f78c --- /dev/null +++ b/ton_client/src/json_interface/utils.rs @@ -0,0 +1,83 @@ +/* +* Copyright 2018-2020 TON DEV SOLUTIONS LTD. +* +* Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use +* this file except in compliance with the License. +* +* 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. +*/ + +use crate::ClientContext; +use crate::error::ClientResult; + +#[derive(Serialize, Deserialize, ApiType, Default, Debug)] +pub struct ParamsOfCompressZstd { + /// Uncompressed data. Must be encoded as base64. + pub uncompressed: String, + /// Compression level, from 1 to 21. + /// Where: + /// 1 - lowest compression level (fastest compression); + /// 21 - highest compression level (slowest compression). + /// If level is omitted, the default compression level is used (currently `3`). + pub level: Option, +} + +#[derive(Serialize, Deserialize, ApiType, Default, Debug)] +pub struct ResultOfCompressZstd { + /// Compressed data. Must be encoded as base64. + pub compressed: String, +} + +/// Compresses data using Zstandard algorithm +#[api_function] +pub fn compress_zstd( + _context: std::sync::Arc, + params: ParamsOfCompressZstd, +) -> ClientResult { + let uncompressed = base64::decode(¶ms.uncompressed) + .map_err( + |err| + crate::utils::Error::compression_error(format!("Unable to decode BASE64: {}", err)) + )?; + + let compressed = crate::utils::compression::compress_zstd(uncompressed.as_slice(), params.level)?; + + Ok(ResultOfCompressZstd { + compressed: base64::encode(&compressed), + }) +} + +#[derive(Serialize, Deserialize, ApiType, Default, Debug)] +pub struct ParamsOfDecompressZstd { + /// Compressed data. Must be encoded as base64. + pub compressed: String, +} + +#[derive(Serialize, Deserialize, ApiType, Default, Debug)] +pub struct ResultOfDecompressZstd { + /// Decompressed data. Must be encoded as base64. + pub decompressed: String, +} + +/// Decompresses data using Zstandard algorithm +#[api_function] +pub fn decompress_zstd( + _context: std::sync::Arc, + params: ParamsOfDecompressZstd, +) -> ClientResult { + let compressed = base64::decode(¶ms.compressed) + .map_err( + |err| + crate::utils::Error::decompression_error(format!("Unable to decode BASE64: {}", err)) + )?; + + let decompressed = crate::utils::compression::decompress_zstd(compressed.as_slice())?; + + Ok(ResultOfDecompressZstd { + decompressed: base64::encode(&decompressed), + }) +} diff --git a/ton_client/src/tests/contracts/abi_v2/helloDebot.abi.json b/ton_client/src/tests/contracts/abi_v2/helloDebot.abi.json new file mode 100644 index 000000000..915c6cb1b --- /dev/null +++ b/ton_client/src/tests/contracts/abi_v2/helloDebot.abi.json @@ -0,0 +1,94 @@ +{ + "ABI version": 2, + "header": ["pubkey", "time", "expire"], + "functions": [ + { + "name": "setIcon", + "inputs": [ + {"name":"icon","type":"bytes"} + ], + "outputs": [ + ] + }, + { + "name": "start", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "getDebotInfo", + "id": "0xDEB", + "inputs": [ + ], + "outputs": [ + {"name":"name","type":"bytes"}, + {"name":"version","type":"bytes"}, + {"name":"publisher","type":"bytes"}, + {"name":"key","type":"bytes"}, + {"name":"author","type":"bytes"}, + {"name":"support","type":"address"}, + {"name":"hello","type":"bytes"}, + {"name":"language","type":"bytes"}, + {"name":"dabi","type":"bytes"}, + {"name":"icon","type":"bytes"} + ] + }, + { + "name": "getRequiredInterfaces", + "inputs": [ + ], + "outputs": [ + {"name":"interfaces","type":"uint256[]"} + ] + }, + { + "name": "setUserInput", + "inputs": [ + {"name":"value","type":"bytes"} + ], + "outputs": [ + ] + }, + { + "name": "getVersion", + "inputs": [ + ], + "outputs": [ + {"name":"name","type":"bytes"}, + {"name":"semver","type":"uint24"} + ] + }, + { + "name": "getDebotOptions", + "inputs": [ + ], + "outputs": [ + {"name":"options","type":"uint8"}, + {"name":"debotAbi","type":"bytes"}, + {"name":"targetAbi","type":"bytes"}, + {"name":"targetAddr","type":"address"} + ] + }, + { + "name": "setABI", + "inputs": [ + {"name":"dabi","type":"bytes"} + ], + "outputs": [ + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + ], + "events": [ + ] +} diff --git a/ton_client/src/tests/contracts/abi_v2/helloDebot.sol b/ton_client/src/tests/contracts/abi_v2/helloDebot.sol new file mode 100644 index 000000000..25fe759ba --- /dev/null +++ b/ton_client/src/tests/contracts/abi_v2/helloDebot.sol @@ -0,0 +1,53 @@ +pragma ton-solidity >=0.35.0; +pragma AbiHeader expire; +pragma AbiHeader time; +pragma AbiHeader pubkey; +// import required DeBot interfaces and basic DeBot contract. +import "../Debot.sol"; +import "../Terminal.sol"; + +contract HelloDebot is Debot { + + /// @notice Entry point function for DeBot. + function start() public override { + // print string to user. + Terminal.print(0, "Hello, World!"); + // input string from user and define callback that receives entered string. + Terminal.input(tvm.functionId(setUserInput), "How is it going?", false); + } + + /// @notice Returns Metadata about DeBot. + function getDebotInfo() public override view returns( + string name, string version, string publisher, string key, string author, + address support, string hello, string language, string dabi + ) { + name = "HelloWorld"; + version = "0.2.0"; + publisher = "TON Labs"; + key = "Start develop DeBot from here"; + author = "TON Labs"; + support = address.makeAddrStd(0, 0x841288ed3b55d9cdafa806807f02a0ae0c169aa5edfe88a789a6482429756a94); + hello = "Hello, i'am a helloworld DeBot."; + language = "en"; + dabi = m_debotAbi.get(); + } + + function getRequiredInterfaces() public view override returns (uint256[] interfaces) { + return [ Terminal.ID ]; + } + + function setUserInput(string value) public { + // TODO: continue DeBot logic here... + Terminal.print(0, format("You entered \"{}\"", value)); + } + + // @notice Define DeBot version and title here. + function getVersion() public override returns (string name, uint24 semver) { + (name, semver) = ("HelloWorld DeBot", _version(0,1,0)); + } + + function _version(uint24 major, uint24 minor, uint24 fix) private pure inline returns (uint24) { + return (major << 16) | (minor << 8) | (fix); + } + +} \ No newline at end of file diff --git a/ton_client/src/tests/contracts/abi_v2/helloDebot.tvc b/ton_client/src/tests/contracts/abi_v2/helloDebot.tvc new file mode 100644 index 000000000..0fbeef5e9 Binary files /dev/null and b/ton_client/src/tests/contracts/abi_v2/helloDebot.tvc differ diff --git a/ton_client/src/tests/contracts/abi_v2/testDebot6.abi.json b/ton_client/src/tests/contracts/abi_v2/testDebot6.abi.json new file mode 100644 index 000000000..c618fc4da --- /dev/null +++ b/ton_client/src/tests/contracts/abi_v2/testDebot6.abi.json @@ -0,0 +1,123 @@ +{ + "ABI version": 2, + "header": ["pubkey", "time", "expire"], + "functions": [ + { + "name": "start", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "onSuccess1", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "onSuccess2", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "onSuccess3", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "onError1", + "inputs": [ + {"name":"sdkError","type":"uint32"}, + {"name":"exitCode","type":"uint32"} + ], + "outputs": [ + ] + }, + { + "name": "onError2", + "inputs": [ + {"name":"sdkError","type":"uint32"}, + {"name":"exitCode","type":"uint32"} + ], + "outputs": [ + ] + }, + { + "name": "onError3", + "inputs": [ + {"name":"sdkError","type":"uint32"}, + {"name":"exitCode","type":"uint32"} + ], + "outputs": [ + ] + }, + { + "name": "send3", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "send2", + "inputs": [ + ], + "outputs": [ + ] + }, + { + "name": "send1", + "inputs": [ + {"name":"value1","type":"uint64"}, + {"name":"value2","type":"uint64"} + ], + "outputs": [ + ] + }, + { + "name": "getVersion", + "inputs": [ + ], + "outputs": [ + {"name":"name","type":"bytes"}, + {"name":"semver","type":"uint24"} + ] + }, + { + "name": "getDebotOptions", + "inputs": [ + ], + "outputs": [ + {"name":"options","type":"uint8"}, + {"name":"debotAbi","type":"bytes"}, + {"name":"targetAbi","type":"bytes"}, + {"name":"targetAddr","type":"address"} + ] + }, + { + "name": "setABI", + "inputs": [ + {"name":"dabi","type":"bytes"} + ], + "outputs": [ + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + ], + "events": [ + ] +} diff --git a/ton_client/src/tests/contracts/abi_v2/testDebot6.sol b/ton_client/src/tests/contracts/abi_v2/testDebot6.sol new file mode 100644 index 000000000..00847c80c --- /dev/null +++ b/ton_client/src/tests/contracts/abi_v2/testDebot6.sol @@ -0,0 +1,104 @@ +pragma ton-solidity >=0.35.0; +pragma AbiHeader expire; +pragma AbiHeader time; +pragma AbiHeader pubkey; +// import required DeBot interfaces and basic DeBot contract. +import "../Debot.sol"; +import "../Terminal.sol"; + +contract testDebot is Debot { + + /// @notice Entry point function for DeBot. + function start() public override { + optional(uint256) pubkey = tvm.pubkey(); + this.send1{ + abiVer: 2, + extMsg: true, + sign: true, + time: 0, + expire: 0, + pubkey: pubkey, + callbackId: tvm.functionId(onSuccess1), + onErrorId: tvm.functionId(onError1) + }(2.2 ton, 3.5 ton); + + this.send2{ + abiVer: 2, + extMsg: true, + sign: true, + time: 0, + expire: 0, + pubkey: pubkey, + callbackId: tvm.functionId(onSuccess2), + onErrorId: tvm.functionId(onError2) + }(); + + this.send3{ + abiVer: 2, + extMsg: true, + sign: true, + time: 0, + expire: 0, + pubkey: pubkey, + callbackId: tvm.functionId(onSuccess3), + onErrorId: tvm.functionId(onError3) + }(); + } + + function onSuccess1() public { + Terminal.print(0, "Send1 succeeded"); + } + + function onSuccess2() public pure { + require(false, 201); + } + + function onSuccess3() public pure { + require(false, 304); + } + + function onError1(uint32 sdkError, uint32 exitCode) public pure { + sdkError = sdkError; + exitCode = exitCode; + require(false, 200); + } + + function onError2(uint32 sdkError, uint32 exitCode) public { + require(sdkError == 812, 300); + require(exitCode == 0, 301); + Terminal.print(0, "Send2 rejected"); + } + + function onError3(uint32 sdkError, uint32 exitCode) public pure { + require(sdkError == 414, 102); + require(exitCode == 303, 103); + } + + function send3() public view { + require(false, 303); + } + + function send2() public view { + require(msg.pubkey() == tvm.pubkey(), 101); + tvm.accept(); + address(this).transfer(10 ton, true, 1); + } + + function send1(uint64 value1, uint64 value2) public view { + require(msg.pubkey() == tvm.pubkey(), 101); + tvm.accept(); + address(this).transfer(value1, true, 1); + address addr = address.makeAddrStd(0, 0); + addr.transfer(value2, false, 1); + } + + // @notice Define DeBot version and title here. + function getVersion() public override returns (string name, uint24 semver) { + (name, semver) = ("Test DeBot 6 for testing approve callback", _version(0,1,0)); + } + + function _version(uint24 major, uint24 minor, uint24 fix) private pure inline returns (uint24) { + return (major << 16) | (minor << 8) | (fix); + } + +} \ No newline at end of file diff --git a/ton_client/src/tests/contracts/abi_v2/testDebot6.tvc b/ton_client/src/tests/contracts/abi_v2/testDebot6.tvc new file mode 100644 index 000000000..26889c105 Binary files /dev/null and b/ton_client/src/tests/contracts/abi_v2/testDebot6.tvc differ diff --git a/ton_client/src/tests/mod.rs b/ton_client/src/tests/mod.rs index 8c699829e..158a93b76 100644 --- a/ton_client/src/tests/mod.rs +++ b/ton_client/src/tests/mod.rs @@ -317,6 +317,12 @@ impl TestClient { ) } + pub fn icon(name: &str, abi_version: Option) -> String { + hex::encode( + std::fs::read(format!("{}{}.tvc", Self::contracts_path(abi_version), name)).unwrap() + ) + } + pub fn package(name: &str, abi_version: Option) -> (Abi, String) { (Self::abi(name, abi_version), Self::tvc(name, abi_version)) } @@ -699,7 +705,7 @@ impl TestClient { "client.resolve_app_request", ParamsOfResolveAppRequest { app_request_id, - result: AppRequestResult::Ok { + result: AppRequestResult::Ok { result: json!(result) } }, diff --git a/ton_client/src/utils/compression.rs b/ton_client/src/utils/compression.rs new file mode 100644 index 000000000..8a3203f1f --- /dev/null +++ b/ton_client/src/utils/compression.rs @@ -0,0 +1,49 @@ +/* +* Copyright 2018-2020 TON DEV SOLUTIONS LTD. +* +* Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use +* this file except in compliance with the License. +* +* 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. +*/ + +use std::io::Cursor; + +use crate::error::ClientResult; + +/// Compresses data using Zstandard algorithm +pub fn compress_zstd(uncompressed: &[u8], level: Option) -> ClientResult> { + let level = match level { + None => 0, + Some(level) => { + if !(1..=21).contains(&level) { + return Err(super::errors::Error::compression_error( + format!("Invalid compression level: {}", level) + )); + } + level + } + }; + + let mut compressed = Vec::new(); + zstd::stream::copy_encode( + &mut Cursor::new(uncompressed), + &mut compressed, + level + ).map_err(|err| super::errors::Error::compression_error(err))?; + + Ok(compressed) +} + +/// Decompresses data using Zstandard algorithm +pub fn decompress_zstd(compressed: &[u8]) -> ClientResult> { + let mut decompressed = Vec::new(); + zstd::stream::copy_decode(&mut Cursor::new(compressed), &mut decompressed) + .map_err(|err| super::errors::Error::decompression_error(err))?; + + Ok(decompressed) +} diff --git a/ton_client/src/utils/errors.rs b/ton_client/src/utils/errors.rs index 724984c3c..55d56906d 100644 --- a/ton_client/src/utils/errors.rs +++ b/ton_client/src/utils/errors.rs @@ -1,5 +1,37 @@ -pub enum ErrorCode {} +/* +* Copyright 2018-2020 TON DEV SOLUTIONS LTD. +* +* Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use +* this file except in compliance with the License. +* +* 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. +*/ + +use std::fmt::Display; + +use crate::error::ClientError; + +#[derive(ApiType)] +pub enum ErrorCode { + CompressionError = 701, +} pub struct Error; -impl Error {} +fn error(code: ErrorCode, message: String) -> ClientError { + ClientError::with_code_message(code as u32, message) +} + +impl Error { + pub fn compression_error(err: E) -> ClientError { + error(ErrorCode::CompressionError, format!("Compression error: {}", err)) + } + + pub fn decompression_error(err: E) -> ClientError { + error(ErrorCode::CompressionError, format!("Decompression error: {}", err)) + } +} diff --git a/ton_client/src/utils/mod.rs b/ton_client/src/utils/mod.rs index 0ce73f207..a73a794d6 100644 --- a/ton_client/src/utils/mod.rs +++ b/ton_client/src/utils/mod.rs @@ -17,6 +17,7 @@ mod tests; pub(crate) mod calc_storage_fee; pub(crate) mod conversion; +pub(crate) mod compression; mod errors; pub use calc_storage_fee::{ @@ -25,4 +26,5 @@ pub use calc_storage_fee::{ pub use conversion::{ convert_address, AddressStringFormat, ParamsOfConvertAddress, ResultOfConvertAddress, }; +pub use compression::{compress_zstd, decompress_zstd}; pub use errors::{Error, ErrorCode}; diff --git a/ton_client/src/utils/tests.rs b/ton_client/src/utils/tests.rs index 23abc8edb..1df166119 100644 --- a/ton_client/src/utils/tests.rs +++ b/ton_client/src/utils/tests.rs @@ -1,6 +1,9 @@ use crate::{boc::tests::ACCOUNT, json_interface::modules::UtilsModule}; use crate::tests::TestClient; use super::*; +use crate::json_interface::utils::{ + ParamsOfCompressZstd, ResultOfCompressZstd, ResultOfDecompressZstd, ParamsOfDecompressZstd +}; use api_info::ApiModule; #[tokio::test(core_threads = 2)] @@ -82,3 +85,43 @@ async fn test_calc_storage_fee() { assert_eq!(result.fee, "330"); } + +#[test] +fn test_compression() { + let client = TestClient::new(); + let uncompressed = + b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \ + incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \ + exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure \ + dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \ + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit \ + anim id est laborum."; + + let compressed: ResultOfCompressZstd = client.request( + "utils.compress_zstd", + ParamsOfCompressZstd { + uncompressed: base64::encode(uncompressed), + level: Some(21), + } + ).unwrap(); + + assert_eq!( + compressed.compressed, + "KLUv/QCAdQgAJhc5GJCnsA2AIm2tVzjno88mHb3Ttx9b8fXHHDAAMgAyAMUsVo6Pi3rPTDF2WDl510aHTwt44hrUxb\ + n5oF6iUfiUiRbQhYo/PSM2WvKYt/hMIOQmuOaY/bmJQoRky46EF+cEd+Thsep5Hloo9DLCSwe1vFwcqIHycEKlMqBSo\ + +szAiIBhkukH5kSIVlFukEWNF2SkIv6HBdPjFAjoUliCPjzKB/4jK91X95rTAKoASkPNqwUEw2Gkscdb3lR8YRYOR+P\ + 0sULCqzPQ8mQFJWnBSyP25mWIY2bFEUSJiGsWD+9NBqLhIAGDggQkLMbt5Y1aDR4uLKqwJXmQFPg/XTXIL7LCgspIF1\ + YYplND4Uo" + ); + + let decompressed: ResultOfDecompressZstd = client.request( + "utils.decompress_zstd", + ParamsOfDecompressZstd { + compressed: compressed.compressed + } + ).unwrap(); + + let decompressed = base64::decode(&decompressed.decompressed).unwrap(); + + assert_eq!(decompressed, uncompressed); +} diff --git a/ton_sdk/Cargo.toml b/ton_sdk/Cargo.toml index 6e19a480d..78fea024b 100644 --- a/ton_sdk/Cargo.toml +++ b/ton_sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ton_sdk" -version = "1.11.2" +version = "1.12.0" edition = "2018" license = "Apache-2.0" authors = ["TON DEV SOLUTIONS LTD "] diff --git a/toncli/Cargo.toml b/toncli/Cargo.toml index 4802fca82..f2d47c250 100644 --- a/toncli/Cargo.toml +++ b/toncli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "toncli" -version = "1.11.2" +version = "1.12.0" description = "TON CLient Command Line Tool" authors = ["TON DEV SOLUTIONS LTD "] repository = "https://github.com/tonlabs/TON-SDK" diff --git a/tools/api.json b/tools/api.json index 172d431b8..0f4c1ceea 100644 --- a/tools/api.json +++ b/tools/api.json @@ -1,5 +1,5 @@ { - "version": "1.11.1", + "version": "1.12.0", "modules": [ { "name": "client", @@ -6914,6 +6914,73 @@ ], "summary": null, "description": null + }, + { + "name": "ParamsOfCompressZstd", + "type": "Struct", + "struct_fields": [ + { + "name": "uncompressed", + "type": "String", + "summary": "Uncompressed data.", + "description": "Must be encoded as base64." + }, + { + "name": "level", + "type": "Optional", + "optional_inner": { + "type": "Number", + "number_type": "Int", + "number_size": 32 + }, + "summary": "Compression level, from 1 to 21. Where: 1 - lowest compression level (fastest compression); 21 - highest compression level (slowest compression). If level is omitted, the default compression level is used (currently `3`).", + "description": null + } + ], + "summary": null, + "description": null + }, + { + "name": "ResultOfCompressZstd", + "type": "Struct", + "struct_fields": [ + { + "name": "compressed", + "type": "String", + "summary": "Compressed data.", + "description": "Must be encoded as base64." + } + ], + "summary": null, + "description": null + }, + { + "name": "ParamsOfDecompressZstd", + "type": "Struct", + "struct_fields": [ + { + "name": "compressed", + "type": "String", + "summary": "Compressed data.", + "description": "Must be encoded as base64." + } + ], + "summary": null, + "description": null + }, + { + "name": "ResultOfDecompressZstd", + "type": "Struct", + "struct_fields": [ + { + "name": "decompressed", + "type": "String", + "summary": "Decompressed data.", + "description": "Must be encoded as base64." + } + ], + "summary": null, + "description": null } ], "functions": [ @@ -6992,6 +7059,82 @@ ] }, "errors": null + }, + { + "name": "compress_zstd", + "summary": "Compresses data using Zstandard algorithm", + "description": null, + "params": [ + { + "name": "_context", + "type": "Generic", + "generic_name": "Arc", + "generic_args": [ + { + "type": "Ref", + "ref_name": "ClientContext" + } + ], + "summary": null, + "description": null + }, + { + "name": "params", + "type": "Ref", + "ref_name": "utils.ParamsOfCompressZstd", + "summary": null, + "description": null + } + ], + "result": { + "type": "Generic", + "generic_name": "ClientResult", + "generic_args": [ + { + "type": "Ref", + "ref_name": "utils.ResultOfCompressZstd" + } + ] + }, + "errors": null + }, + { + "name": "decompress_zstd", + "summary": "Decompresses data using Zstandard algorithm", + "description": null, + "params": [ + { + "name": "_context", + "type": "Generic", + "generic_name": "Arc", + "generic_args": [ + { + "type": "Ref", + "ref_name": "ClientContext" + } + ], + "summary": null, + "description": null + }, + { + "name": "params", + "type": "Ref", + "ref_name": "utils.ParamsOfDecompressZstd", + "summary": null, + "description": null + } + ], + "result": { + "type": "Generic", + "generic_name": "ClientResult", + "generic_args": [ + { + "type": "Ref", + "ref_name": "utils.ResultOfDecompressZstd" + } + ] + }, + "errors": null } ] }, @@ -8759,6 +8902,20 @@ "value": "810", "summary": null, "description": null + }, + { + "name": "DebotBrowserCallbackFailed", + "type": "Number", + "value": "811", + "summary": null, + "description": null + }, + { + "name": "DebotOperationRejected", + "type": "Number", + "value": "812", + "summary": null, + "description": null } ], "summary": null, @@ -8821,7 +8978,194 @@ "description": null }, { - "name": "ParamsOfStart", + "name": "DebotInfo", + "type": "Struct", + "struct_fields": [ + { + "name": "name", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "DeBot short name.", + "description": null + }, + { + "name": "version", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "DeBot semantic version.", + "description": null + }, + { + "name": "publisher", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "The name of DeBot deployer.", + "description": null + }, + { + "name": "key", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "Short info about DeBot.", + "description": null + }, + { + "name": "author", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "The name of DeBot developer.", + "description": null + }, + { + "name": "support", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "TON address of author for questions and donations.", + "description": null + }, + { + "name": "hello", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "String with the first messsage from DeBot.", + "description": null + }, + { + "name": "language", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "String with DeBot interface language (ISO-639).", + "description": null + }, + { + "name": "dabi", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "String with DeBot ABI.", + "description": null + }, + { + "name": "icon", + "type": "Optional", + "optional_inner": { + "type": "String" + }, + "summary": "DeBot icon.", + "description": null + }, + { + "name": "interfaces", + "type": "Array", + "array_item": { + "type": "String" + }, + "summary": "Vector with IDs of DInterfaces used by DeBot.", + "description": null + } + ], + "summary": "[UNSTABLE](UNSTABLE.md) Describes DeBot metadata.", + "description": null + }, + { + "name": "DebotActivity", + "type": "EnumOfTypes", + "enum_types": [ + { + "name": "Transaction", + "type": "Struct", + "struct_fields": [ + { + "name": "msg", + "type": "String", + "summary": "External inbound message BOC.", + "description": null + }, + { + "name": "dst", + "type": "String", + "summary": "Target smart contract address.", + "description": null + }, + { + "name": "out", + "type": "Array", + "array_item": { + "type": "Ref", + "ref_name": "debot.Spending" + }, + "summary": "List of spendings as a result of transaction.", + "description": null + }, + { + "name": "fee", + "type": "BigInt", + "number_type": "UInt", + "number_size": 64, + "summary": "Transaction total fee.", + "description": null + }, + { + "name": "setcode", + "type": "Boolean", + "summary": "Indicates if target smart contract updates its code.", + "description": null + }, + { + "name": "signkey", + "type": "String", + "summary": "Public key from keypair that was used to sign external message.", + "description": null + } + ], + "summary": "DeBot wants to create new transaction in blockchain.", + "description": null + } + ], + "summary": "[UNSTABLE](UNSTABLE.md) Describes the operation that the DeBot wants to perform.", + "description": null + }, + { + "name": "Spending", + "type": "Struct", + "struct_fields": [ + { + "name": "amount", + "type": "BigInt", + "number_type": "UInt", + "number_size": 64, + "summary": "Amount of nanotokens that will be sent to `dst` address.", + "description": null + }, + { + "name": "dst", + "type": "String", + "summary": "Destination address of recipient of funds.", + "description": null + } + ], + "summary": "[UNSTABLE](UNSTABLE.md) Describes how much funds will be debited from the target contract balance as a result of the transaction.", + "description": null + }, + { + "name": "ParamsOfInit", "type": "Struct", "struct_fields": [ { @@ -8831,7 +9175,7 @@ "description": null } ], - "summary": "[UNSTABLE](UNSTABLE.md) Parameters to start debot.", + "summary": "[UNSTABLE](UNSTABLE.md) Parameters to init DeBot.", "description": null }, { @@ -8850,9 +9194,16 @@ "type": "String", "summary": "Debot abi as json string.", "description": null + }, + { + "name": "info", + "type": "Ref", + "ref_name": "debot.DebotInfo", + "summary": "Debot metadata.", + "description": null } ], - "summary": "[UNSTABLE](UNSTABLE.md) Structure for storing debot handle returned from `start` and `fetch` functions.", + "summary": "[UNSTABLE](UNSTABLE.md) Structure for storing debot handle returned from `init` function.", "description": null }, { @@ -8966,6 +9317,21 @@ ], "summary": "Used by Debot to call DInterface implemented by Debot Browser.", "description": null + }, + { + "name": "Approve", + "type": "Struct", + "struct_fields": [ + { + "name": "activity", + "type": "Ref", + "ref_name": "debot.DebotActivity", + "summary": "DeBot activity details.", + "description": null + } + ], + "summary": "Requests permission from DeBot Browser to execute DeBot operation.", + "description": null } ], "summary": "[UNSTABLE](UNSTABLE.md) Debot Browser callbacks", @@ -9010,11 +9376,40 @@ "struct_fields": [], "summary": "Result of debot invoking.", "description": null + }, + { + "name": "Approve", + "type": "Struct", + "struct_fields": [ + { + "name": "approved", + "type": "Boolean", + "summary": "Indicates whether the DeBot is allowed to perform the specified operation.", + "description": null + } + ], + "summary": "Result of `approve` callback.", + "description": null } ], "summary": "[UNSTABLE](UNSTABLE.md) Returning values from Debot Browser callbacks.", "description": null }, + { + "name": "ParamsOfStart", + "type": "Struct", + "struct_fields": [ + { + "name": "debot_handle", + "type": "Ref", + "ref_name": "debot.DebotHandle", + "summary": "Debot handle which references an instance of debot engine.", + "description": null + } + ], + "summary": "[UNSTABLE](UNSTABLE.md) Parameters to start DeBot. DeBot must be already initialized with init() function.", + "description": null + }, { "name": "ParamsOfFetch", "type": "Struct", @@ -9022,11 +9417,26 @@ { "name": "address", "type": "String", - "summary": "Debot smart contract address", + "summary": "Debot smart contract address.", "description": null } ], - "summary": "[UNSTABLE](UNSTABLE.md) Parameters to fetch debot.", + "summary": "[UNSTABLE](UNSTABLE.md) Parameters to fetch DeBot metadata.", + "description": null + }, + { + "name": "ResultOfFetch", + "type": "Struct", + "struct_fields": [ + { + "name": "info", + "type": "Ref", + "ref_name": "debot.DebotInfo", + "summary": "Debot metadata.", + "description": null + } + ], + "summary": "[UNSTABLE](UNSTABLE.md)", "description": null }, { @@ -9071,13 +9481,28 @@ ], "summary": "[UNSTABLE](UNSTABLE.md) Parameters of `send` function.", "description": null + }, + { + "name": "ParamsOfRemove", + "type": "Struct", + "struct_fields": [ + { + "name": "debot_handle", + "type": "Ref", + "ref_name": "debot.DebotHandle", + "summary": "Debot handle which references an instance of debot engine.", + "description": null + } + ], + "summary": "[UNSTABLE](UNSTABLE.md)", + "description": null } ], "functions": [ { - "name": "start", - "summary": "[UNSTABLE](UNSTABLE.md) Starts an instance of debot.", - "description": "Downloads debot smart contract from blockchain and switches it to\ncontext zero.\nReturns a debot handle which can be used later in `execute` function.\nThis function must be used by Debot Browser to start a dialog with debot.\nWhile the function is executing, several Browser Callbacks can be called,\nsince the debot tries to display all actions from the context 0 to the user.\n\n# Remarks\n`start` is equivalent to `fetch` + switch to context 0.", + "name": "init", + "summary": "[UNSTABLE](UNSTABLE.md) Creates and instance of DeBot.", + "description": "Downloads debot smart contract (code and data) from blockchain and creates\nan instance of Debot Engine for it.\n\n# Remarks\nIt does not switch debot to context 0. Browser Callbacks are not called.", "params": [ { "name": "context", @@ -9095,7 +9520,7 @@ { "name": "params", "type": "Ref", - "ref_name": "debot.ParamsOfStart", + "ref_name": "debot.ParamsOfInit", "summary": null, "description": null }, @@ -9130,9 +9555,9 @@ "errors": null }, { - "name": "fetch", - "summary": "[UNSTABLE](UNSTABLE.md) Fetches debot from blockchain.", - "description": "Downloads debot smart contract (code and data) from blockchain and creates\nan instance of Debot Engine for it.\n\n# Remarks\nIt does not switch debot to context 0. Browser Callbacks are not called.", + "name": "start", + "summary": "[UNSTABLE](UNSTABLE.md) Starts the DeBot.", + "description": "Downloads debot smart contract from blockchain and switches it to\ncontext zero.\n\nThis function must be used by Debot Browser to start a dialog with debot.\nWhile the function is executing, several Browser Callbacks can be called,\nsince the debot tries to display all actions from the context 0 to the user.\n\nWhen the debot starts SDK registers `BrowserCallbacks` AppObject.\nTherefore when `debote.remove` is called the debot is being deleted and the callback is called\nwith `finish`=`true` which indicates that it will never be used again.", "params": [ { "name": "context", @@ -9150,26 +9575,46 @@ { "name": "params", "type": "Ref", - "ref_name": "debot.ParamsOfFetch", + "ref_name": "debot.ParamsOfStart", "summary": null, "description": null - }, + } + ], + "result": { + "type": "Generic", + "generic_name": "ClientResult", + "generic_args": [ + { + "type": "None" + } + ] + }, + "errors": null + }, + { + "name": "fetch", + "summary": "[UNSTABLE](UNSTABLE.md) Fetches DeBot metadata from blockchain.", + "description": "Downloads DeBot from blockchain and creates and fetches its metadata.", + "params": [ { - "name": "app_object", + "name": "context", "type": "Generic", - "generic_name": "AppObject", + "generic_name": "Arc", "generic_args": [ { "type": "Ref", - "ref_name": "debot.ParamsOfAppDebotBrowser" - }, - { - "type": "Ref", - "ref_name": "debot.ResultOfAppDebotBrowser" + "ref_name": "ClientContext" } ], "summary": null, "description": null + }, + { + "name": "params", + "type": "Ref", + "ref_name": "debot.ParamsOfFetch", + "summary": null, + "description": null } ], "result": { @@ -9178,7 +9623,7 @@ "generic_args": [ { "type": "Ref", - "ref_name": "debot.RegisteredDebot" + "ref_name": "debot.ResultOfFetch" } ] }, @@ -9279,7 +9724,7 @@ { "name": "params", "type": "Ref", - "ref_name": "debot.RegisteredDebot", + "ref_name": "debot.ParamsOfRemove", "summary": null, "description": null }