From 6862ae4559e512e544d18b55fecc9d4a11f34ec4 Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Mon, 10 Mar 2025 18:14:50 +0100 Subject: [PATCH 1/9] update ton-abi --- Cargo.toml | 2 +- nekoton-abi/Cargo.toml | 2 +- nekoton-abi/src/lib.rs | 11 ++++------- nekoton-contracts/Cargo.toml | 2 +- nekoton-derive/Cargo.toml | 2 +- nekoton-jetton/Cargo.toml | 2 +- src/core/jetton_wallet/mod.rs | 2 ++ 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d4870ad7..87930ecf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ ed25519-dalek = { git = "https://github.com/broxus/ed25519-dalek.git", optional tiny-bip39 = { git = "https://github.com/broxus/tiny-bip39.git", default-features = false, optional = true } tiny-hderive = { git = "https://github.com/broxus/tiny-hderive.git", optional = true } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_executor = { git = "https://github.com/broxus/ton-labs-executor.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-abi/Cargo.toml b/nekoton-abi/Cargo.toml index 70208fab6..0f29ad9af 100644 --- a/nekoton-abi/Cargo.toml +++ b/nekoton-abi/Cargo.toml @@ -25,7 +25,7 @@ thiserror = "1.0" ed25519-dalek = { git = "https://github.com/broxus/ed25519-dalek.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_executor = { git = "https://github.com/broxus/ton-labs-executor.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-abi/src/lib.rs b/nekoton-abi/src/lib.rs index 13ffa2607..24e05cf42 100644 --- a/nekoton-abi/src/lib.rs +++ b/nekoton-abi/src/lib.rs @@ -58,8 +58,8 @@ use std::sync::Arc; use anyhow::Result; use num_traits::ToPrimitive; use smallvec::smallvec; -use ton_abi::token::Cursor; use ton_abi::{Function, Param, Token, TokenValue}; +use ton_abi::token::Cursor; use ton_block::{ Account, AccountStuff, Deserializable, GetRepresentationHash, MsgAddrStd, MsgAddressInt, Serializable, @@ -247,12 +247,9 @@ pub fn unpack_from_cell( abi_version: ton_abi::contract::AbiVersion, ) -> Result> { let cs: Cursor = cursor.into(); - let (tokens, cursor) = - TokenValue::decode_params_with_cursor(params, cs, &abi_version, allow_partial, false)?; + let (tokens, cursor) = TokenValue::decode_params_with_cursor(params, cs, &abi_version, allow_partial, false)?; - if !allow_partial - && (cursor.slice.remaining_references() != 0 || cursor.slice.remaining_bits() != 0) - { + if !allow_partial && (cursor.slice.remaining_references() != 0 || cursor.slice.remaining_bits() != 0) { Err(AbiError::IncompleteDeserialization(cursor.slice).into()) } else { Ok(tokens) @@ -358,7 +355,7 @@ pub fn decode_input<'a>( None => return Ok(None), }; - let input = function.decode_input(message_body, internal, false)?; + let input = function.decode_input(message_body, internal, false )?; Ok(Some((function, input))) } diff --git a/nekoton-contracts/Cargo.toml b/nekoton-contracts/Cargo.toml index 35de2e683..c3a83c58d 100644 --- a/nekoton-contracts/Cargo.toml +++ b/nekoton-contracts/Cargo.toml @@ -17,7 +17,7 @@ thiserror = "1.0" ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } nekoton-abi = { path = "../nekoton-abi", features = ["derive"] } nekoton-jetton = { path = "../nekoton-jetton" } diff --git a/nekoton-derive/Cargo.toml b/nekoton-derive/Cargo.toml index 1cef9cfd1..781efc558 100644 --- a/nekoton-derive/Cargo.toml +++ b/nekoton-derive/Cargo.toml @@ -22,7 +22,7 @@ num-bigint = "0.4" num-traits = "0.2" trybuild = "1.0" -ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-jetton/Cargo.toml b/nekoton-jetton/Cargo.toml index 8bae8aa6f..7bb9b229a 100644 --- a/nekoton-jetton/Cargo.toml +++ b/nekoton-jetton/Cargo.toml @@ -17,7 +17,7 @@ sha2 = "0.10.8" ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } nekoton-utils = { path = "../nekoton-utils" } num-bigint = "0.4" diff --git a/src/core/jetton_wallet/mod.rs b/src/core/jetton_wallet/mod.rs index bd433e3da..55768ff4e 100644 --- a/src/core/jetton_wallet/mod.rs +++ b/src/core/jetton_wallet/mod.rs @@ -595,6 +595,8 @@ mod tests { account_stuff: &state, }); + + let details = contract.get_details()?; assert_eq!(details.admin_address, MsgAddressInt::default()); From 1136ecac89850853e4f32ff5c813d95d5c2374b7 Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Tue, 11 Mar 2025 14:04:42 +0100 Subject: [PATCH 2/9] update tests, bump ton-abi --- Cargo.toml | 2 +- nekoton-abi/Cargo.toml | 2 +- nekoton-contracts/Cargo.toml | 2 +- nekoton-derive/Cargo.toml | 2 +- nekoton-jetton/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87930ecf0..12be8b94b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ ed25519-dalek = { git = "https://github.com/broxus/ed25519-dalek.git", optional tiny-bip39 = { git = "https://github.com/broxus/tiny-bip39.git", default-features = false, optional = true } tiny-hderive = { git = "https://github.com/broxus/tiny-hderive.git", optional = true } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_executor = { git = "https://github.com/broxus/ton-labs-executor.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-abi/Cargo.toml b/nekoton-abi/Cargo.toml index 0f29ad9af..d804e6c08 100644 --- a/nekoton-abi/Cargo.toml +++ b/nekoton-abi/Cargo.toml @@ -25,7 +25,7 @@ thiserror = "1.0" ed25519-dalek = { git = "https://github.com/broxus/ed25519-dalek.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_executor = { git = "https://github.com/broxus/ton-labs-executor.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-contracts/Cargo.toml b/nekoton-contracts/Cargo.toml index c3a83c58d..261e8fa85 100644 --- a/nekoton-contracts/Cargo.toml +++ b/nekoton-contracts/Cargo.toml @@ -17,7 +17,7 @@ thiserror = "1.0" ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } nekoton-abi = { path = "../nekoton-abi", features = ["derive"] } nekoton-jetton = { path = "../nekoton-jetton" } diff --git a/nekoton-derive/Cargo.toml b/nekoton-derive/Cargo.toml index 781efc558..f8016cf1b 100644 --- a/nekoton-derive/Cargo.toml +++ b/nekoton-derive/Cargo.toml @@ -22,7 +22,7 @@ num-bigint = "0.4" num-traits = "0.2" trybuild = "1.0" -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-jetton/Cargo.toml b/nekoton-jetton/Cargo.toml index 7bb9b229a..1240f488c 100644 --- a/nekoton-jetton/Cargo.toml +++ b/nekoton-jetton/Cargo.toml @@ -17,7 +17,7 @@ sha2 = "0.10.8" ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", rev = "6a0a4b3424ffc576951e8c9bf62822cb84199493" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } nekoton-utils = { path = "../nekoton-utils" } num-bigint = "0.4" From d730d4951c52c7e4a615d59454671f07c29f1899 Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Tue, 11 Mar 2025 14:18:06 +0100 Subject: [PATCH 3/9] fmt --- nekoton-abi/src/lib.rs | 11 +++++++---- src/core/jetton_wallet/mod.rs | 2 -- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/nekoton-abi/src/lib.rs b/nekoton-abi/src/lib.rs index 24e05cf42..13ffa2607 100644 --- a/nekoton-abi/src/lib.rs +++ b/nekoton-abi/src/lib.rs @@ -58,8 +58,8 @@ use std::sync::Arc; use anyhow::Result; use num_traits::ToPrimitive; use smallvec::smallvec; -use ton_abi::{Function, Param, Token, TokenValue}; use ton_abi::token::Cursor; +use ton_abi::{Function, Param, Token, TokenValue}; use ton_block::{ Account, AccountStuff, Deserializable, GetRepresentationHash, MsgAddrStd, MsgAddressInt, Serializable, @@ -247,9 +247,12 @@ pub fn unpack_from_cell( abi_version: ton_abi::contract::AbiVersion, ) -> Result> { let cs: Cursor = cursor.into(); - let (tokens, cursor) = TokenValue::decode_params_with_cursor(params, cs, &abi_version, allow_partial, false)?; + let (tokens, cursor) = + TokenValue::decode_params_with_cursor(params, cs, &abi_version, allow_partial, false)?; - if !allow_partial && (cursor.slice.remaining_references() != 0 || cursor.slice.remaining_bits() != 0) { + if !allow_partial + && (cursor.slice.remaining_references() != 0 || cursor.slice.remaining_bits() != 0) + { Err(AbiError::IncompleteDeserialization(cursor.slice).into()) } else { Ok(tokens) @@ -355,7 +358,7 @@ pub fn decode_input<'a>( None => return Ok(None), }; - let input = function.decode_input(message_body, internal, false )?; + let input = function.decode_input(message_body, internal, false)?; Ok(Some((function, input))) } diff --git a/src/core/jetton_wallet/mod.rs b/src/core/jetton_wallet/mod.rs index 55768ff4e..bd433e3da 100644 --- a/src/core/jetton_wallet/mod.rs +++ b/src/core/jetton_wallet/mod.rs @@ -595,8 +595,6 @@ mod tests { account_stuff: &state, }); - - let details = contract.get_details()?; assert_eq!(details.admin_address, MsgAddressInt::default()); From 34426d6ee7429b51308352749fc8afe9322ad401 Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Mon, 5 May 2025 19:53:11 +0200 Subject: [PATCH 4/9] Partially implement transport fot TON api v4 --- Cargo.toml | 3 +- nekoton-transport/Cargo.toml | 3 +- nekoton-transport/src/lib.rs | 2 + nekoton-transport/src/ton.rs | 71 ++++++ nekoton-utils/src/serde_helpers.rs | 172 ++++++++++++++ src/external/mod.rs | 20 ++ src/transport/mod.rs | 3 + src/transport/ton/mod.rs | 360 +++++++++++++++++++++++++++++ src/transport/ton/models.rs | 177 ++++++++++++++ 9 files changed, 809 insertions(+), 2 deletions(-) create mode 100644 nekoton-transport/src/ton.rs create mode 100644 src/transport/ton/mod.rs create mode 100644 src/transport/ton/models.rs diff --git a/Cargo.toml b/Cargo.toml index 12be8b94b..2a925fe47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,7 @@ nekoton-utils = { path = "nekoton-utils" } nekoton-proto = { path = "nekoton-proto", optional = true } [dev-dependencies] -reqwest = { version = "0.11.8", features = ["gzip"] } +reqwest = { version = "0.11.8", features = ["gzip", "json"] } cargo-husky = { version = "1", features = ["default", "run-cargo-fmt", "run-cargo-check"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } @@ -85,6 +85,7 @@ web = [ gql_transport = ["dep:erased-serde"] jrpc_transport = ["dep:tiny-jsonrpc"] proto_transport = ["dep:nekoton-proto"] +ton_transport = [] extended_models = [] non_threadsafe = [] wallet_core = ["dep:pbkdf2", "dep:chacha20poly1305", "dep:zeroize", "dep:secstr", "dep:hmac", "dep:ed25519-dalek", diff --git a/nekoton-transport/Cargo.toml b/nekoton-transport/Cargo.toml index 3a57fae79..a74dacfb4 100644 --- a/nekoton-transport/Cargo.toml +++ b/nekoton-transport/Cargo.toml @@ -16,6 +16,7 @@ futures-util = "0.3" log = "0.4" reqwest = { version = "0.11", features = ["json", "gzip", "rustls-tls"], default-features = false } serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" thiserror = "1.0" tokio = { version = "1", features = ["sync", "time"] } @@ -24,7 +25,6 @@ nekoton-utils = { path = "../nekoton-utils" } nekoton = { path = ".." } [dev-dependencies] -base64 = "0.13" tokio = { version = "1", features = ["sync", "time", "macros"] } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } @@ -34,3 +34,4 @@ default = ["gql_transport"] gql_transport = ["nekoton/gql_transport"] jrpc_transport = ["nekoton/jrpc_transport"] proto_transport = ["nekoton/proto_transport"] +ton_transport = ["nekoton/ton_transport"] diff --git a/nekoton-transport/src/lib.rs b/nekoton-transport/src/lib.rs index 6e3595139..a5db373cf 100644 --- a/nekoton-transport/src/lib.rs +++ b/nekoton-transport/src/lib.rs @@ -58,3 +58,5 @@ pub mod gql; pub mod jrpc; #[cfg(feature = "proto_transport")] pub mod proto; +#[cfg(feature = "ton_transport")] +pub mod ton; diff --git a/nekoton-transport/src/ton.rs b/nekoton-transport/src/ton.rs new file mode 100644 index 000000000..d7fc190d9 --- /dev/null +++ b/nekoton-transport/src/ton.rs @@ -0,0 +1,71 @@ +use nekoton::external::TonApiError; +use reqwest::{IntoUrl, Url}; +use serde::Serialize; + +pub struct TonClient { + endpoint: Url, + client: reqwest::Client, +} + +impl TonClient { + pub fn new_v4(endpoint: U) -> anyhow::Result { + let url = endpoint.into_url()?; + Ok(Self { + endpoint: url, + client: reqwest::Client::new(), + }) + } + + pub fn endpoint(&self) -> &Url { + &self.endpoint + } + + pub async fn send_get(&self, path: U) -> anyhow::Result { + let path = path.into_url()?; + let result = self + .client + .get(self.endpoint.clone().join(path.as_str())?) + .header("ContentType", "application/json") + .send() + .await? + .json() + .await?; + + Ok(result) + } + + pub async fn send_post( + &self, + body: R, + path: U, + ) -> anyhow::Result { + let path = path.into_url()?; + let result = self + .client + .post(self.endpoint.clone().join(path.as_str())?) + .body(serde_json::to_string(&body)?) + .header("ContentType", "application/json") + .send() + .await? + .json() + .await?; + + Ok(result) + } +} + +#[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] +#[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] +impl nekoton::external::TonConnection for TonClient { + async fn send_get(&self, path: &str) -> Result { + self.send_get(path).await.map_err(TonApiError::from) + } + + async fn send_post( + &self, + body: &serde_json::Value, + path: &str, + ) -> Result { + self.send_post(body, path).await.map_err(TonApiError::from) + } +} diff --git a/nekoton-utils/src/serde_helpers.rs b/nekoton-utils/src/serde_helpers.rs index c6223633e..b7cc1a662 100644 --- a/nekoton-utils/src/serde_helpers.rs +++ b/nekoton-utils/src/serde_helpers.rs @@ -46,6 +46,42 @@ impl<'de> Deserialize<'de> for StringOrNumber { } } +struct U128StringOrNumber(u128); + +impl Serialize for U128StringOrNumber { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + if self.0 <= 0x1fffffffffffff_u128 || !serializer.is_human_readable() { + serializer.serialize_u128(self.0) + } else { + serializer.serialize_str(&self.0.to_string()) + } + } +} + +impl<'de> Deserialize<'de> for U128StringOrNumber { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Value<'a> { + String(#[serde(borrow)] Cow<'a, str>), + Number(u128), + } + + match Value::deserialize(deserializer)? { + Value::String(str) => u128::from_str(str.as_ref()) + .map(Self) + .map_err(|_| D::Error::custom("Invalid number")), + Value::Number(value) => Ok(Self(value)), + } + } +} + pub mod serde_u64 { use super::*; @@ -64,6 +100,25 @@ pub mod serde_u64 { } } + +pub mod serde_u128 { + use super::*; + + pub fn serialize(data: &u128, serializer: S) -> Result + where + S: serde::Serializer, + { + U128StringOrNumber(*data).serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + U128StringOrNumber::deserialize(deserializer).map(|U128StringOrNumber(x)| x) + } +} + pub mod serde_optional_u64 { use super::*; @@ -138,6 +193,33 @@ pub mod serde_base64_array { } } +pub mod serde_optional_base64_array { + use super::*; + + pub fn serialize(data: &dyn AsRef<[u8]>, serializer: S) -> Result + where + S: serde::Serializer, + { + serde_bytes_base64::serialize(data, serializer) + } + + pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let data = serde_bytes_base64::deserialize_string_optional(deserializer)?; + match data { + Some(data) => { + let result = data.try_into().map_err(|_| { + D::Error::custom(format!("Invalid array length, expected: {N}")) + })?; + Ok(Some(result)) + } + None => Ok(None), + } + } +} + pub mod serde_hex_array { use super::*; @@ -292,6 +374,25 @@ pub mod serde_uint256 { } } +pub mod serde_base64_uint256 { + use super::*; + + pub fn serialize(data: &UInt256, serializer: S) -> Result + where + S: serde::Serializer, + { + serde_base64_array::serialize(data.as_slice(), serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data: [u8; 32] = serde_base64_array::deserialize(deserializer)?; + Ok(UInt256::from_slice(&data[..])) + } +} + pub mod serde_optional_uint256 { use super::*; @@ -397,6 +498,26 @@ pub mod serde_address { } } +pub mod serde_base64_address { + use super::*; + use crate::repack_address; + + pub fn serialize(data: &MsgAddressInt, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&data.to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data = String::deserialize(deserializer)?; + repack_address(&data).map_err(|_| D::Error::custom("Invalid address")) + } +} + pub mod serde_optional_address { use super::*; @@ -579,6 +700,37 @@ pub mod serde_bytes_base64 { deserializer.deserialize_bytes(BytesVisitor) } } + + pub fn deserialize_string_optional<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: serde::Deserializer<'de>, + { + struct Base64Visitor; + + impl<'de> Visitor<'de> for Base64Visitor { + type Value = Option>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("base64-encoded byte array") + } + + fn visit_str(self, value: &str) -> Result { + if value.is_empty() { + return Ok(None); + } + base64::decode(value) + .map(|x| Some(x)) + .map_err(|_| E::invalid_type(Unexpected::Str(value), &self)) + } + + // See the `deserializing_flattened_field` test for an example why this is needed. + fn visit_bytes(self, value: &[u8]) -> Result { + Ok(Some(value.to_vec())) + } + } + + deserializer.deserialize_str(Base64Visitor) + } } pub mod serde_bytes_base64_optional { @@ -672,6 +824,26 @@ pub mod serde_cell { } } +pub mod serde_transaction_array { + use super::*; + use ton_block::{Deserializable, Transaction}; + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let bytes = serde_bytes_base64::deserialize(deserializer)?; + let cells = + ton_types::deserialize_cells_tree(&mut bytes.as_slice()).map_err(Error::custom)?; + let mut transactions = Vec::new(); + for c in cells { + let t = Transaction::construct_from_cell(c).map_err(Error::custom)?; + transactions.push(t); + } + + Ok(transactions) + } +} + pub mod serde_ton_block { use ton_block::{Deserializable, Serializable}; diff --git a/src/external/mod.rs b/src/external/mod.rs index fc5634f6c..ff3f96148 100644 --- a/src/external/mod.rs +++ b/src/external/mod.rs @@ -65,6 +65,26 @@ pub trait ProtoConnection: Send + Sync { async fn post(&self, req: ProtoRequest) -> Result>; } +#[cfg(feature = "ton_transport")] +#[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] +#[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] +pub trait TonConnection: Send + Sync { + async fn send_get(&self, path: &str) -> Result; + async fn send_post( + &self, + body: &serde_json::Value, + path: &str, + ) -> Result; +} + +#[derive(thiserror::Error, Debug)] +pub enum TonApiError { + #[error("Requested entity not found")] + NotFound, + #[error("General error occurred {0}")] + General(#[from] anyhow::Error), +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct LedgerSignatureContext { diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 8038c8303..0318e8a0f 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -13,12 +13,15 @@ pub mod gql; pub mod jrpc; #[cfg(feature = "proto_transport")] pub mod proto; +#[cfg(feature = "ton_transport")] +pub mod ton; pub mod models; #[cfg(any( feature = "gql_transport", feature = "jrpc_transport", feature = "proto_transport", + feature = "ton_transport", ))] mod utils; diff --git a/src/transport/ton/mod.rs b/src/transport/ton/mod.rs new file mode 100644 index 000000000..e7db89124 --- /dev/null +++ b/src/transport/ton/mod.rs @@ -0,0 +1,360 @@ +mod models; + +use nekoton_abi::{GenTimings, LastTransactionId, TransactionId}; +use nekoton_utils::{pack_std_smc_addr, Clock}; +use std::sync::Arc; +use ton_block::{ + AccountStorage, AccountStuff, Block, CurrencyCollection, GetRepresentationHash, Grams, Message, + MsgAddressInt, Serializable, StorageInfo, VarUInteger7, +}; +use ton_executor::BlockchainConfig; +use ton_types::{Cell, UInt256}; + +use super::{Transport, TransportInfo}; +use crate::external::{TonApiError, TonConnection}; +use crate::models::{NetworkCapabilities, ReliableBehavior}; +use crate::transport::models::{ + ExistingContract, PollContractState, RawContractState, RawTransaction, +}; +use crate::transport::ton::models::*; + +pub struct TonTransport { + connection: Arc, +} + +impl TonTransport { + pub fn new(connection: Arc) -> Self { + Self { connection } + } + async fn get_latest_block(&self) -> anyhow::Result { + let result = self.connection.send_get("block/latest").await?; + let result = serde_json::from_value(result)?; + Ok(result) + } + + // pub async fn get_full_block(&self, seqno: u32) -> anyhow::Result { + // let result = self.connection.send_get(&format!("block/{seqno}")).await?; + // let result = serde_json::from_value(result)?; + // Ok(result) + // } + + // pub async fn get_full_block_by_utime(&self, utime: u64) -> anyhow::Result { + // let result = self.connection.send_get(&format!("block/utime/{utime}")).await?; + // let result = serde_json::from_value(result)?; + // Ok(result) + // } + + async fn get_account_state( + &self, + block_seqno: u32, + address: &MsgAddressInt, + ) -> anyhow::Result> { + let base64_address = pack_std_smc_addr(false, address, false)?; + let result = self + .connection + .send_get(&format!("block/{block_seqno}/{base64_address}")) + .await; + let result = match result { + Ok(result) if result.is_null() => return Ok(None), + Ok(result) => serde_json::from_value(result)?, + Err(TonApiError::NotFound) => return Ok(None), + Err(err) => return Err(err.into()), + }; + let result = serde_json::from_value(result)?; + Ok(result) + } + + // async fn check_account_changed(&self, block_seqno: u64, address: &MsgAddressInt, since_lt: u64) -> anyhow::Result { + // let base64_address = pack_std_smc_addr(false, address, false)?; + // let result = self.connection.send_get(&format!("block/{block_seqno}/{base64_address}/changed/{since_lt}")).await?; + // let result = serde_json::from_value(result)?; + // Ok(result) + // } + + async fn get_config(&self, block_seqno: u32, params: I) -> anyhow::Result + where + I: IntoIterator, + { + let params_string = params + .into_iter() + .map(|x| x.to_string()) + .collect::>() + .join(","); + let result = self + .connection + .send_get(&format!("block/{block_seqno}/config/{params_string}")) + .await?; + let result = serde_json::from_value(result)?; + Ok(result) + } + + async fn get_account_transactions( + &self, + address: &MsgAddressInt, + lt: u64, + ) -> anyhow::Result { + let base64_address = pack_std_smc_addr(false, address, false)?; + let result = self + .connection + .send_get(&format!("account/{base64_address}/tx/{lt}/-")) + .await?; + let result = serde_json::from_value(result)?; + Ok(result) + } + + async fn send_message(&self, message_cell: Cell) -> anyhow::Result<()> { + let bytes = ton_types::serialize_toc(&message_cell)?; + let boc = base64::encode(bytes); + let body = serde_json::to_value(&MessageBoc { boc })?; + + self.connection.send_post(&body, "/send").await?; + Ok(()) + } +} + +#[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] +#[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] +impl Transport for TonTransport { + fn info(&self) -> TransportInfo { + TransportInfo { + max_transactions_per_fetch: 20, + reliable_behavior: ReliableBehavior::BlockWalking, + has_key_blocks: false, + } + } + + async fn send_message(&self, message: &Message) -> anyhow::Result<()> { + let cell = message.serialize()?; + self.send_message(cell).await + } + + async fn get_contract_state( + &self, + address: &MsgAddressInt, + ) -> anyhow::Result { + let latest_block = self.get_latest_block().await?; + let state_opt = self + .get_account_state(latest_block.last.seqno, address) + .await?; + + let timings = GenTimings::Known { + gen_lt: 0, + gen_utime: latest_block.now, + }; //TODO: how to get gen_lt + + let state = match state_opt { + None => return Ok(RawContractState::NotExists { timings }), + Some(state) => state, + }; + + let account_state = match state.account.state { + AccountState::Active { code, data } => ton_block::AccountState::AccountActive { + state_init: ton_block::StateInit { + split_depth: None, + special: None, + code: Some(code), + data: Some(data), + library: Default::default(), + }, + }, + AccountState::Frozen { state_init_hash } => { + ton_block::AccountState::AccountFrozen { state_init_hash } + } + AccountState::Uninit => ton_block::AccountState::AccountUninit, + }; + + let mut balance = CurrencyCollection::new(); + balance.grams = Grams::new(state.account.balance.coins)?; + for (key, value) in state.account.balance.currencies.unwrap_or_default() { + balance.set_other(key, value)?; + } + + let used = state.account.storage_stat.used; + + let stuff = AccountStuff { + addr: address.clone(), + storage_stat: StorageInfo { + used: ton_block::StorageUsed { + cells: VarUInteger7::new(used.cells)?, + bits: VarUInteger7::new(used.bits)?, + public_cells: VarUInteger7::new(used.public_cells)?, + }, + last_paid: state.account.storage_stat.last_paid, + due_payment: match state.account.storage_stat.due_payment { + Some(due_payment) => Some(Grams::new(due_payment)?), + None => None, + }, + }, + storage: AccountStorage { + last_trans_lt: state + .account + .last_transaction + .as_ref() + .map(|x| x.lt) + .unwrap_or_default(), + balance, + state: account_state, + init_code_hash: None, + }, + }; + + let contract_state = RawContractState::Exists(ExistingContract { + account: stuff, + timings, + last_transaction_id: match state.account.last_transaction { + Some(last) => LastTransactionId::Exact(TransactionId { + lt: last.lt, + hash: last.hash, + }), + None => LastTransactionId::Inexact { latest_lt: 0 }, + }, + }); + + Ok(contract_state) + } + + async fn get_library_cell(&self, _: &UInt256) -> anyhow::Result> { + todo!() + } + + async fn poll_contract_state( + &self, + _: &MsgAddressInt, + _: u64, + ) -> anyhow::Result { + todo!() + } + + async fn get_accounts_by_code_hash( + &self, + _: &UInt256, + _: u8, + _: &Option, + ) -> anyhow::Result> { + todo!() + } + + async fn get_transactions( + &self, + address: &MsgAddressInt, + from_lt: u64, + _: u8, + ) -> anyhow::Result> { + let result = self.get_account_transactions(address, from_lt).await?; + let mut transactions = Vec::with_capacity(result.transactions.len()); + for t in result.transactions { + transactions.push(RawTransaction { + hash: t.hash()?, + data: t.clone(), + }); + } + + Ok(transactions) + } + + async fn get_transaction(&self, id: &UInt256) -> anyhow::Result> { + todo!() + } + + async fn get_dst_transaction(&self, _: &UInt256) -> anyhow::Result> { + todo!() + } + + async fn get_latest_key_block(&self) -> anyhow::Result { + todo!() + } + + async fn get_capabilities(&self, _: &dyn Clock) -> anyhow::Result { + todo!() + } + + async fn get_blockchain_config( + &self, + _: &dyn Clock, + _: bool, + ) -> anyhow::Result { + let latest_block = self.get_latest_block().await?; + let config = self + .get_config(latest_block.last.seqno, vec![20, 21, 24, 25, 18, 31]) + .await?; + if let Some(config) = config.config { + let config = ton_block::ConfigParams::with_root(config.cell); + return Ok(BlockchainConfig::with_config(config, 0)?); + } + + anyhow::bail!("Failed to get blockchain config") + } +} + +#[cfg(test)] +pub mod tests { + use crate::external::{TonApiError, TonConnection}; + use crate::transport::ton::TonTransport; + use crate::transport::Transport; + use nekoton_utils::{unpack_std_smc_addr, SimpleClock}; + use reqwest::Url; + use serde_json::Value; + use std::sync::Arc; + + #[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] + #[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] + impl TonConnection for reqwest::Client { + async fn send_get(&self, path: &str) -> Result { + let base = Url::parse("https://mainnet-v4.tonhubapi.com") + .map_err(|e| TonApiError::General(e.into()))?; + + let path = base + .join(path) + .map_err(|e| TonApiError::General(e.into()))?; + let result = self + .get(path) + .header("ContentType", "application/json") + .send() + .await + .map_err(|e| TonApiError::General(e.into()))? + .json() + .await + .map_err(|e| TonApiError::General(e.into()))?; + + Ok(result) + } + + async fn send_post(&self, body: &Value, path: &str) -> Result { + todo!() + } + } + + #[tokio::test] + async fn test_account_state() -> anyhow::Result<(), TonApiError> { + let client = reqwest::Client::new(); + let address = + unpack_std_smc_addr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N", true)?; + let transport = TonTransport::new(Arc::new(client)); + let state = transport.get_contract_state(&address).await?; + println!("{:?}", state); + Ok(()) + } + + #[tokio::test] + async fn test_get_transactions() -> anyhow::Result<(), TonApiError> { + let client = reqwest::Client::new(); + let address = + unpack_std_smc_addr("EQCo6VT63H1vKJTiUo6W4M8RrTURCyk5MdbosuL5auEqpz-C", true)?; + let transport = TonTransport::new(Arc::new(client)); + let state = transport + .get_transactions(&address, 27668319000001, 20) + .await?; + println!("{:?}", state); + Ok(()) + } + + #[tokio::test] + async fn test_get_config() -> anyhow::Result<(), TonApiError> { + let client = reqwest::Client::new(); + let transport = TonTransport::new(Arc::new(client)); + let config = transport.get_blockchain_config(&SimpleClock, true).await?; + println!("{:?}", config.get_fwd_prices(true)); + println!("{:?}", config.get_fwd_prices(false)); + Ok(()) + } +} diff --git a/src/transport/ton/models.rs b/src/transport/ton/models.rs new file mode 100644 index 000000000..f534a3217 --- /dev/null +++ b/src/transport/ton/models.rs @@ -0,0 +1,177 @@ +use nekoton_utils::{ + serde_base64_address, serde_base64_uint256, serde_cell, serde_optional_base64_array, + serde_transaction_array, serde_u128, serde_u64, +}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use ton_block::VarUInteger7; +use ton_types::{Cell, UInt256}; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct LatestBlock { + pub last: BlockId, + pub init: Init, + #[serde(with = "serde_optional_base64_array")] + pub state_root_hash: Option<[u8; 32]>, + pub now: u32, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct BlockId { + #[serde(with = "serde_base64_uint256")] + pub root_hash: UInt256, + #[serde(with = "serde_base64_uint256")] + pub file_hash: UInt256, + pub seqno: u32, + //#[serde(with = "serde_string_to_u64")] + pub shard: String, + pub workchain: i32, + #[serde(default)] + pub transactions: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Init { + #[serde(with = "serde_base64_uint256")] + pub root_hash: UInt256, + #[serde(with = "serde_base64_uint256")] + pub file_hash: UInt256, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct FullBlock { + pub exist: bool, + pub block: Option, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ShardInfo { + pub shards: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Transaction { + #[serde(with = "serde_base64_address")] + pub account: ton_block::MsgAddressInt, + #[serde(with = "serde_base64_uint256")] + pub hash: UInt256, + #[serde(with = "serde_u64")] + pub lt: u64, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AccountStateResult { + pub account: AccountInfo, + pub block: BlockId, +} + +#[derive(Debug, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "camelCase")] +pub enum AccountState { + #[serde(rename = "active")] + Active { + #[serde(with = "serde_cell")] + code: Cell, + #[serde(with = "serde_cell")] + data: Cell, + }, + + #[serde(rename = "frozen")] + Frozen { + #[serde(rename = "state_hash", with = "serde_base64_uint256")] + state_init_hash: UInt256, + }, + + #[serde(rename = "uninit")] + Uninit, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AccountInfo { + pub state: AccountState, + pub balance: AccountBalance, + pub storage_stat: StorageStat, + #[serde(rename = "last")] + pub last_transaction: Option, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct StorageStat { + pub last_paid: u32, + pub used: StorageUsed, + #[serde(default)] + pub due_payment: Option, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct StorageUsed { + pub cells: u64, + pub bits: u64, + pub public_cells: u64, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AccountBalance { + #[serde(with = "serde_u128")] + pub coins: u128, + #[serde(default)] + pub currencies: Option>, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct LastTransaction { + #[serde(with = "serde_base64_uint256")] + pub hash: UInt256, + #[serde(with = "serde_u64")] + pub lt: u64, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AccountChangedResult { + pub changed: bool, + pub block: BlockId, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ConfigResult { + pub exist: bool, + pub config: Option, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ConfigInfo { + #[serde(with = "serde_cell")] + pub cell: Cell, + #[serde(with = "serde_base64_address")] + pub address: ton_block::MsgAddressInt, + pub global_balance: AccountBalance, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AccountTransactionsResult { + pub blocks: Vec, + #[serde(rename = "boc", with = "serde_transaction_array")] + pub transactions: Vec, +} + +#[derive(Serialize, Debug)] +pub struct MessageBoc { + pub boc: String, +} From 1cef606d99ca89e52579bca44763896ab7a9e011 Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Tue, 6 May 2025 16:36:09 +0200 Subject: [PATCH 5/9] Update transaction obtaining --- src/transport/ton/mod.rs | 66 ++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/transport/ton/mod.rs b/src/transport/ton/mod.rs index e7db89124..460c60185 100644 --- a/src/transport/ton/mod.rs +++ b/src/transport/ton/mod.rs @@ -238,21 +238,48 @@ impl Transport for TonTransport { &self, address: &MsgAddressInt, from_lt: u64, - _: u8, + count: u8, ) -> anyhow::Result> { - let result = self.get_account_transactions(address, from_lt).await?; - let mut transactions = Vec::with_capacity(result.transactions.len()); - for t in result.transactions { - transactions.push(RawTransaction { - hash: t.hash()?, - data: t.clone(), - }); + const AT_MOST: usize = 20; + + let mut remaining = count; + let mut transactions = Vec::with_capacity(count as usize); + + loop { + let result = self.get_account_transactions(address, from_lt).await?; + let len = result.transactions.len(); + let to_process = if len > remaining as usize { + result.transactions.into_iter().take(remaining as usize).collect::>() + } else { + result.transactions + }; + + for t in &to_process { + transactions.push(RawTransaction { + hash: t.hash()?, + data: t.clone(), + }); + } + remaining = remaining.saturating_sub(len as u8); + + if AT_MOST > len || remaining == 0 { + break; + } + + if let Some(last) = transactions.last() { + if last.data.prev_trans_lt == 0 { + break; + } + } } + + + Ok(transactions) } - async fn get_transaction(&self, id: &UInt256) -> anyhow::Result> { + async fn get_transaction(&self, _: &UInt256) -> anyhow::Result> { todo!() } @@ -275,7 +302,7 @@ impl Transport for TonTransport { ) -> anyhow::Result { let latest_block = self.get_latest_block().await?; let config = self - .get_config(latest_block.last.seqno, vec![20, 21, 24, 25, 18, 31]) + .get_config(latest_block.last.seqno, vec![8, 20, 21, 24, 25, 18, 31]) .await?; if let Some(config) = config.config { let config = ton_block::ConfigParams::with_root(config.cell); @@ -288,14 +315,15 @@ impl Transport for TonTransport { #[cfg(test)] pub mod tests { - use crate::external::{TonApiError, TonConnection}; - use crate::transport::ton::TonTransport; - use crate::transport::Transport; use nekoton_utils::{unpack_std_smc_addr, SimpleClock}; use reqwest::Url; use serde_json::Value; use std::sync::Arc; + use crate::external::{TonApiError, TonConnection}; + use crate::transport::ton::TonTransport; + use crate::transport::Transport; + #[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] #[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] impl TonConnection for reqwest::Client { @@ -306,6 +334,7 @@ pub mod tests { let path = base .join(path) .map_err(|e| TonApiError::General(e.into()))?; + let result = self .get(path) .header("ContentType", "application/json") @@ -341,10 +370,15 @@ pub mod tests { let address = unpack_std_smc_addr("EQCo6VT63H1vKJTiUo6W4M8RrTURCyk5MdbosuL5auEqpz-C", true)?; let transport = TonTransport::new(Arc::new(client)); - let state = transport - .get_transactions(&address, 27668319000001, 20) + let transactions = transport + .get_transactions(&address, 27668319000001, u8::MAX) .await?; - println!("{:?}", state); + + let mut prev_tx_lt = transactions.first().unwrap().data.lt; + for i in &transactions { + assert_eq!(i.data.lt, prev_tx_lt); + prev_tx_lt = i.data.prev_trans_lt; + } Ok(()) } From 7f95f94b42b3af57292acf2d534d0e72afe67888 Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Tue, 6 May 2025 17:29:52 +0200 Subject: [PATCH 6/9] Add support for poll_contract_state + account_cache to ton_transport --- nekoton-utils/src/serde_helpers.rs | 1 - src/transport/ton/mod.rs | 210 ++++++++++++++++++----------- src/transport/ton/models.rs | 6 +- 3 files changed, 133 insertions(+), 84 deletions(-) diff --git a/nekoton-utils/src/serde_helpers.rs b/nekoton-utils/src/serde_helpers.rs index b7cc1a662..86501fe34 100644 --- a/nekoton-utils/src/serde_helpers.rs +++ b/nekoton-utils/src/serde_helpers.rs @@ -100,7 +100,6 @@ pub mod serde_u64 { } } - pub mod serde_u128 { use super::*; diff --git a/src/transport/ton/mod.rs b/src/transport/ton/mod.rs index 460c60185..9429ba5e7 100644 --- a/src/transport/ton/mod.rs +++ b/src/transport/ton/mod.rs @@ -17,14 +17,19 @@ use crate::transport::models::{ ExistingContract, PollContractState, RawContractState, RawTransaction, }; use crate::transport::ton::models::*; +use crate::transport::utils::AccountsCache; pub struct TonTransport { connection: Arc, + accounts_cache: AccountsCache, } impl TonTransport { pub fn new(connection: Arc) -> Self { - Self { connection } + Self { + connection, + accounts_cache: AccountsCache::new(), + } } async fn get_latest_block(&self) -> anyhow::Result { let result = self.connection.send_get("block/latest").await?; @@ -64,74 +69,10 @@ impl TonTransport { Ok(result) } - // async fn check_account_changed(&self, block_seqno: u64, address: &MsgAddressInt, since_lt: u64) -> anyhow::Result { - // let base64_address = pack_std_smc_addr(false, address, false)?; - // let result = self.connection.send_get(&format!("block/{block_seqno}/{base64_address}/changed/{since_lt}")).await?; - // let result = serde_json::from_value(result)?; - // Ok(result) - // } - - async fn get_config(&self, block_seqno: u32, params: I) -> anyhow::Result - where - I: IntoIterator, - { - let params_string = params - .into_iter() - .map(|x| x.to_string()) - .collect::>() - .join(","); - let result = self - .connection - .send_get(&format!("block/{block_seqno}/config/{params_string}")) - .await?; - let result = serde_json::from_value(result)?; - Ok(result) - } - - async fn get_account_transactions( + async fn get_contract_state_ext( &self, address: &MsgAddressInt, - lt: u64, - ) -> anyhow::Result { - let base64_address = pack_std_smc_addr(false, address, false)?; - let result = self - .connection - .send_get(&format!("account/{base64_address}/tx/{lt}/-")) - .await?; - let result = serde_json::from_value(result)?; - Ok(result) - } - - async fn send_message(&self, message_cell: Cell) -> anyhow::Result<()> { - let bytes = ton_types::serialize_toc(&message_cell)?; - let boc = base64::encode(bytes); - let body = serde_json::to_value(&MessageBoc { boc })?; - - self.connection.send_post(&body, "/send").await?; - Ok(()) - } -} - -#[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] -#[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] -impl Transport for TonTransport { - fn info(&self) -> TransportInfo { - TransportInfo { - max_transactions_per_fetch: 20, - reliable_behavior: ReliableBehavior::BlockWalking, - has_key_blocks: false, - } - } - - async fn send_message(&self, message: &Message) -> anyhow::Result<()> { - let cell = message.serialize()?; - self.send_message(cell).await - } - - async fn get_contract_state( - &self, - address: &MsgAddressInt, - ) -> anyhow::Result { + ) -> Result { let latest_block = self.get_latest_block().await?; let state_opt = self .get_account_state(latest_block.last.seqno, address) @@ -213,16 +154,120 @@ impl Transport for TonTransport { Ok(contract_state) } + // async fn check_account_changed(&self, block_seqno: u64, address: &MsgAddressInt, since_lt: u64) -> anyhow::Result { + // let base64_address = pack_std_smc_addr(false, address, false)?; + // let result = self.connection.send_get(&format!("block/{block_seqno}/{base64_address}/changed/{since_lt}")).await?; + // let result = serde_json::from_value(result)?; + // Ok(result) + // } + + async fn get_config(&self, block_seqno: u32, params: I) -> anyhow::Result + where + I: IntoIterator, + { + let params_string = params + .into_iter() + .map(|x| x.to_string()) + .collect::>() + .join(","); + let result = self + .connection + .send_get(&format!("block/{block_seqno}/config/{params_string}")) + .await?; + let result = serde_json::from_value(result)?; + Ok(result) + } + + async fn get_account_transactions( + &self, + address: &MsgAddressInt, + lt: u64, + ) -> anyhow::Result { + let base64_address = pack_std_smc_addr(false, address, false)?; + let result = self + .connection + .send_get(&format!("account/{base64_address}/tx/{lt}/-")) + .await?; + let result = serde_json::from_value(result)?; + Ok(result) + } + + async fn send_message(&self, message_cell: Cell) -> anyhow::Result<()> { + let bytes = ton_types::serialize_toc(&message_cell)?; + let boc = base64::encode(bytes); + let body = serde_json::to_value(&MessageBoc { boc })?; + + self.connection.send_post(&body, "/send").await?; + Ok(()) + } +} + +#[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] +#[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] +impl Transport for TonTransport { + fn info(&self) -> TransportInfo { + TransportInfo { + max_transactions_per_fetch: 20, + reliable_behavior: ReliableBehavior::BlockWalking, + has_key_blocks: false, + } + } + + async fn send_message(&self, message: &Message) -> anyhow::Result<()> { + let cell = message.serialize()?; + self.send_message(cell).await + } + + async fn get_contract_state( + &self, + address: &MsgAddressInt, + ) -> anyhow::Result { + if let Some(known_state) = self.accounts_cache.get_account_state(address) { + if let Some(last_trans_lt) = known_state.last_known_trans_lt() { + let poll = self.poll_contract_state(address, last_trans_lt).await?; + return Ok(match poll.to_changed() { + Ok(contract) => { + self.accounts_cache.update_account_state(address, &contract); + contract + } + Err(timings) => { + let mut known_state = known_state.as_ref().clone(); + known_state.update_timings(timings); + known_state + } + }); + } + } + + let state = self.get_contract_state_ext(address).await?; + self.accounts_cache.update_account_state(address, &state); + Ok(state) + } + async fn get_library_cell(&self, _: &UInt256) -> anyhow::Result> { todo!() } async fn poll_contract_state( &self, - _: &MsgAddressInt, - _: u64, + address: &MsgAddressInt, + last_transaction_lt: u64, ) -> anyhow::Result { - todo!() + let state = self.get_contract_state_ext(address).await?; + match &state { + RawContractState::Exists(contract) => { + if contract.last_transaction_id.lt() <= last_transaction_lt { + return Ok(PollContractState::Unchanged { + timings: GenTimings::Unknown, + }); + } + self.accounts_cache.update_account_state(address, &state); + Ok(PollContractState::Exists(contract.clone())) + } + RawContractState::NotExists { timings } => Ok(PollContractState::NotExists { + timings: timings.clone(), + }), + } } async fn get_accounts_by_code_hash( @@ -241,40 +286,41 @@ impl Transport for TonTransport { count: u8, ) -> anyhow::Result> { const AT_MOST: usize = 20; - + let mut remaining = count; let mut transactions = Vec::with_capacity(count as usize); - + loop { let result = self.get_account_transactions(address, from_lt).await?; let len = result.transactions.len(); let to_process = if len > remaining as usize { - result.transactions.into_iter().take(remaining as usize).collect::>() + result + .transactions + .into_iter() + .take(remaining as usize) + .collect::>() } else { result.transactions }; - + for t in &to_process { transactions.push(RawTransaction { hash: t.hash()?, data: t.clone(), }); } - remaining = remaining.saturating_sub(len as u8); - + remaining = remaining.saturating_sub(len as u8); + if AT_MOST > len || remaining == 0 { break; } - + if let Some(last) = transactions.last() { if last.data.prev_trans_lt == 0 { break; } } } - - - Ok(transactions) } @@ -334,7 +380,7 @@ pub mod tests { let path = base .join(path) .map_err(|e| TonApiError::General(e.into()))?; - + let result = self .get(path) .header("ContentType", "application/json") @@ -373,7 +419,7 @@ pub mod tests { let transactions = transport .get_transactions(&address, 27668319000001, u8::MAX) .await?; - + let mut prev_tx_lt = transactions.first().unwrap().data.lt; for i in &transactions { assert_eq!(i.data.lt, prev_tx_lt); diff --git a/src/transport/ton/models.rs b/src/transport/ton/models.rs index f534a3217..0f42027e9 100644 --- a/src/transport/ton/models.rs +++ b/src/transport/ton/models.rs @@ -4,7 +4,6 @@ use nekoton_utils::{ }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use ton_block::VarUInteger7; use ton_types::{Cell, UInt256}; #[derive(Deserialize, Debug)] @@ -69,6 +68,7 @@ pub struct Transaction { #[serde(rename_all = "camelCase")] pub struct AccountStateResult { pub account: AccountInfo, + #[allow(unused)] pub block: BlockId, } @@ -142,6 +142,7 @@ pub struct LastTransaction { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct AccountChangedResult { + #[allow(unused)] pub changed: bool, pub block: BlockId, } @@ -149,6 +150,7 @@ pub struct AccountChangedResult { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ConfigResult { + #[allow(unused)] pub exist: bool, pub config: Option, } @@ -158,6 +160,7 @@ pub struct ConfigResult { pub struct ConfigInfo { #[serde(with = "serde_cell")] pub cell: Cell, + #[allow(unused)] #[serde(with = "serde_base64_address")] pub address: ton_block::MsgAddressInt, pub global_balance: AccountBalance, @@ -166,6 +169,7 @@ pub struct ConfigInfo { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct AccountTransactionsResult { + #[allow(unused)] pub blocks: Vec, #[serde(rename = "boc", with = "serde_transaction_array")] pub transactions: Vec, From 8377919e2e6e21dbd69c302a5ea93afa18a0111e Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Wed, 7 May 2025 10:55:18 +0200 Subject: [PATCH 7/9] Rework poll_contract_state for TON transport. Use check_account_changed method --- src/transport/ton/mod.rs | 140 ++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 32 deletions(-) diff --git a/src/transport/ton/mod.rs b/src/transport/ton/mod.rs index 9429ba5e7..56a22d050 100644 --- a/src/transport/ton/mod.rs +++ b/src/transport/ton/mod.rs @@ -69,18 +69,46 @@ impl TonTransport { Ok(result) } - async fn get_contract_state_ext( + async fn check_account_changed_ext( &self, address: &MsgAddressInt, - ) -> Result { + since_lt: u64, + ) -> anyhow::Result { let latest_block = self.get_latest_block().await?; - let state_opt = self - .get_account_state(latest_block.last.seqno, address) + let result = self + .check_account_changed(latest_block.last.seqno, address, since_lt) .await?; + let state = self + .get_contract_state_ext(latest_block.last.seqno, latest_block.now, address) + .await?; + + let state = match (result.changed, state) { + (false, RawContractState::Exists(_)) => PollContractState::Unchanged { + timings: GenTimings::Known { + gen_lt: 0, + gen_utime: latest_block.now, + }, + }, + (true, RawContractState::Exists(contract)) => PollContractState::Exists(contract), + (_, RawContractState::NotExists { timings }) => { + PollContractState::NotExists { timings } + } + }; + + Ok(state) + } + + async fn get_contract_state_ext( + &self, + block_seqno: u32, + block_utime: u32, + address: &MsgAddressInt, + ) -> Result { + let state_opt = self.get_account_state(block_seqno, address).await?; let timings = GenTimings::Known { gen_lt: 0, - gen_utime: latest_block.now, + gen_utime: block_utime, }; //TODO: how to get gen_lt let state = match state_opt { @@ -154,12 +182,22 @@ impl TonTransport { Ok(contract_state) } - // async fn check_account_changed(&self, block_seqno: u64, address: &MsgAddressInt, since_lt: u64) -> anyhow::Result { - // let base64_address = pack_std_smc_addr(false, address, false)?; - // let result = self.connection.send_get(&format!("block/{block_seqno}/{base64_address}/changed/{since_lt}")).await?; - // let result = serde_json::from_value(result)?; - // Ok(result) - // } + async fn check_account_changed( + &self, + block_seqno: u32, + address: &MsgAddressInt, + since_lt: u64, + ) -> anyhow::Result { + let base64_address = pack_std_smc_addr(false, address, false)?; + let result = self + .connection + .send_get(&format!( + "block/{block_seqno}/{base64_address}/changed/{since_lt}" + )) + .await?; + let result = serde_json::from_value(result)?; + Ok(result) + } async fn get_config(&self, block_seqno: u32, params: I) -> anyhow::Result where @@ -208,7 +246,7 @@ impl Transport for TonTransport { fn info(&self) -> TransportInfo { TransportInfo { max_transactions_per_fetch: 20, - reliable_behavior: ReliableBehavior::BlockWalking, + reliable_behavior: ReliableBehavior::IntensivePolling, has_key_blocks: false, } } @@ -239,7 +277,10 @@ impl Transport for TonTransport { } } - let state = self.get_contract_state_ext(address).await?; + let latest_block = self.get_latest_block().await?; + let state = self + .get_contract_state_ext(latest_block.last.seqno, latest_block.now, address) + .await?; self.accounts_cache.update_account_state(address, &state); Ok(state) } @@ -253,21 +294,8 @@ impl Transport for TonTransport { address: &MsgAddressInt, last_transaction_lt: u64, ) -> anyhow::Result { - let state = self.get_contract_state_ext(address).await?; - match &state { - RawContractState::Exists(contract) => { - if contract.last_transaction_id.lt() <= last_transaction_lt { - return Ok(PollContractState::Unchanged { - timings: GenTimings::Unknown, - }); - } - self.accounts_cache.update_account_state(address, &state); - Ok(PollContractState::Exists(contract.clone())) - } - RawContractState::NotExists { timings } => Ok(PollContractState::NotExists { - timings: timings.clone(), - }), - } + self.check_account_changed_ext(address, last_transaction_lt) + .await } async fn get_accounts_by_code_hash( @@ -361,15 +389,17 @@ impl Transport for TonTransport { #[cfg(test)] pub mod tests { + use crate::core::generic_contract::{GenericContract, GenericContractSubscriptionHandler}; + use crate::core::ContractSubscription; + use crate::external::{TonApiError, TonConnection}; + use crate::models::{ContractState, PendingTransaction, Transaction, TransactionsBatchInfo}; + use crate::transport::ton::TonTransport; + use crate::transport::Transport; use nekoton_utils::{unpack_std_smc_addr, SimpleClock}; use reqwest::Url; use serde_json::Value; use std::sync::Arc; - use crate::external::{TonApiError, TonConnection}; - use crate::transport::ton::TonTransport; - use crate::transport::Transport; - #[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] #[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] impl TonConnection for reqwest::Client { @@ -437,4 +467,50 @@ pub mod tests { println!("{:?}", config.get_fwd_prices(false)); Ok(()) } + + pub struct SimpleHandler; + impl GenericContractSubscriptionHandler for SimpleHandler { + fn on_message_sent( + &self, + pending_transaction: PendingTransaction, + transaction: Option, + ) { + println!("on_message_sent"); + } + + fn on_message_expired(&self, pending_transaction: PendingTransaction) { + println!("on_message_expired"); + } + + fn on_state_changed(&self, new_state: ContractState) { + println!("on_state_changed"); + } + + fn on_transactions_found( + &self, + transactions: Vec, + batch_info: TransactionsBatchInfo, + ) { + println!("on_transactions_found"); + } + } + + #[tokio::test] + async fn subscription_test() -> anyhow::Result<(), TonApiError> { + let client = reqwest::Client::new(); + let transport = TonTransport::new(Arc::new(client)); + let address = + unpack_std_smc_addr("EQCo6VT63H1vKJTiUo6W4M8RrTURCyk5MdbosuL5auEqpz-C", true)?; + let sub = GenericContract::subscribe( + Arc::new(SimpleClock), + Arc::new(transport), + address, + Arc::new(SimpleHandler), + true, + ) + .await?; + let state = sub.contract_state(); + println!("{:?}", state); + Ok(()) + } } From ed28a935149bc133d99681a0ce709996b13f88be Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Wed, 7 May 2025 14:44:46 +0200 Subject: [PATCH 8/9] change TonConnection result to String in favor of wasm --- Cargo.toml | 2 +- nekoton-abi/Cargo.toml | 2 +- nekoton-contracts/Cargo.toml | 2 +- nekoton-derive/Cargo.toml | 2 +- nekoton-jetton/Cargo.toml | 2 +- nekoton-transport/src/ton.rs | 35 +++++---- src/core/contract_subscription/mod.rs | 2 - src/external/mod.rs | 16 +--- src/transport/ton/mod.rs | 108 +++++++++++++++----------- src/transport/ton/models.rs | 1 + 10 files changed, 88 insertions(+), 84 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a925fe47..d1f4c1a72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ ed25519-dalek = { git = "https://github.com/broxus/ed25519-dalek.git", optional tiny-bip39 = { git = "https://github.com/broxus/tiny-bip39.git", default-features = false, optional = true } tiny-hderive = { git = "https://github.com/broxus/tiny-hderive.git", optional = true } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi"} ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_executor = { git = "https://github.com/broxus/ton-labs-executor.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-abi/Cargo.toml b/nekoton-abi/Cargo.toml index d804e6c08..70208fab6 100644 --- a/nekoton-abi/Cargo.toml +++ b/nekoton-abi/Cargo.toml @@ -25,7 +25,7 @@ thiserror = "1.0" ed25519-dalek = { git = "https://github.com/broxus/ed25519-dalek.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_executor = { git = "https://github.com/broxus/ton-labs-executor.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-contracts/Cargo.toml b/nekoton-contracts/Cargo.toml index 261e8fa85..35de2e683 100644 --- a/nekoton-contracts/Cargo.toml +++ b/nekoton-contracts/Cargo.toml @@ -17,7 +17,7 @@ thiserror = "1.0" ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } nekoton-abi = { path = "../nekoton-abi", features = ["derive"] } nekoton-jetton = { path = "../nekoton-jetton" } diff --git a/nekoton-derive/Cargo.toml b/nekoton-derive/Cargo.toml index f8016cf1b..1cef9cfd1 100644 --- a/nekoton-derive/Cargo.toml +++ b/nekoton-derive/Cargo.toml @@ -22,7 +22,7 @@ num-bigint = "0.4" num-traits = "0.2" trybuild = "1.0" -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } diff --git a/nekoton-jetton/Cargo.toml b/nekoton-jetton/Cargo.toml index 1240f488c..8bae8aa6f 100644 --- a/nekoton-jetton/Cargo.toml +++ b/nekoton-jetton/Cargo.toml @@ -17,7 +17,7 @@ sha2 = "0.10.8" ton_block = { git = "https://github.com/broxus/ton-labs-block.git" } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi", branch = "feature/new-abi" } +ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } nekoton-utils = { path = "../nekoton-utils" } num-bigint = "0.4" diff --git a/nekoton-transport/src/ton.rs b/nekoton-transport/src/ton.rs index d7fc190d9..596d95815 100644 --- a/nekoton-transport/src/ton.rs +++ b/nekoton-transport/src/ton.rs @@ -1,4 +1,3 @@ -use nekoton::external::TonApiError; use reqwest::{IntoUrl, Url}; use serde::Serialize; @@ -20,25 +19,28 @@ impl TonClient { &self.endpoint } - pub async fn send_get(&self, path: U) -> anyhow::Result { + pub async fn send_get(&self, path: U) -> anyhow::Result> { let path = path.into_url()?; let result = self .client .get(self.endpoint.clone().join(path.as_str())?) .header("ContentType", "application/json") .send() - .await? - .json() .await?; - Ok(result) + if matches!(result.status(), reqwest::StatusCode::NOT_FOUND) { + return Ok(None); + } + + let result = result.text().await?; + Ok(Some(result)) } pub async fn send_post( &self, body: R, path: U, - ) -> anyhow::Result { + ) -> anyhow::Result> { let path = path.into_url()?; let result = self .client @@ -46,26 +48,25 @@ impl TonClient { .body(serde_json::to_string(&body)?) .header("ContentType", "application/json") .send() - .await? - .json() .await?; - Ok(result) + if matches!(result.status(), reqwest::StatusCode::NOT_FOUND) { + return Ok(None); + } + + let result = result.text().await?; + Ok(Some(result)) } } #[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] #[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] impl nekoton::external::TonConnection for TonClient { - async fn send_get(&self, path: &str) -> Result { - self.send_get(path).await.map_err(TonApiError::from) + async fn send_get(&self, path: &str) -> anyhow::Result> { + self.send_get(path).await } - async fn send_post( - &self, - body: &serde_json::Value, - path: &str, - ) -> Result { - self.send_post(body, path).await.map_err(TonApiError::from) + async fn send_post(&self, body: &str, path: &str) -> anyhow::Result> { + self.send_post(body, path).await } } diff --git a/src/core/contract_subscription/mod.rs b/src/core/contract_subscription/mod.rs index 6a9aa14f2..c3c91541a 100644 --- a/src/core/contract_subscription/mod.rs +++ b/src/core/contract_subscription/mod.rs @@ -44,11 +44,9 @@ impl ContractSubscription { pending_transactions: Vec::new(), transactions_synced: false, }; - result.transactions_synced = !result .refresh_contract_state_impl(None, on_contract_state) .await?; - if !result.transactions_synced { if let Some(on_transactions_found) = on_transactions_found { // Preload transactions if `on_transactions_found` specified diff --git a/src/external/mod.rs b/src/external/mod.rs index ff3f96148..01429b3c9 100644 --- a/src/external/mod.rs +++ b/src/external/mod.rs @@ -69,20 +69,8 @@ pub trait ProtoConnection: Send + Sync { #[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] #[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] pub trait TonConnection: Send + Sync { - async fn send_get(&self, path: &str) -> Result; - async fn send_post( - &self, - body: &serde_json::Value, - path: &str, - ) -> Result; -} - -#[derive(thiserror::Error, Debug)] -pub enum TonApiError { - #[error("Requested entity not found")] - NotFound, - #[error("General error occurred {0}")] - General(#[from] anyhow::Error), + async fn send_get(&self, path: &str) -> Result>; + async fn send_post(&self, body: &str, path: &str) -> Result>; } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/transport/ton/mod.rs b/src/transport/ton/mod.rs index 56a22d050..7fcbe4589 100644 --- a/src/transport/ton/mod.rs +++ b/src/transport/ton/mod.rs @@ -11,7 +11,7 @@ use ton_executor::BlockchainConfig; use ton_types::{Cell, UInt256}; use super::{Transport, TransportInfo}; -use crate::external::{TonApiError, TonConnection}; +use crate::external::TonConnection; use crate::models::{NetworkCapabilities, ReliableBehavior}; use crate::transport::models::{ ExistingContract, PollContractState, RawContractState, RawTransaction, @@ -33,7 +33,11 @@ impl TonTransport { } async fn get_latest_block(&self) -> anyhow::Result { let result = self.connection.send_get("block/latest").await?; - let result = serde_json::from_value(result)?; + let result = match result { + Some(t) => t, + None => anyhow::bail!("Address or block not found"), + }; + let result = serde_json::from_str(&result)?; Ok(result) } @@ -54,18 +58,16 @@ impl TonTransport { block_seqno: u32, address: &MsgAddressInt, ) -> anyhow::Result> { - let base64_address = pack_std_smc_addr(false, address, false)?; + let base64_address = pack_std_smc_addr(true, address, false)?; let result = self .connection .send_get(&format!("block/{block_seqno}/{base64_address}")) .await; let result = match result { - Ok(result) if result.is_null() => return Ok(None), - Ok(result) => serde_json::from_value(result)?, - Err(TonApiError::NotFound) => return Ok(None), + Ok(Some(result)) => Some(serde_json::from_str(&result)?), + Ok(None) => return Ok(None), Err(err) => return Err(err.into()), }; - let result = serde_json::from_value(result)?; Ok(result) } @@ -103,7 +105,7 @@ impl TonTransport { block_seqno: u32, block_utime: u32, address: &MsgAddressInt, - ) -> Result { + ) -> anyhow::Result { let state_opt = self.get_account_state(block_seqno, address).await?; let timings = GenTimings::Known { @@ -188,14 +190,20 @@ impl TonTransport { address: &MsgAddressInt, since_lt: u64, ) -> anyhow::Result { - let base64_address = pack_std_smc_addr(false, address, false)?; + let base64_address = pack_std_smc_addr(true, address, false)?; let result = self .connection .send_get(&format!( "block/{block_seqno}/{base64_address}/changed/{since_lt}" )) .await?; - let result = serde_json::from_value(result)?; + + let result = match result { + Some(t) => t, + None => anyhow::bail!("Address or block not found"), + }; + + let result = serde_json::from_str(&result)?; Ok(result) } @@ -212,7 +220,13 @@ impl TonTransport { .connection .send_get(&format!("block/{block_seqno}/config/{params_string}")) .await?; - let result = serde_json::from_value(result)?; + + let result = match result { + Some(t) => t, + None => anyhow::bail!("Block not found"), + }; + + let result = serde_json::from_str(&result)?; Ok(result) } @@ -221,19 +235,24 @@ impl TonTransport { address: &MsgAddressInt, lt: u64, ) -> anyhow::Result { - let base64_address = pack_std_smc_addr(false, address, false)?; + let base64_address = pack_std_smc_addr(true, address, false)?; let result = self .connection .send_get(&format!("account/{base64_address}/tx/{lt}/-")) .await?; - let result = serde_json::from_value(result)?; + + let result = match result { + Some(t) => t, + None => anyhow::bail!("Address not found"), + }; + let result = serde_json::from_str(&result)?; Ok(result) } async fn send_message(&self, message_cell: Cell) -> anyhow::Result<()> { let bytes = ton_types::serialize_toc(&message_cell)?; let boc = base64::encode(bytes); - let body = serde_json::to_value(&MessageBoc { boc })?; + let body = serde_json::to_string(&MessageBoc { boc })?; self.connection.send_post(&body, "/send").await?; Ok(()) @@ -286,7 +305,7 @@ impl Transport for TonTransport { } async fn get_library_cell(&self, _: &UInt256) -> anyhow::Result> { - todo!() + Ok(None) } async fn poll_contract_state( @@ -389,48 +408,44 @@ impl Transport for TonTransport { #[cfg(test)] pub mod tests { + use nekoton_utils::{unpack_std_smc_addr, SimpleClock}; + use reqwest::Url; + use std::sync::Arc; + use crate::core::generic_contract::{GenericContract, GenericContractSubscriptionHandler}; - use crate::core::ContractSubscription; - use crate::external::{TonApiError, TonConnection}; + use crate::external::TonConnection; use crate::models::{ContractState, PendingTransaction, Transaction, TransactionsBatchInfo}; use crate::transport::ton::TonTransport; use crate::transport::Transport; - use nekoton_utils::{unpack_std_smc_addr, SimpleClock}; - use reqwest::Url; - use serde_json::Value; - use std::sync::Arc; #[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] #[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] impl TonConnection for reqwest::Client { - async fn send_get(&self, path: &str) -> Result { - let base = Url::parse("https://mainnet-v4.tonhubapi.com") - .map_err(|e| TonApiError::General(e.into()))?; - - let path = base - .join(path) - .map_err(|e| TonApiError::General(e.into()))?; + async fn send_get(&self, path: &str) -> anyhow::Result> { + let base = Url::parse("https://mainnet-v4.tonhubapi.com")?; + let path = base.join(path)?; let result = self .get(path) .header("ContentType", "application/json") .send() - .await - .map_err(|e| TonApiError::General(e.into()))? - .json() - .await - .map_err(|e| TonApiError::General(e.into()))?; + .await?; - Ok(result) + if matches!(result.status(), reqwest::StatusCode::NOT_FOUND) { + return Ok(None); + } + + let result = result.text().await?; + Ok(Some(result)) } - async fn send_post(&self, body: &Value, path: &str) -> Result { + async fn send_post(&self, _: &str, _: &str) -> anyhow::Result> { todo!() } } #[tokio::test] - async fn test_account_state() -> anyhow::Result<(), TonApiError> { + async fn test_account_state() -> anyhow::Result<()> { let client = reqwest::Client::new(); let address = unpack_std_smc_addr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N", true)?; @@ -441,7 +456,7 @@ pub mod tests { } #[tokio::test] - async fn test_get_transactions() -> anyhow::Result<(), TonApiError> { + async fn test_get_transactions() -> anyhow::Result<()> { let client = reqwest::Client::new(); let address = unpack_std_smc_addr("EQCo6VT63H1vKJTiUo6W4M8RrTURCyk5MdbosuL5auEqpz-C", true)?; @@ -459,7 +474,7 @@ pub mod tests { } #[tokio::test] - async fn test_get_config() -> anyhow::Result<(), TonApiError> { + async fn test_get_config() -> anyhow::Result<()> { let client = reqwest::Client::new(); let transport = TonTransport::new(Arc::new(client)); let config = transport.get_blockchain_config(&SimpleClock, true).await?; @@ -468,39 +483,40 @@ pub mod tests { Ok(()) } + #[derive(Copy, Clone, Debug)] pub struct SimpleHandler; impl GenericContractSubscriptionHandler for SimpleHandler { fn on_message_sent( &self, - pending_transaction: PendingTransaction, - transaction: Option, + _: PendingTransaction, + _: Option, ) { println!("on_message_sent"); } - fn on_message_expired(&self, pending_transaction: PendingTransaction) { + fn on_message_expired(&self, _: PendingTransaction) { println!("on_message_expired"); } - fn on_state_changed(&self, new_state: ContractState) { + fn on_state_changed(&self, _: ContractState) { println!("on_state_changed"); } fn on_transactions_found( &self, - transactions: Vec, - batch_info: TransactionsBatchInfo, + _: Vec, + _: TransactionsBatchInfo, ) { println!("on_transactions_found"); } } #[tokio::test] - async fn subscription_test() -> anyhow::Result<(), TonApiError> { + async fn subscription_test() -> anyhow::Result<()> { let client = reqwest::Client::new(); let transport = TonTransport::new(Arc::new(client)); let address = - unpack_std_smc_addr("EQCo6VT63H1vKJTiUo6W4M8RrTURCyk5MdbosuL5auEqpz-C", true)?; + unpack_std_smc_addr("UQBXJ9VgpXcBGYGDXYurquCsl3LV0bLmsIWuhv9VmIkxCm8q", true)?; let sub = GenericContract::subscribe( Arc::new(SimpleClock), Arc::new(transport), diff --git a/src/transport/ton/models.rs b/src/transport/ton/models.rs index 0f42027e9..12e48a09b 100644 --- a/src/transport/ton/models.rs +++ b/src/transport/ton/models.rs @@ -163,6 +163,7 @@ pub struct ConfigInfo { #[allow(unused)] #[serde(with = "serde_base64_address")] pub address: ton_block::MsgAddressInt, + #[allow(unused)] pub global_balance: AccountBalance, } From 0ff66e93f971aae84574e7dafa1793659fed499f Mon Sep 17 00:00:00 2001 From: Stanislav Eliseev Date: Fri, 16 May 2025 11:44:07 +0200 Subject: [PATCH 9/9] Fix some clippy warnings --- src/core/ton_wallet/wallet_v5r1.rs | 2 +- src/transport/jrpc/mod.rs | 2 +- src/transport/ton/mod.rs | 4 ++-- src/transport/ton/models.rs | 7 +++++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/core/ton_wallet/wallet_v5r1.rs b/src/core/ton_wallet/wallet_v5r1.rs index cc30bbe24..bc4f9259b 100644 --- a/src/core/ton_wallet/wallet_v5r1.rs +++ b/src/core/ton_wallet/wallet_v5r1.rs @@ -382,7 +382,7 @@ mod tests { if let AccountState::AccountActive { state_init } = state.storage.state() { let init_data = InitData::try_from(state_init.data().unwrap())?; - assert_eq!(init_data.is_signature_allowed, true); + assert!(init_data.is_signature_allowed); assert_eq!( init_data.public_key.to_hex_string(), "9107a65271437e1a982bb98404bd9a82c434f31ee30c621b6596702bb59bf0a0" diff --git a/src/transport/jrpc/mod.rs b/src/transport/jrpc/mod.rs index de20cb5f8..5684ebef3 100644 --- a/src/transport/jrpc/mod.rs +++ b/src/transport/jrpc/mod.rs @@ -398,7 +398,7 @@ mod tests { transport .get_transactions(&address, 21968513000000, 10) .await?; - + transport .get_transaction(&ton_types::UInt256::from_slice( &hex::decode("4a0a06bfbfaba4da8fcc7f5ad617fdee5344d954a1794e35618df2a4b349d15c") diff --git a/src/transport/ton/mod.rs b/src/transport/ton/mod.rs index 7fcbe4589..917edd205 100644 --- a/src/transport/ton/mod.rs +++ b/src/transport/ton/mod.rs @@ -66,7 +66,7 @@ impl TonTransport { let result = match result { Ok(Some(result)) => Some(serde_json::from_str(&result)?), Ok(None) => return Ok(None), - Err(err) => return Err(err.into()), + Err(err) => return Err(err), }; Ok(result) } @@ -399,7 +399,7 @@ impl Transport for TonTransport { .await?; if let Some(config) = config.config { let config = ton_block::ConfigParams::with_root(config.cell); - return Ok(BlockchainConfig::with_config(config, 0)?); + return BlockchainConfig::with_config(config, 0); } anyhow::bail!("Failed to get blockchain config") diff --git a/src/transport/ton/models.rs b/src/transport/ton/models.rs index 12e48a09b..fe9dfeee0 100644 --- a/src/transport/ton/models.rs +++ b/src/transport/ton/models.rs @@ -20,6 +20,7 @@ pub struct LatestBlock { #[serde(rename_all = "camelCase")] pub struct BlockId { #[serde(with = "serde_base64_uint256")] + #[allow(unused)] pub root_hash: UInt256, #[serde(with = "serde_base64_uint256")] pub file_hash: UInt256, @@ -35,8 +36,10 @@ pub struct BlockId { #[serde(rename_all = "camelCase")] pub struct Init { #[serde(with = "serde_base64_uint256")] + #[allow(unused)] pub root_hash: UInt256, #[serde(with = "serde_base64_uint256")] + #[allow(unused)] pub file_hash: UInt256, } @@ -57,10 +60,13 @@ pub struct ShardInfo { #[serde(rename_all = "camelCase")] pub struct Transaction { #[serde(with = "serde_base64_address")] + #[allow(unused)] pub account: ton_block::MsgAddressInt, #[serde(with = "serde_base64_uint256")] + #[allow(unused)] pub hash: UInt256, #[serde(with = "serde_u64")] + #[allow(unused)] pub lt: u64, } @@ -144,6 +150,7 @@ pub struct LastTransaction { pub struct AccountChangedResult { #[allow(unused)] pub changed: bool, + #[allow(unused)] pub block: BlockId, }