From 296210f54e7c85f340c4723b28cd303f12c7c4c7 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 26 Jan 2026 17:52:58 +0300 Subject: [PATCH 01/59] start refactor --- Cargo.lock | 45 +- Cargo.toml | 2 +- src/utils/mod.rs | 2 + src/utils/ton_wallet/ever_wallet.rs | 165 +++ src/utils/ton_wallet/highload_wallet_v2.rs | 384 +++++ src/utils/ton_wallet/mod.rs | 1255 +++++++++++++++++ src/utils/ton_wallet/multisig.rs | 744 ++++++++++ src/utils/ton_wallet/wallet_v3.rs | 358 +++++ src/utils/ton_wallet/wallet_v3v4.rs | 435 ++++++ src/utils/ton_wallet/wallet_v5r1.rs | 468 ++++++ .../wallets/code/BridgeMultisigWallet.tvc | Bin 0 -> 4368 bytes src/utils/wallets/code/Multisig2.tvc | Bin 0 -> 4073 bytes src/utils/wallets/code/Multisig2_1.tvc | Bin 0 -> 4286 bytes src/utils/wallets/code/SafeMultisigWallet.tvc | Bin 0 -> 4352 bytes .../wallets/code/SafeMultisigWallet24h.tvc | Bin 0 -> 4355 bytes .../wallets/code/SetcodeMultisigWallet.tvc | Bin 0 -> 6672 bytes .../wallets/code/SetcodeMultisigWallet24h.tvc | Bin 0 -> 6730 bytes src/utils/wallets/code/Surf.tvc | Bin 0 -> 4800 bytes src/utils/wallets/code/ever_wallet_code.boc | Bin 0 -> 267 bytes .../wallets/code/highload_wallet_v2_code.boc | Bin 0 -> 240 bytes src/utils/wallets/code/mod.rs | 34 + src/utils/wallets/code/wallet_v3_code.boc | Bin 0 -> 124 bytes src/utils/wallets/code/wallet_v3r1_code.boc | Bin 0 -> 109 bytes src/utils/wallets/code/wallet_v3r2_code.boc | Bin 0 -> 124 bytes src/utils/wallets/code/wallet_v4r1_code.boc | Bin 0 -> 769 bytes src/utils/wallets/code/wallet_v4r2_code.boc | Bin 0 -> 736 bytes src/utils/wallets/code/wallet_v5r1_code.boc | Bin 0 -> 657 bytes src/utils/wallets/ever_wallet.rs | 100 ++ src/utils/wallets/mod.rs | 5 + src/utils/wallets/multisig.rs | 192 +++ src/utils/wallets/multisig2.rs | 303 ++++ src/utils/wallets/notifications.rs | 41 + 32 files changed, 4525 insertions(+), 8 deletions(-) create mode 100644 src/utils/ton_wallet/ever_wallet.rs create mode 100644 src/utils/ton_wallet/highload_wallet_v2.rs create mode 100644 src/utils/ton_wallet/mod.rs create mode 100644 src/utils/ton_wallet/multisig.rs create mode 100644 src/utils/ton_wallet/wallet_v3.rs create mode 100644 src/utils/ton_wallet/wallet_v3v4.rs create mode 100644 src/utils/ton_wallet/wallet_v5r1.rs create mode 100644 src/utils/wallets/code/BridgeMultisigWallet.tvc create mode 100644 src/utils/wallets/code/Multisig2.tvc create mode 100644 src/utils/wallets/code/Multisig2_1.tvc create mode 100644 src/utils/wallets/code/SafeMultisigWallet.tvc create mode 100644 src/utils/wallets/code/SafeMultisigWallet24h.tvc create mode 100644 src/utils/wallets/code/SetcodeMultisigWallet.tvc create mode 100644 src/utils/wallets/code/SetcodeMultisigWallet24h.tvc create mode 100644 src/utils/wallets/code/Surf.tvc create mode 100644 src/utils/wallets/code/ever_wallet_code.boc create mode 100644 src/utils/wallets/code/highload_wallet_v2_code.boc create mode 100644 src/utils/wallets/code/mod.rs create mode 100644 src/utils/wallets/code/wallet_v3_code.boc create mode 100644 src/utils/wallets/code/wallet_v3r1_code.boc create mode 100644 src/utils/wallets/code/wallet_v3r2_code.boc create mode 100644 src/utils/wallets/code/wallet_v4r1_code.boc create mode 100644 src/utils/wallets/code/wallet_v4r2_code.boc create mode 100644 src/utils/wallets/code/wallet_v5r1_code.boc create mode 100644 src/utils/wallets/ever_wallet.rs create mode 100644 src/utils/wallets/mod.rs create mode 100644 src/utils/wallets/multisig.rs create mode 100644 src/utils/wallets/multisig2.rs create mode 100644 src/utils/wallets/notifications.rs diff --git a/Cargo.lock b/Cargo.lock index 506a970..b12972c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -857,6 +857,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -1120,6 +1121,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519 2.2.3", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" @@ -2462,7 +2477,7 @@ dependencies = [ "curve25519-dalek-ng", "downcast-rs", "dyn-clone", - "ed25519-dalek", + "ed25519-dalek 1.0.1", "erased-serde", "futures-util", "getrandom 0.2.16", @@ -2501,7 +2516,7 @@ source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc dependencies = [ "anyhow", "base64 0.13.1", - "ed25519-dalek", + "ed25519-dalek 1.0.1", "hex", "log", "nekoton-derive", @@ -2574,7 +2589,7 @@ dependencies = [ "anyhow", "base64 0.13.1", "chacha20poly1305", - "ed25519-dalek", + "ed25519-dalek 1.0.1", "hex", "hmac 0.11.0", "pbkdf2 0.12.2", @@ -4541,7 +4556,7 @@ dependencies = [ "base64 0.13.1", "byteorder", "ed25519 1.5.3", - "ed25519-dalek", + "ed25519-dalek 1.0.1", "hex", "num-bigint", "num-traits", @@ -4563,7 +4578,7 @@ dependencies = [ "base64 0.13.1", "crc", "ed25519 1.5.3", - "ed25519-dalek", + "ed25519-dalek 1.0.1", "hex", "log", "num", @@ -4618,7 +4633,7 @@ source = "git+https://github.com/broxus/ton-labs-vm.git#211bd88f46fa257ac4b93944 dependencies = [ "anyhow", "ed25519 1.5.3", - "ed25519-dalek", + "ed25519-dalek 1.0.1", "hex", "lazy_static", "log", @@ -4984,11 +4999,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2ccb37e250becb5c4b827536644c777c247b41ac6c9ddd30902ff1db29818a7" dependencies = [ "ahash 0.8.12", + "anyhow", "base64 0.22.1", "bitflags", "blake3", + "bytes", "crc32c", "dashmap 6.1.0", + "ed25519-dalek 2.2.0", "hex", "num-bigint", "num-traits", @@ -5001,10 +5019,23 @@ dependencies = [ "thiserror 2.0.17", "tl-proto", "tycho-crypto", + "tycho-types-abi-proc", "tycho-types-proc", "typeid", ] +[[package]] +name = "tycho-types-abi-proc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c813c08a03554252747f9e5e88485d9af4c30077394a1c3bb6d774ddca56b07" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "tycho-types-proc" version = "0.3.0" @@ -5117,7 +5148,7 @@ dependencies = [ "clap", "dashmap 5.5.3", "derive_more", - "ed25519-dalek", + "ed25519-dalek 1.0.1", "futures", "futures-util", "hex", diff --git a/Cargo.toml b/Cargo.toml index e45c495..a4c8ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ tower-http = { version = "0.6", features = ["trace", "cors", "limit", "set-heade tower-service = "0.3.3" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tycho-types = { version = "0.3.1", features = ["tycho", "stats", "serde"] } +tycho-types = { version = "0.3.1", features = ["tycho", "stats", "serde", "abi"] } uuid = { version = "1.1", features = ["v4", "serde"] } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 24f5898..a7affee 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -20,6 +20,8 @@ mod pending_messages_queue; mod shard_utils; mod token_wallet; mod tx_context; +mod ton_wallet; +mod wallets; pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs new file mode 100644 index 0000000..e769503 --- /dev/null +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -0,0 +1,165 @@ + +use anyhow::Result; +use ed25519_dalek::PublicKey; +use tycho_types::{abi::{AbiHeaderType, AbiVersion, Function, NamedAbiValue, UnsignedExternalMessage}, cell::{CellBuilder, HashBytes}, models::{Account, AccountState, IntAddr, IntMsgInfo, Message, MessageLayout, MsgInfo, StateInit, StdAddr}}; + +use crate::utils::ton_wallet::{Gift, TonWalletDetails, ever_wallet}; + + +pub fn prepare_deploy( + public_key: &PublicKey, + workchain: i8, + expire_at: u32, +) -> Result { + let state_init = prepare_state_init(public_key)?; + let hash = CellBuilder::build_from(&state_init)?.repr_hash(); + + let dst = StdAddr::new( + workchain, + hash.into(), + ); + + let headers = vec![AbiHeaderType::Time, AbiHeaderType::Expire, AbiHeaderType::Pubkey]; + let function = Function::builder(AbiVersion::V2_3, "sendTransactionRaw") + .with_headers(headers) + .with_inputs(vec![]) + .with_outputs(vec![]) + .with_id(0x169e3e11) + .build(); + + let unsigned_body = function.encode_external(&[]).with_expire_at(expire_at).build_input()?; + let mut unsigned_message = unsigned_body.with_dst(dst); + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) +} + +pub fn prepare_transfer( + public_key: &PublicKey, + current_state: &Account, + address: StdAddr, + gifts: Vec, + expire_at: u32, +) -> Result { + use crate::utils::wallets::ever_wallet; + + if gifts.len() > MAX_MESSAGES { + return Err(EverWalletError::TooManyGifts.into()); + } + + let mut gifts = gifts.into_iter(); + let body = match (gifts.len(), gifts.next()) { + (1, Some(gift)) if gift.state_init.is_none() => { + let function = ever_wallet::send_transaction(); + function.encode_external(&[ + NamedAbiValue::from(("destination", gift.destination)), + NamedAbiValue::from(("amount", gift.amount.into())), + NamedAbiValue::from(("bounce", gift.bounce)), + NamedAbiValue::from(("flags", gift.flags)), + NamedAbiValue::from(("body", gift.body.unwrap_or_default().into_cell())), + ])? + } + (len, gift) => { + let function = match len { + 0 => ever_wallet::send_transaction_raw_0(), + 1 => ever_wallet::send_transaction_raw_1(), + 2 => ever_wallet::send_transaction_raw_2(), + 3 => ever_wallet::send_transaction_raw_3(), + _ => ever_wallet::send_transaction_raw_4(), + }; + + let mut tokens = Vec::with_capacity(len * 2); + for gift in gift.into_iter().chain(gifts) { + let internal_message = Message { + info: MsgInfo::Int(IntMsgInfo { + ihr_disabled: true, + bounce: gift.bounce, + dst: gift.destination, + value: gift.amount.into(), + ..Default::default() + }), + init: gift.state_init, + body: gift.body.unwrap_or(Default::default()).as_slice()?, + layout: None, + }; + + tokens.push(NamedAbiValue::from(("flags", gift.flags.token_value()))); + tokens.push(NamedAbiValue::from(( + "message", + CellBuilder::build_from(internal_message.borrow())? + ))); + } + function.encode_external(&tokens)? + } + }; + + + let unsigned_body = function.encode_external(&[]).with_expire_at(expire_at).build_input()?; + let mut unsigned_message = unsigned_body.with_dst(address); + + match ¤t_state.state { + AccountState::Active { .. } => {} + AccountState::Frozen { .. } => { + return Err(EverWalletError::AccountIsFrozen.into()) + } + AccountState::Uninit => { + unsigned_message.set_state_init(Some(prepare_state_init(public_key)?)); + } + }; + + Ok(unsigned_message) +} + +pub static CODE_HASH: &[u8; 32] = &[ + 0x3b, 0xa6, 0x52, 0x8a, 0xb2, 0x69, 0x4c, 0x11, 0x81, 0x80, 0xaa, 0x3b, 0xd1, 0x0d, 0xd1, 0x9f, + 0xf4, 0x00, 0xb9, 0x09, 0xab, 0x4d, 0xcf, 0x58, 0xfc, 0x69, 0x92, 0x5b, 0x2c, 0x7b, 0x12, 0xa6, +]; + +pub fn is_ever_wallet(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH +} + +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> IntAddr { + let hash = prepare_state_init(public_key) + .and_then(|state| state.hash()) + .trust_me(); + IntAddr::Std(StdAddr::new( + workchain_id, + hash.into(), + )) +} + +pub fn prepare_state_init(public_key: &PublicKey) -> Result { + let mut builder = CellBuilder::new(); + builder.store_u256(public_key.as_bytes())?; + builder.store_u64(0)?; + + let data = builder.build()?; + + Ok(StateInit { + code: Some(ever_wallet()), + data: Some(data), + ..Default::default() + }) +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 EVER + max_messages: MAX_MESSAGES, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +const MAX_MESSAGES: usize = 4; + +#[derive(thiserror::Error, Debug)] +enum EverWalletError { + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, +} diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs new file mode 100644 index 0000000..2966fb0 --- /dev/null +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -0,0 +1,384 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::PublicKey; +use ton_block::{MsgAddrStd, MsgAddressInt, Serializable}; +use ton_types::{BuilderData, Cell, HashmapE, HashmapType, IBitstring, SliceData, UInt256}; + +use nekoton_utils::*; + +use super::{Gift, TonWalletDetails, TransferAction}; +use crate::core::models::{Expiration, ExpireAt}; +use crate::crypto::{SignedMessage, UnsignedMessage}; + +pub fn prepare_deploy( + clock: &dyn Clock, + public_key: &PublicKey, + workchain: i8, + expiration: Expiration, +) -> Result> { + let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); + let dst = compute_contract_address(public_key, workchain); + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst, + ..Default::default() + }); + + message.set_state_init(init_data.make_state_init()?); + + let expire_at = ExpireAt::new(clock, expiration); + let (hash, payload) = init_data.make_deploy_payload(expire_at.timestamp)?; + + Ok(Box::new(UnsignedHighloadWalletV2Message { + init_data, + gifts: Vec::new(), + payload, + message, + expire_at, + hash, + })) +} + +pub fn prepare_state_init(public_key: &PublicKey) -> Result { + let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); + init_data.make_state_init() +} + +pub fn prepare_transfer( + clock: &dyn Clock, + public_key: &PublicKey, + current_state: &ton_block::AccountStuff, + gifts: Vec, + expiration: Expiration, +) -> Result { + if gifts.len() > DETAILS.max_messages { + return Err(HighloadWalletV2Error::TooManyGifts.into()); + } + + let (init_data, with_state_init) = match ¤t_state.storage.state { + ton_block::AccountState::AccountActive { state_init, .. } => match &state_init.data { + Some(data) => (InitData::try_from(data)?, false), + None => return Err(HighloadWalletV2Error::InvalidInitData.into()), + }, + ton_block::AccountState::AccountFrozen { .. } => { + return Err(HighloadWalletV2Error::AccountIsFrozen.into()) + } + ton_block::AccountState::AccountUninit => ( + InitData::from_key(public_key).with_wallet_id(WALLET_ID), + true, + ), + }; + + if init_data.data.len()? >= 500_usize { + return Err(HighloadWalletV2Error::InitDataTooLarge.into()); + } + + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst: current_state.addr.clone(), + ..Default::default() + }); + + if with_state_init { + message.set_state_init(init_data.make_state_init()?); + } + + let expire_at = ExpireAt::new(clock, expiration); + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at.timestamp)?; + + Ok(TransferAction::Sign(Box::new( + UnsignedHighloadWalletV2Message { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }, + ))) +} + +#[derive(Clone)] +struct UnsignedHighloadWalletV2Message { + init_data: InitData, + gifts: Vec, + payload: BuilderData, + hash: UInt256, + expire_at: ExpireAt, + message: ton_block::Message, +} + +impl UnsignedMessage for UnsignedHighloadWalletV2Message { + fn refresh_timeout(&mut self, clock: &dyn Clock) { + if !self.expire_at.refresh(clock) { + return; + } + + let expire_at = self.expire_at(); + + let (hash, payload) = if self.gifts.is_empty() { + self.init_data.make_deploy_payload(expire_at) + } else { + self.init_data + .make_transfer_payload(self.gifts.clone(), expire_at) + } + .trust_me(); + + self.hash = hash; + self.payload = payload; + } + + fn expire_at(&self) -> u32 { + self.expire_at.timestamp + } + + fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, signature.len() * 8)?; + + let mut message = self.message.clone(); + message.set_body(ton_types::SliceData::load_builder(payload)?); + + Ok(SignedMessage { + message, + expire_at: self.expire_at(), + }) + } + + fn sign_with_pruned_payload( + &self, + signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH], + prune_after_depth: u16, + ) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, signature.len() * 8)?; + let body = payload.into_cell()?; + + let mut message = self.message.clone(); + message.set_body(prune_deep_cells(&body, prune_after_depth)?); + + Ok(SignedMessage { + message, + expire_at: self.expire_at(), + }) + } +} + +pub static CODE_HASH: &[u8; 32] = &[ + 0x0b, 0x3a, 0x88, 0x7a, 0xea, 0xcd, 0x2a, 0x7d, 0x40, 0xbb, 0x55, 0x50, 0xbc, 0x92, 0x53, 0x15, + 0x6a, 0x02, 0x90, 0x65, 0xae, 0xfb, 0x6d, 0x6b, 0x58, 0x37, 0x35, 0xd5, 0x8d, 0xa9, 0xd5, 0xbe, +]; + +pub fn is_highload_wallet_v2(code_hash: &UInt256) -> bool { + code_hash.as_slice() == CODE_HASH +} + +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> MsgAddressInt { + InitData::from_key(public_key) + .with_wallet_id(WALLET_ID) + .compute_addr(workchain_id) + .trust_me() +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 TON + max_messages: 250, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +/// `HighloadWalletV2` init data +#[derive(Clone)] +pub struct InitData { + pub wallet_id: u32, + pub last_cleaned: u64, + pub public_key: UInt256, + pub data: HashmapE, +} + +impl InitData { + pub fn public_key(&self) -> &[u8; 32] { + self.public_key.as_slice() + } + + pub fn from_key(key: &PublicKey) -> Self { + Self { + wallet_id: 0, + last_cleaned: 0, + public_key: key.as_bytes().into(), + data: HashmapE::default(), + } + } + + pub fn with_wallet_id(mut self, id: u32) -> Self { + self.wallet_id = id; + self + } + + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let init_state = self.make_state_init()?.serialize()?; + let hash = init_state.repr_hash(); + Ok(MsgAddressInt::AddrStd(MsgAddrStd { + anycast: None, + workchain_id, + address: hash.into(), + })) + } + + pub fn make_state_init(&self) -> Result { + Ok(ton_block::StateInit { + code: Some(nekoton_contracts::wallets::code::highload_wallet_v2()), + data: Some(self.serialize()?), + ..Default::default() + }) + } + + pub fn serialize(&self) -> Result { + let mut data = BuilderData::new(); + data.append_u32(self.wallet_id)? + .append_u64(self.last_cleaned)? + .append_raw(self.public_key.as_slice(), 256)?; + self.data.write_hashmap_data(&mut data)?; + data.into_cell() + } + + pub fn make_deploy_payload(&self, expire_at: u32) -> Result<(UInt256, BuilderData)> { + let mut payload = BuilderData::new(); + payload + .append_u32(self.wallet_id)? + .append_u32(expire_at)? + .append_u32(u32::MAX)? + .append_bit_zero()?; + + let hash = payload.clone().into_cell()?.repr_hash(); + + Ok((hash, payload)) + } + + pub fn make_transfer_payload( + &self, + gifts: impl IntoIterator, + expire_at: u32, + ) -> Result<(UInt256, BuilderData)> { + // Prepare messages array + let mut messages = ton_types::HashmapE::with_bit_len(16); + for (i, gift) in gifts.into_iter().enumerate() { + let mut internal_message = + ton_block::Message::with_int_header(ton_block::InternalMessageHeader { + ihr_disabled: true, + bounce: gift.bounce, + dst: gift.destination, + value: ton_block::CurrencyCollection::from_grams(ton_block::Grams::new( + gift.amount, + )?), + ..Default::default() + }); + + if let Some(body) = gift.body { + internal_message.set_body(body); + } + + if let Some(state_init) = gift.state_init { + internal_message.set_state_init(state_init); + } + + let mut item = BuilderData::new(); + item.append_u8(gift.flags)? + .checked_append_reference(internal_message.serialize()?)?; + + let key = (i as u16) + .serialize() + .and_then(SliceData::load_cell) + .trust_me(); + + messages.set_builder(key, &item)?; + } + + let messages = messages.serialize()?; + let messages_hash = messages.repr_hash(); + + // Build payload + let mut payload = BuilderData::new(); + payload + .append_u32(self.wallet_id)? + .append_u32(expire_at)? + .append_raw(&messages_hash.as_slice()[28..32], 32)? + .append_builder(&messages.into())?; + + let hash = payload.clone().into_cell()?.repr_hash(); + + Ok((hash, payload)) + } +} + +impl TryFrom<&Cell> for InitData { + type Error = anyhow::Error; + + fn try_from(data: &Cell) -> Result { + let mut cs = SliceData::load_cell_ref(data)?; + + Ok(Self { + wallet_id: cs.get_next_u32()?, + last_cleaned: cs.get_next_u64()?, + public_key: UInt256::from_be_bytes(&cs.get_next_bytes(32)?), + data: { + let mut map = HashmapE::with_bit_len(64); + map.read_hashmap_data(&mut cs)?; + map + }, + }) + } +} + +const WALLET_ID: u32 = 0x00000000; + +#[derive(thiserror::Error, Debug)] +enum HighloadWalletV2Error { + #[error("Invalid init data")] + InvalidInitData, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, + #[error("Account init data is too large")] + InitDataTooLarge, +} + +#[cfg(test)] +pub mod tests { + use crate::core::ton_wallet::highload_wallet_v2::InitData; + use anyhow::Result; + use ton_block::Deserializable; + use ton_types::HashmapType; + + #[tokio::test] + async fn check_state() -> Result<()> { + let data = "te6ccgICCBAAAQAAOOsAAAIBmggHAAEBWQAAAABij1ipvO9y33lafOQTY2Zjcpu/tM7FomMbSFDp4+8Ei8aUcpwDouTBwAACAgiLsUesAl4AAwIBYgA1AAQCAnAAFAAFAgFIABEABgIBIAAKAAcCAW4ACQAIAAm3P2/0YAAJty45OOACASAADAALAAm7gCmMCAIBIAAQAA0CASAADwAOAAm3SNaR4AAJt2IEcmAACbn59J8wAgEgABMAEgAJvH0s0sQACb0fFP0kAgEgACYAFQIBIAAfABYCASAAGgAXAgEgABkAGAAJulOvR9gACbr90p9IAgFYABwAGwAJuaPhPtACASAAHgAdAAm29FO6oAAJt4e9WeACASAAJQAgAgEgACQAIQIBIAAjACIACbg4HOPQAAm5N8GT0AAJumQHj3gACbxuQUbMAgEgACwAJwIBIAApACgACbxoeVqsAgEgACsAKgAJu3kh5AgACbuT7z2oAgEgADIALQIBIAAxAC4CAUgAMAAvAAm3D/yF4AAJttuTf6AACboR+FvYAgEgADQAMwAJu/xtoCgACbswljEoAgEgAVEANgIBIADGADcCASAAfQA4AgEgAFwAOQIBIABLADoCASAASgA7AgEgAD8APAIBIAA+AD0ACbv67XUoAAm728BYuAIBIABDAEACASAAQgBBAAm4XHbOEAAJuEX0C9ACASAASQBEAgEgAEgARQIBSABHAEYACLJFqpcACLIZDQgACbb/vM5gAAm5tBZx8AAJv6qZvYYCASAAVQBMAgEgAFIATQIBWABRAE4CAWYAUABPAAizva6wAAizJMbvAAm4yrfY0AIBIABUAFMACbv97cQIAAm7rFAKCAIBIABXAFYACbwdUD3MAgEgAFkAWAAJuqMTi4gCA5B3AFsAWgAHqXo6sAAHqTSr0AIBIABsAF0CASAAYwBeAgEgAGIAXwICcQBhAGAACbU816HAAAm0goAhwAAJvMcETxwCASAAawBkAgEgAGoAZQIBIABnAGYACbjQvmEQAgJxAGkAaAAHsBsUkQAHsGq37wAJu6lVp4gACb01zX/cAgEgAHYAbQIBIABzAG4CASAAcABvAAm6QVAyuAIBSAByAHEACbd6cIWgAAm2kdMmYAIBIAB1AHQACbotzlT4AAm66wR8CAIBIAB8AHcCASAAewB4AgEgAHoA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trax3CACASADAgMBAAm7WoeimAIBIAMEAwMACbm8yrWwAAm5HOVjEAIBSAMHAwYACbpktfloAAm7LsDquAIBIAMUAwkCAVgDCwMKAAm6hR6B6AIBIAMPAwwCASADDgMNAAm2f4hroAAJtllCn+ACASADEQMQAAm2Yx6y4AIBIAMTAxIACbSflolAAAm0QerswAIBIAMYAxUCASADFwMWAAm7tkoeSAAJuknHOugACb2TEThEAgEgAzkDGgIBIAMoAxsCASADIQMcAgEgAyADHQIBIAMfAx4ACbuaE9GIAAm76TwyuAAJvTF5SlwCASADJQMiAgFmAyQDIwAJt+MXWCAACbfdIXagAgEgAycDJgAJu+jFS4gACbqhfTLYAgEgAzIDKQIBIAMvAyoCAVgDLgMrAgFYAy0DLAAJtOPh2kAACbTQbPDAAAm5hunfsAIBIAMxAzAACbqF6XDoAAm7UeL9mAIBIAM2AzMCASADNQM0AAm7xZq5KAAJuxrvMUgCASADOAM3AAm7OY2quAAJumXrW7gCASADSwM6AgEgA0YDOwIBIANBAzwCASADPgM9AAm7wUzueAIBIANAAz8ACbmxiDdwAAm5KDyKEAIBIANDA0IACbupeOFIAgEgA0UDRAAJuNLLTRAACbir+oZQAgEgA0oDRwIBIANJA0gACbonQw4YAAm6iJr9KAAJvAwRrswCASADVQNMAgEgA1QDTQIBIANPA04ACbo2gb2IAgFqA1EDUAAJtAxQcEACAVgDUwNSAAex5r87AAex7i7vAAm8a0P6FAIBIANXA1YACb0WUg0sAgEgA1kDWAAJupoiEwgACbsZNqNoAgEgA94DWwIBIAObA1wCASADeANdAgEgA3EDXgIBIANmA18CASADYwNgAgEgA2IDYQAJu3UoScgACbtyxfUoAgEgA2UDZAAJu7/lGDgACbugeIXoAgEgA24DZwIBIANrA2gCASADagNpAAm5vLA10AAJubPahdACASADbQNsAAm5tK+QUAAJuEioGPACAUgDcANvAAm5CURc8AAJuCyslfACAUgDcwNyAAm8IYMwTAIBSAN3A3QCASADdgN1AAm3DbyIoAAJti1cfKAACbgyOlvwAgEgA4oDeQIBIAOBA3oCASADgAN7AgEgA30DfAAJu0UfkFgCASADfwN+AAm4pvk5sAAJua8OZHAA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"; + + let bytes = base64::decode(data).expect("cant decode data"); + let cell = + ton_types::deserialize_tree_of_cells(&mut bytes.as_slice()).expect("deser failed"); + let account = ton_block::AccountState::construct_from_cell(cell)?; + + let data = match account { + ton_block::AccountState::AccountActive { state_init } => state_init.data.unwrap(), + _ => anyhow::bail!("ACCOUNT NOT ACTIVE"), + }; + + let init_data = InitData::try_from(&data).expect("init data failed"); + + println!("{:?}", init_data.data.len().unwrap()); + + Ok(()) + } +} diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs new file mode 100644 index 0000000..849f16f --- /dev/null +++ b/src/utils/ton_wallet/mod.rs @@ -0,0 +1,1255 @@ +use std::borrow::Cow; +use std::convert::TryFrom; +use std::num::NonZeroU8; +use std::str::FromStr; +use std::sync::Arc; + +use anyhow::Result; +use ed25519_dalek::PublicKey; +use serde::{Deserialize, Serialize}; +use ton_block::MsgAddressInt; +use ton_types::{BuilderData, SliceData, UInt256}; + +use nekoton_abi::*; +use nekoton_utils::*; +use tycho_types::cell::Cell; +use tycho_types::models::{IntAddr, StateInit, StdAddr}; + +pub use self::multisig::MultisigType; +use super::models::{ + ContractState, Expiration, MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate, + PendingTransaction, Transaction, TransactionAdditionalInfo, TransactionWithData, + TransactionsBatchInfo, +}; +use super::{ContractSubscription, PollingMethod}; +use crate::core::parsing::*; +use crate::core::InternalMessage; +use crate::crypto::UnsignedMessage; +use crate::models::ExpireAt; +use crate::transport::models::{ExistingContract, RawContractState, RawTransaction}; +use crate::transport::Transport; + +pub mod ever_wallet; +pub mod highload_wallet_v2; +pub mod multisig; +pub mod wallet_v3; +pub mod wallet_v3v4; +pub mod wallet_v5r1; + +pub const DEFAULT_WORKCHAIN: i8 = 0; + +pub struct TonWallet { + clock: Arc, + public_key: PublicKey, + wallet_type: WalletType, + contract_subscription: ContractSubscription, + handler: Arc, + wallet_data: WalletData, +} + +impl TonWallet { + pub async fn subscribe( + clock: Arc, + transport: Arc, + workchain: i8, + public_key: PublicKey, + wallet_type: WalletType, + handler: Arc, + ) -> Result { + let address = compute_address(&public_key, wallet_type, workchain); + + let mut wallet_data = WalletData::default(); + + let contract_subscription = ContractSubscription::subscribe( + clock.clone(), + transport, + address, + &mut make_contract_state_handler( + clock.as_ref(), + handler.as_ref(), + &public_key, + wallet_type, + &mut wallet_data, + ), + Some(&mut make_transactions_handler( + handler.as_ref(), + wallet_type, + )), + ) + .await?; + + Ok(Self { + clock, + public_key, + wallet_type, + contract_subscription, + handler, + wallet_data, + }) + } + + pub async fn subscribe_by_address( + clock: Arc, + transport: Arc, + address: MsgAddressInt, + handler: Arc, + ) -> Result { + let (public_key, wallet_type) = match transport.get_contract_state(&address).await? { + RawContractState::Exists(contract) => extract_wallet_init_data(&contract)?, + RawContractState::NotExists { .. } => { + return Err(TonWalletError::AccountNotExists.into()) + } + }; + + let mut wallet_data = WalletData::default(); + + let contract_subscription = ContractSubscription::subscribe( + clock.clone(), + transport, + address, + &mut make_contract_state_handler( + clock.as_ref(), + handler.as_ref(), + &public_key, + wallet_type, + &mut wallet_data, + ), + Some(&mut make_transactions_handler( + handler.as_ref(), + wallet_type, + )), + ) + .await?; + + Ok(Self { + clock, + public_key, + wallet_type, + contract_subscription, + handler, + wallet_data, + }) + } + + pub async fn subscribe_by_existing( + clock: Arc, + transport: Arc, + existing_wallet: ExistingWalletInfo, + handler: Arc, + ) -> Result { + let mut wallet_data = WalletData::default(); + + let contract_subscription = ContractSubscription::subscribe( + clock.clone(), + transport, + existing_wallet.address, + &mut make_contract_state_handler( + clock.as_ref(), + handler.as_ref(), + &existing_wallet.public_key, + existing_wallet.wallet_type, + &mut wallet_data, + ), + Some(&mut make_transactions_handler( + handler.as_ref(), + existing_wallet.wallet_type, + )), + ) + .await?; + + Ok(Self { + clock, + public_key: existing_wallet.public_key, + wallet_type: existing_wallet.wallet_type, + contract_subscription, + handler, + wallet_data, + }) + } + + pub fn make_state_init(&self) -> Result { + match self.wallet_type { + WalletType::Multisig(multisig_type) => Ok(multisig::prepare_state_init( + &self.public_key, + multisig_type, + )), + WalletType::WalletV3 => wallet_v3::prepare_state_init(&self.public_key), + WalletType::WalletV3R1 => { + wallet_v3v4::prepare_state_init(&self.public_key, wallet_v3v4::WalletVersion::V3R1) + } + WalletType::WalletV3R2 => { + wallet_v3v4::prepare_state_init(&self.public_key, wallet_v3v4::WalletVersion::V3R2) + } + WalletType::WalletV4R1 => { + wallet_v3v4::prepare_state_init(&self.public_key, wallet_v3v4::WalletVersion::V4R1) + } + WalletType::WalletV4R2 => { + wallet_v3v4::prepare_state_init(&self.public_key, wallet_v3v4::WalletVersion::V4R2) + } + WalletType::WalletV5R1 => wallet_v5r1::prepare_state_init(&self.public_key), + WalletType::EverWallet => ever_wallet::prepare_state_init(&self.public_key), + WalletType::HighloadWalletV2 => { + highload_wallet_v2::prepare_state_init(&self.public_key) + } + } + } + + pub fn contract_subscription(&self) -> &ContractSubscription { + &self.contract_subscription + } + + pub fn workchain(&self) -> i8 { + self.contract_subscription.address().workchain_id() as i8 + } + + pub fn address(&self) -> &MsgAddressInt { + self.contract_subscription.address() + } + + pub fn public_key(&self) -> &PublicKey { + &self.public_key + } + + pub fn wallet_type(&self) -> WalletType { + self.wallet_type + } + + pub fn contract_state(&self) -> &ContractState { + self.contract_subscription.contract_state() + } + + pub fn pending_transactions(&self) -> &[PendingTransaction] { + self.contract_subscription.pending_transactions() + } + + pub fn polling_method(&self) -> PollingMethod { + self.contract_subscription.polling_method() + } + + pub fn details(&self) -> TonWalletDetails { + self.wallet_data + .details + .unwrap_or_else(|| self.wallet_type.details()) + } + + pub fn get_unconfirmed_transactions(&self) -> &[MultisigPendingTransaction] { + &self.wallet_data.unconfirmed_transactions + } + + pub fn get_unconfirmed_updates(&self) -> &[MultisigPendingUpdate] { + &self.wallet_data.unconfirmed_updates + } + + pub fn get_custodians(&self) -> &Option> { + &self.wallet_data.custodians + } + + pub fn prepare_deploy(&self, expiration: Expiration) -> Result> { + match self.wallet_type { + WalletType::Multisig(multisig_type) => multisig::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + multisig_type, + self.workchain(), + expiration, + multisig::DeployParams::single_custodian(&self.public_key), + ), + WalletType::WalletV3 => wallet_v3::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + self.workchain(), + expiration, + ), + WalletType::WalletV3R1 => wallet_v3v4::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + self.workchain(), + expiration, + wallet_v3v4::WalletVersion::V3R1, + ), + WalletType::WalletV3R2 => wallet_v3v4::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + self.workchain(), + expiration, + wallet_v3v4::WalletVersion::V3R2, + ), + WalletType::WalletV4R1 => wallet_v3v4::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + self.workchain(), + expiration, + wallet_v3v4::WalletVersion::V4R1, + ), + WalletType::WalletV4R2 => wallet_v3v4::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + self.workchain(), + expiration, + wallet_v3v4::WalletVersion::V4R2, + ), + WalletType::WalletV5R1 => wallet_v5r1::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + self.workchain(), + expiration, + ), + WalletType::EverWallet => ever_wallet::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + self.workchain(), + expiration, + ), + WalletType::HighloadWalletV2 => highload_wallet_v2::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + self.workchain(), + expiration, + ), + } + } + + pub fn prepare_deploy_with_multiple_owners( + &self, + expiration: Expiration, + custodians: &[PublicKey], + req_confirms: u8, + expiration_time: Option, + ) -> Result> { + match self.wallet_type { + WalletType::Multisig(multisig_type) => multisig::prepare_deploy( + self.clock.as_ref(), + &self.public_key, + multisig_type, + self.workchain(), + expiration, + multisig::DeployParams { + owners: custodians, + req_confirms, + expiration_time, + }, + ), + // Non-multisig wallets doesn't support multiple owners + _ => Err(TonWalletError::InvalidContractType.into()), + } + } + + pub fn make_unsigned_wallet_v5_transfer_payload( + &self, + current_state: &ton_block::AccountStuff, + public_key: &PublicKey, + gifts: Vec, + expiration: Expiration, + is_internal_flow: bool, + ) -> Result<(UInt256, BuilderData)> { + if !matches!(self.wallet_type, WalletType::WalletV5R1) { + return Err(TonWalletError::InvalidContractType.into()); + } + let (init_data, _) = wallet_v5r1::get_init_data(current_state.storage.state(), public_key)?; + + let expire_at = ExpireAt::new(self.clock.as_ref(), expiration); + let (hash, builder_data) = + init_data.make_transfer_payload(gifts, expire_at.timestamp, is_internal_flow)?; + Ok((hash, builder_data)) + } + + pub fn make_unsigned_new_wallet_v5_transfer_payload( + &self, + state_init: &ton_block::StateInit, + gifts: Vec, + expiration: Expiration, + is_internal_flow: bool, + ) -> Result<(UInt256, BuilderData)> { + if !matches!(self.wallet_type, WalletType::WalletV5R1) { + return Err(TonWalletError::InvalidContractType.into()); + } + let init_data = wallet_v5r1::get_init_data_from_state_init(state_init)?; + + let expire_at = ExpireAt::new(self.clock.as_ref(), expiration); + let (hash, builder_data) = + init_data.make_transfer_payload(gifts, expire_at.timestamp, is_internal_flow)?; + Ok((hash, builder_data)) + } + + pub fn get_wallet_v5_seqno( + &self, + current_state: &ton_block::AccountStuff, + public_key: &PublicKey, + ) -> Result { + if !matches!(self.wallet_type, WalletType::WalletV5R1) { + return Err(TonWalletError::InvalidContractType.into()); + } + + let (init_data, _) = wallet_v5r1::get_init_data(current_state.storage.state(), public_key)?; + Ok(init_data.seqno) + } + + pub fn prepare_transfer( + &mut self, + current_state: &ton_block::AccountStuff, + public_key: &PublicKey, + gifts: Vec, + expiration: Expiration, + ) -> Result { + match self.wallet_type { + WalletType::Multisig(multisig_type) => { + anyhow::ensure!( + gifts.len() == 1, + "Multiple outgoing messages are not supported by multisig contract" + ); + let gift = gifts.into_iter().next().unwrap(); + + match ¤t_state.storage.state { + ton_block::AccountState::AccountFrozen { .. } => { + return Err(TonWalletError::AccountIsFrozen.into()) + } + ton_block::AccountState::AccountUninit => { + return Ok(TransferAction::DeployFirst) + } + ton_block::AccountState::AccountActive { .. } => {} + }; + + self.wallet_data.update( + self.clock.as_ref(), + &self.public_key, + self.wallet_type, + current_state, + self.handler.as_ref(), + )?; + + let has_multiple_owners = match &self.wallet_data.custodians { + Some(custodians) => custodians.len() > 1, + None => return Err(TonWalletError::CustodiansNotFound.into()), + }; + + multisig::prepare_transfer( + self.clock.as_ref(), + multisig_type, + public_key, + has_multiple_owners, + self.address().clone(), + gift, + expiration, + ) + } + WalletType::WalletV3 => wallet_v3::prepare_transfer( + self.clock.as_ref(), + public_key, + current_state, + 0, + gifts, + expiration, + ), + WalletType::WalletV3R1 => wallet_v3v4::prepare_transfer( + self.clock.as_ref(), + public_key, + current_state, + 0, + gifts, + expiration, + wallet_v3v4::WalletVersion::V3R1, + ), + WalletType::WalletV3R2 => wallet_v3v4::prepare_transfer( + self.clock.as_ref(), + public_key, + current_state, + 0, + gifts, + expiration, + wallet_v3v4::WalletVersion::V3R2, + ), + WalletType::WalletV4R1 => wallet_v3v4::prepare_transfer( + self.clock.as_ref(), + public_key, + current_state, + 0, + gifts, + expiration, + wallet_v3v4::WalletVersion::V4R1, + ), + WalletType::WalletV4R2 => wallet_v3v4::prepare_transfer( + self.clock.as_ref(), + public_key, + current_state, + 0, + gifts, + expiration, + wallet_v3v4::WalletVersion::V4R2, + ), + WalletType::WalletV5R1 => wallet_v5r1::prepare_transfer( + self.clock.as_ref(), + public_key, + current_state, + 0, + gifts, + expiration, + ), + WalletType::EverWallet => ever_wallet::prepare_transfer( + self.clock.as_ref(), + public_key, + current_state, + self.address().clone(), + gifts, + expiration, + ), + WalletType::HighloadWalletV2 => highload_wallet_v2::prepare_transfer( + self.clock.as_ref(), + public_key, + current_state, + gifts, + expiration, + ), + } + } + + pub fn prepare_confirm_transaction( + &self, + current_state: &ton_block::AccountStuff, + public_key: &PublicKey, + transaction_id: u64, + expiration: Expiration, + ) -> Result> { + match self.wallet_type { + WalletType::Multisig(multisig_type) => { + let has_pending_transaction = multisig::find_pending_transaction( + self.clock.as_ref(), + multisig_type, + Cow::Borrowed(current_state), + transaction_id, + )?; + if !has_pending_transaction { + return Err(TonWalletError::PendingTransactionNotFound.into()); + } + + multisig::prepare_confirm_transaction( + self.clock.as_ref(), + multisig_type, + public_key, + self.address().clone(), + transaction_id, + expiration, + ) + } + // Non-multisig wallets doesn't support pending transactions + _ => Err(TonWalletError::PendingTransactionNotFound.into()), + } + } + + pub fn prepare_code_update( + &self, + public_key: &PublicKey, + new_code_hash: &[u8; 32], + expiration: Expiration, + ) -> Result> { + match self.wallet_type { + WalletType::Multisig(multisig_type) if multisig_type.is_multisig2() => { + multisig::prepare_code_update( + self.clock.as_ref(), + multisig_type, + public_key, + self.address().clone(), + new_code_hash, + expiration, + ) + } + _ => Err(TonWalletError::UpdateNotSupported.into()), + } + } + + pub fn prepare_confirm_update( + &self, + current_state: &ton_block::AccountStuff, + public_key: &PublicKey, + update_id: u64, + expiration: Expiration, + ) -> Result> { + match self.wallet_type { + WalletType::Multisig(multisig_type) => { + let pending_update = multisig::find_pending_update( + self.clock.as_ref(), + multisig_type, + Cow::Borrowed(current_state), + update_id, + )?; + if pending_update.is_none() { + return Err(TonWalletError::PendingUpdateNotFound.into()); + } + + multisig::prepare_confirm_update( + self.clock.as_ref(), + multisig_type, + public_key, + self.address().clone(), + update_id, + expiration, + ) + } + _ => Err(TonWalletError::PendingUpdateNotFound.into()), + } + } + + pub fn prepare_execute_code_update( + &self, + current_state: &ton_block::AccountStuff, + public_key: &PublicKey, + update_id: u64, + new_code: ton_types::Cell, + expiration: Expiration, + ) -> Result> { + match self.wallet_type { + WalletType::Multisig(multisig_type) => { + let update = match multisig::find_pending_update( + self.clock.as_ref(), + multisig_type, + Cow::Borrowed(current_state), + update_id, + )? { + Some(update) => update, + None => return Err(TonWalletError::PendingUpdateNotFound.into()), + }; + + if !matches!(update.new_code_hash, Some(hash) if new_code.repr_hash() == hash) { + return Err(TonWalletError::UpdatedDataMismatch.into()); + } + + multisig::prepare_execute_update( + self.clock.as_ref(), + multisig_type, + public_key, + self.address().clone(), + update_id, + Some(new_code), + expiration, + ) + } + _ => Err(TonWalletError::PendingUpdateNotFound.into()), + } + } + + pub async fn send( + &mut self, + message: &ton_block::Message, + expire_at: u32, + ) -> Result { + self.contract_subscription.send(message, expire_at).await + } + + pub async fn refresh(&mut self) -> Result<()> { + let handler = self.handler.as_ref(); + self.contract_subscription + .refresh( + &mut make_contract_state_handler( + self.clock.as_ref(), + handler, + &self.public_key, + self.wallet_type, + &mut self.wallet_data, + ), + &mut make_transactions_handler(handler, self.wallet_type), + &mut make_message_sent_handler(handler), + &mut make_message_expired_handler(handler), + ) + .await + } + + pub async fn handle_block(&mut self, block: &ton_block::Block) -> Result<()> { + // TODO: update wallet data here + + let handler = self.handler.as_ref(); + let new_account_state = self.contract_subscription.handle_block( + block, + &mut make_transactions_handler(handler, self.wallet_type), + &mut make_message_sent_handler(handler), + &mut make_message_expired_handler(handler), + )?; + + if let Some(account_state) = new_account_state { + handler.on_state_changed(account_state); + } + + Ok(()) + } + + pub async fn preload_transactions(&mut self, from_lt: u64) -> Result<()> { + let handler = self.handler.as_ref(); + self.contract_subscription + .preload_transactions( + from_lt, + &mut make_transactions_handler(handler, self.wallet_type), + ) + .await + } + + pub async fn estimate_fees(&mut self, message: &ton_block::Message) -> Result { + self.contract_subscription.estimate_fees(message).await + } +} + +#[derive(Default)] +struct WalletData { + custodians: Option>, + unconfirmed_transactions: Vec, + unconfirmed_updates: Vec, + details: Option, +} + +impl WalletData { + fn update( + &mut self, + clock: &dyn Clock, + public_key: &PublicKey, + wallet_type: WalletType, + account_stuff: &ton_block::AccountStuff, + handler: &dyn TonWalletSubscriptionHandler, + ) -> Result<()> { + // Extract details + if self.details.is_none() { + let mut details = wallet_type.details(); + + if let WalletType::Multisig(multisig_type) = wallet_type { + let params = + multisig::get_params(clock, multisig_type, Cow::Borrowed(account_stuff))?; + details.expiration_time = params.expiration_time.try_into().unwrap_or(u32::MAX); + details.required_confirmations = + NonZeroU8::new(std::cmp::max(params.required_confirms, 1)); + } + + self.details = Some(details); + handler.on_details_changed(details); + } + + // Extract custodians + let multisig_type = match wallet_type { + WalletType::Multisig(multisig_type) => multisig_type, + // Simple path for wallets with single custodian + _ => { + if self.custodians.is_none() { + let custodians = self.custodians.insert(vec![public_key.to_bytes().into()]); + handler.on_custodians_changed(custodians); + } + return Ok(()); + } + }; + + // Extract custodians + let custodians = match &mut self.custodians { + Some(custodians) => custodians, + None => { + let custodians = self.custodians.insert(multisig::get_custodians( + clock, + multisig_type, + Cow::Borrowed(account_stuff), + )?); + handler.on_custodians_changed(custodians); + custodians + } + }; + + if multisig_type.is_updatable() { + let pending_updates = multisig::get_pending_updates( + clock, + multisig_type, + Cow::Borrowed(account_stuff), + custodians, + )?; + if self.unconfirmed_updates != pending_updates { + self.unconfirmed_updates = pending_updates; + handler.on_unconfirmed_updates_changed(&self.unconfirmed_updates); + } + } + + // Skip pending transactions extraction for single custodian + if custodians.len() < 2 { + return Ok(()); + } + + // Extract pending transactions + let pending_transactions = multisig::get_pending_transactions( + clock, + multisig_type, + Cow::Borrowed(account_stuff), + custodians, + )?; + + if self.unconfirmed_transactions != pending_transactions { + self.unconfirmed_transactions = pending_transactions; + handler.on_unconfirmed_transactions_changed(&self.unconfirmed_transactions); + } + + Ok(()) + } +} + +pub fn extract_wallet_init_data(contract: &ExistingContract) -> Result<(PublicKey, WalletType)> { + let (code, data) = match &contract.account.storage.state { + ton_block::AccountState::AccountActive { + state_init: + ton_block::StateInit { + code: Some(code), + data: Some(data), + .. + }, + .. + } => (code, data), + _ => return Err(TonWalletError::AccountNotExists.into()), + }; + + let code_hash = code.repr_hash(); + if let Some(multisig_type) = multisig::guess_multisig_type(&code_hash) { + let public_key = extract_public_key(&contract.account)?; + Ok((public_key, WalletType::Multisig(multisig_type))) + } else if wallet_v3::is_wallet_v3(&code_hash) { + let public_key = PublicKey::from_bytes(wallet_v3::InitData::try_from(data)?.public_key())?; + Ok((public_key, WalletType::WalletV3)) + } else if wallet_v3v4::is_wallet_v4r1(&code_hash) { + let public_key = + PublicKey::from_bytes(wallet_v3v4::InitData::try_from(data)?.public_key())?; + Ok((public_key, WalletType::WalletV4R1)) + } else if wallet_v3v4::is_wallet_v4r2(&code_hash) { + let public_key = + PublicKey::from_bytes(wallet_v3v4::InitData::try_from(data)?.public_key())?; + Ok((public_key, WalletType::WalletV4R2)) + } else if wallet_v5r1::is_wallet_v5r1(&code_hash) { + let public_key = + PublicKey::from_bytes(wallet_v5r1::InitData::try_from(data)?.public_key())?; + Ok((public_key, WalletType::WalletV5R1)) + } else if ever_wallet::is_ever_wallet(&code_hash) { + let public_key = extract_public_key(&contract.account)?; + Ok((public_key, WalletType::EverWallet)) + } else if highload_wallet_v2::is_highload_wallet_v2(&code_hash) { + let public_key = + PublicKey::from_bytes(highload_wallet_v2::InitData::try_from(data)?.public_key())?; + Ok((public_key, WalletType::HighloadWalletV2)) + } else { + Err(TonWalletError::InvalidContractType.into()) + } +} + +pub fn get_wallet_custodians( + clock: &dyn Clock, + contract: &ExistingContract, + public_key: &PublicKey, + wallet_type: WalletType, +) -> Result> { + match wallet_type { + WalletType::Multisig(multisig_type) => { + multisig::get_custodians(clock, multisig_type, Cow::Borrowed(&contract.account)) + } + _ => Ok(vec![public_key.to_bytes().into()]), + } +} + +pub const WALLET_TYPES_BY_POPULARITY: [WalletType; 10] = [ + WalletType::Multisig(MultisigType::SafeMultisigWallet), + WalletType::Multisig(MultisigType::SurfWallet), + WalletType::WalletV3, + WalletType::EverWallet, + WalletType::Multisig(MultisigType::Multisig2_1), + WalletType::Multisig(MultisigType::Multisig2), + WalletType::Multisig(MultisigType::SetcodeMultisigWallet), + WalletType::Multisig(MultisigType::SafeMultisigWallet24h), + WalletType::Multisig(MultisigType::BridgeMultisigWallet), + WalletType::HighloadWalletV2, +]; + +pub async fn find_existing_wallets( + transport: &dyn Transport, + public_key: &PublicKey, + workchain_id: i8, + wallet_types: &[WalletType], +) -> Result> { + use futures_util::stream::{FuturesUnordered, TryStreamExt}; + + wallet_types + .iter() + .map(|&wallet_type| async move { + let address = compute_address(public_key, wallet_type, workchain_id); + + let contract_state = transport.get_contract_state(&address).await?; + + Ok(ExistingWalletInfo { + address, + public_key: *public_key, + wallet_type, + contract_state: contract_state.brief(), + }) + }) + .collect::>() + .try_collect::>() + .await +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExistingWalletInfo { + #[serde(with = "serde_address")] + pub address: MsgAddressInt, + #[serde(with = "serde_public_key")] + pub public_key: PublicKey, + pub wallet_type: WalletType, + pub contract_state: ContractState, +} + +pub trait InternalMessageSender { + fn prepare_transfer( + &mut self, + current_state: &ton_block::AccountStuff, + public_key: &PublicKey, + message: InternalMessage, + expiration: Expiration, + ) -> Result; +} + +impl InternalMessageSender for TonWallet { + fn prepare_transfer( + &mut self, + current_state: &ton_block::AccountStuff, + public_key: &PublicKey, + message: InternalMessage, + expiration: Expiration, + ) -> Result { + if matches!(message.source, Some(source) if &source != self.address()) { + return Err(InternalMessageSenderError::InvalidSender.into()); + } + + let gift = Gift { + flags: MessageFlags::default().into(), + bounce: message.bounce, + destination: message.destination, + amount: message.amount, + body: Some(message.body), + state_init: None, + }; + + self.prepare_transfer(current_state, public_key, vec![gift], expiration) + } +} + +#[derive(thiserror::Error, Debug)] +enum InternalMessageSenderError { + #[error("Invalid sender")] + InvalidSender, +} + +#[derive(thiserror::Error, Debug)] +enum TonWalletError { + #[error("Account not exists")] + AccountNotExists, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Invalid contract type")] + InvalidContractType, + #[error("Custodians not found")] + CustodiansNotFound, + #[error("Pending transaction not found")] + PendingTransactionNotFound, + #[error("Update not supported")] + UpdateNotSupported, + #[error("Pending update not found")] + PendingUpdateNotFound, + #[error("Updated data mismatch")] + UpdatedDataMismatch, +} + +fn make_contract_state_handler<'a>( + clock: &'a dyn Clock, + handler: &'a dyn TonWalletSubscriptionHandler, + public_key: &'a PublicKey, + wallet_type: WalletType, + wallet_data: &'a mut WalletData, +) -> impl FnMut(&RawContractState) + 'a { + move |contract_state| { + if let RawContractState::Exists(contract_state) = contract_state { + if let Err(e) = wallet_data.update( + clock, + public_key, + wallet_type, + &contract_state.account, + handler, + ) { + log::error!("{e}"); + } + } + handler.on_state_changed(contract_state.brief()) + } +} + +fn make_transactions_handler( + handler: &'_ dyn TonWalletSubscriptionHandler, + wallet_type: WalletType, +) -> impl FnMut(Vec, TransactionsBatchInfo) + '_ { + move |transactions, batch_info| { + let transactions = transactions + .into_iter() + .filter_map(move |transaction| { + let data = parse_transaction_additional_info(&transaction.data, wallet_type); + let transaction = + Transaction::try_from((transaction.hash, transaction.data)).ok()?; + Some(TransactionWithData { transaction, data }) + }) + .collect(); + + handler.on_transactions_found(transactions, batch_info) + } +} + +fn make_message_sent_handler( + handler: &'_ dyn TonWalletSubscriptionHandler, +) -> impl FnMut(PendingTransaction, RawTransaction) + '_ { + move |pending_transaction, transaction| { + let transaction = Transaction::try_from((transaction.hash, transaction.data)).ok(); + handler.on_message_sent(pending_transaction, transaction); + } +} + +fn make_message_expired_handler( + handler: &'_ dyn TonWalletSubscriptionHandler, +) -> impl FnMut(PendingTransaction) + '_ { + move |pending_transaction| handler.on_message_expired(pending_transaction) +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct TonWalletDetails { + pub requires_separate_deploy: bool, + #[serde(with = "serde_string")] + pub min_amount: u64, + pub max_messages: usize, + pub supports_payload: bool, + pub supports_state_init: bool, + pub supports_multiple_owners: bool, + pub supports_code_update: bool, + pub expiration_time: u32, + pub required_confirmations: Option, +} + +/// Message info +#[derive(Clone)] +pub struct Gift { + pub flags: u8, + pub bounce: bool, + pub destination: IntAddr, + pub amount: u128, + pub body: Option, + pub state_init: Option, +} + +#[derive(Clone)] +pub enum TransferAction { + DeployFirst, + Sign(Box), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum WalletType { + Multisig(MultisigType), + WalletV3, + WalletV3R1, + WalletV3R2, + WalletV4R1, + WalletV4R2, + WalletV5R1, + HighloadWalletV2, + EverWallet, +} + +impl WalletType { + pub fn details(&self) -> TonWalletDetails { + match self { + Self::Multisig(multisig_type) => multisig::ton_wallet_details(*multisig_type), + Self::WalletV3 => wallet_v3::DETAILS, + Self::WalletV5R1 => wallet_v5r1::DETAILS, + Self::HighloadWalletV2 => highload_wallet_v2::DETAILS, + Self::EverWallet => ever_wallet::DETAILS, + Self::WalletV3R1 | Self::WalletV3R2 | Self::WalletV4R1 | Self::WalletV4R2 => { + wallet_v3v4::DETAILS + } + } + } + + pub fn possible_updates(&self) -> &'static [Self] { + const MULTISIG2_UPDATES: &[WalletType] = &[WalletType::Multisig(MultisigType::Multisig2_1)]; + + match self { + Self::Multisig(MultisigType::Multisig2) => MULTISIG2_UPDATES, + _ => &[], + } + } + + pub fn code_hash(&self) -> &[u8; 32] { + match self { + Self::Multisig(multisig_type) => multisig_type.code_hash(), + Self::WalletV3 => wallet_v3::CODE_HASH, + Self::WalletV3R1 => wallet_v3v4::CODE_HASH_V3_R1, + Self::WalletV3R2 => wallet_v3v4::CODE_HASH_V3_R2, + Self::WalletV4R1 => wallet_v3v4::CODE_HASH_V4_R1, + Self::WalletV4R2 => wallet_v3v4::CODE_HASH_V4_R2, + Self::WalletV5R1 => wallet_v5r1::CODE_HASH, + Self::HighloadWalletV2 => highload_wallet_v2::CODE_HASH, + Self::EverWallet => ever_wallet::CODE_HASH, + } + } + + pub fn code(&self) -> ton_types::Cell { + use nekoton_contracts::wallets; + match self { + Self::Multisig(multisig_type) => multisig_type.code(), + Self::WalletV3 => wallets::code::wallet_v3(), + Self::WalletV3R1 => wallets::code::wallet_v3r1(), + Self::WalletV3R2 => wallets::code::wallet_v3r2(), + Self::WalletV4R1 => wallets::code::wallet_v4r1(), + Self::WalletV4R2 => wallets::code::wallet_v4r2(), + Self::WalletV5R1 => wallets::code::wallet_v5r1(), + Self::HighloadWalletV2 => wallets::code::highload_wallet_v2(), + Self::EverWallet => wallets::code::ever_wallet(), + } + } +} + +impl FromStr for WalletType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "WalletV3" => Self::WalletV3, + "WalletV3R1" => Self::WalletV3R1, + "WalletV3R2" => Self::WalletV3R2, + "WalletV4R1" => Self::WalletV4R1, + "WalletV4R2" => Self::WalletV4R2, + "WalletV5R1" => Self::WalletV5R1, + "HighloadWalletV2" => Self::HighloadWalletV2, + "EverWallet" => Self::EverWallet, + s => Self::Multisig(MultisigType::from_str(s)?), + }) + } +} + +impl TryInto for WalletType { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + let res = match self { + WalletType::WalletV3 => 0, + WalletType::EverWallet => 1, + WalletType::Multisig(MultisigType::SafeMultisigWallet) => 2, + WalletType::Multisig(MultisigType::SafeMultisigWallet24h) => 3, + WalletType::Multisig(MultisigType::SetcodeMultisigWallet) => 4, + WalletType::Multisig(MultisigType::BridgeMultisigWallet) => 5, + WalletType::Multisig(MultisigType::SurfWallet) => 6, + WalletType::Multisig(MultisigType::Multisig2) => 7, + WalletType::Multisig(MultisigType::Multisig2_1) => 8, + WalletType::WalletV4R1 => 9, + WalletType::WalletV4R2 => 10, + WalletType::WalletV5R1 => 11, + _ => anyhow::bail!("Unimplemented wallet type"), + }; + + Ok(res) + } +} + +impl std::fmt::Display for WalletType { + fn fmt(&self, f: &'_ mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Multisig(multisig_type) => multisig_type.fmt(f), + Self::WalletV3 => f.write_str("WalletV3"), + Self::WalletV3R1 => f.write_str("WalletV3R1"), + Self::WalletV3R2 => f.write_str("WalletV3R2"), + Self::WalletV4R1 => f.write_str("WalletV4R1"), + Self::WalletV4R2 => f.write_str("WalletV4R2"), + Self::WalletV5R1 => f.write_str("WalletV5R1"), + Self::HighloadWalletV2 => f.write_str("HighloadWalletV2"), + Self::EverWallet => f.write_str("EverWallet"), + } + } +} + +pub fn compute_address( + public_key: &PublicKey, + wallet_type: WalletType, + workchain_id: i8, +) -> MsgAddressInt { + match wallet_type { + WalletType::Multisig(multisig_type) => { + multisig::compute_contract_address(public_key, multisig_type, workchain_id) + } + WalletType::WalletV3 => wallet_v3::compute_contract_address(public_key, workchain_id), + WalletType::WalletV5R1 => wallet_v5r1::compute_contract_address(public_key, workchain_id), + WalletType::EverWallet => ever_wallet::compute_contract_address(public_key, workchain_id), + WalletType::HighloadWalletV2 => { + highload_wallet_v2::compute_contract_address(public_key, workchain_id) + } + WalletType::WalletV3R1 => wallet_v3v4::compute_contract_address( + public_key, + workchain_id, + wallet_v3v4::WalletVersion::V3R1, + ), + WalletType::WalletV3R2 => wallet_v3v4::compute_contract_address( + public_key, + workchain_id, + wallet_v3v4::WalletVersion::V3R2, + ), + WalletType::WalletV4R1 => wallet_v3v4::compute_contract_address( + public_key, + workchain_id, + wallet_v3v4::WalletVersion::V4R1, + ), + WalletType::WalletV4R2 => wallet_v3v4::compute_contract_address( + public_key, + workchain_id, + wallet_v3v4::WalletVersion::V4R2, + ), + } +} + +pub trait TonWalletSubscriptionHandler: Send + Sync { + /// Called when found transaction which is relative with one of the pending transactions + fn on_message_sent( + &self, + pending_transaction: PendingTransaction, + transaction: Option, + ); + + /// Called when no transactions produced for the specific message before some expiration time + fn on_message_expired(&self, pending_transaction: PendingTransaction); + + /// Called every time a new state is detected + fn on_state_changed(&self, new_state: ContractState) { + let _ = new_state; + } + + /// Called every time new transactions are detected. + /// - When new block found + /// - When manually requesting the latest transactions (can be called several times) + /// - When preloading transactions + fn on_transactions_found( + &self, + transactions: Vec>, + batch_info: TransactionsBatchInfo, + ) { + let _ = transactions; + let _ = batch_info; + } + + /// Called when wallet details changed (e.g. expiration time or required confirms) + fn on_details_changed(&self, details: TonWalletDetails) { + let _ = details; + } + + /// Called when wallet custodians changed (e.g. on code upgrade or first refresh) + fn on_custodians_changed(&self, custodians: &[UInt256]) { + let _ = custodians; + } + + /// Called when wallet has new pending transactions set + fn on_unconfirmed_transactions_changed( + &self, + unconfirmed_transactions: &[MultisigPendingTransaction], + ) { + let _ = unconfirmed_transactions; + } + + /// Called when wallet has new pending updates set + fn on_unconfirmed_updates_changed(&self, unconfirmed_updates: &[MultisigPendingUpdate]) { + let _ = unconfirmed_updates; + } +} diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs new file mode 100644 index 0000000..5704d9c --- /dev/null +++ b/src/utils/ton_wallet/multisig.rs @@ -0,0 +1,744 @@ +use std::borrow::Cow; +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::PublicKey; +use ton_block::{Deserializable, GetRepresentationHash, MsgAddressInt, Serializable}; +use ton_types::UInt256; + +use nekoton_abi::*; +use nekoton_utils::*; + +use super::{Gift, TonWalletDetails, TransferAction}; +use crate::core::models::{ + Expiration, MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate, +}; +use crate::core::utils::*; +use crate::crypto::UnsignedMessage; + +#[derive(Copy, Clone, Debug)] +pub struct DeployParams<'a> { + pub owners: &'a [PublicKey], + pub req_confirms: u8, + pub expiration_time: Option, +} + +impl<'a> DeployParams<'a> { + pub fn single_custodian(pubkey: &'a PublicKey) -> Self { + Self { + owners: std::slice::from_ref(pubkey), + req_confirms: 1, + expiration_time: None, + } + } +} + +pub fn prepare_deploy( + clock: &dyn Clock, + public_key: &PublicKey, + multisig_type: MultisigType, + workchain: i8, + expiration: Expiration, + params: DeployParams<'_>, +) -> Result> { + let state_init = prepare_state_init(public_key, multisig_type); + let hash = state_init.hash().trust_me(); + + let dst = MsgAddressInt::AddrStd(ton_block::MsgAddrStd { + anycast: None, + workchain_id: workchain, + address: hash.into(), + }); + + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst, + ..Default::default() + }); + + message.set_state_init(state_init); + + let owners = params + .owners + .iter() + .map(|public_key| UInt256::from(public_key.as_bytes())) + .collect::>(); + + let is_new_multisig = multisig_type.is_multisig2(); + let function = if is_new_multisig { + nekoton_contracts::wallets::multisig2::constructor() + } else if params.expiration_time.is_none() { + nekoton_contracts::wallets::multisig::constructor() + } else { + return Err(MultisigError::CustomExpirationTimeNotSupported.into()); + }; + + let (function, input) = { + let mut message = MessageBuilder::new(function) + .arg(owners) + .arg(params.req_confirms); + if is_new_multisig { + message = message.arg(params.expiration_time.unwrap_or(DEFAULT_LIFETIME)); + } + message.build() + }; + + make_labs_unsigned_message( + clock, + message, + expiration, + public_key, + Cow::Borrowed(function), + input, + ) +} + +pub fn prepare_confirm_transaction( + clock: &dyn Clock, + multisig_type: MultisigType, + public_key: &PublicKey, + address: MsgAddressInt, + transaction_id: u64, + expiration: Expiration, +) -> Result> { + let function = if multisig_type.is_multisig2() { + nekoton_contracts::wallets::multisig2::confirm_transaction() + } else { + nekoton_contracts::wallets::multisig::confirm_transaction() + }; + let (function, input) = MessageBuilder::new(function).arg(transaction_id).build(); + + make_ext_message(clock, public_key, address, expiration, function, input) +} + +pub fn prepare_transfer( + clock: &dyn Clock, + multisig_type: MultisigType, + public_key: &PublicKey, + has_multiple_owners: bool, + address: MsgAddressInt, + gift: Gift, + expiration: Expiration, +) -> Result { + let is_new_multisig = multisig_type.is_multisig2(); + + let (function, input) = if has_multiple_owners || is_new_multisig && gift.state_init.is_some() { + let all_balance = match MessageFlags::try_from(gift.flags) { + Ok(MessageFlags::Normal) => false, + Ok(MessageFlags::AllBalance) => true, + _ => return Err(MultisigError::UnsupportedFlagsSet.into()), + }; + + let function = if is_new_multisig { + nekoton_contracts::wallets::multisig2::submit_transaction() + } else { + nekoton_contracts::wallets::multisig::submit_transaction() + }; + + let message = MessageBuilder::new(function) + .arg(gift.destination) + .arg(BigUint128(gift.amount.into())) + .arg(gift.bounce) + .arg(all_balance) + .arg(gift.body.unwrap_or_default().into_cell()); + + if is_new_multisig { + message + .arg( + gift.state_init + .map(|state_init| state_init.serialize()) + .transpose()?, + ) + .build() + } else { + message.build() + } + } else { + let function = if is_new_multisig { + nekoton_contracts::wallets::multisig2::send_transaction() + } else { + nekoton_contracts::wallets::multisig::send_transaction() + }; + MessageBuilder::new(function) + .arg(gift.destination) + .arg(BigUint128(gift.amount.into())) + .arg(gift.bounce) + .arg(gift.flags) + .arg(gift.body.unwrap_or_default().into_cell()) + .build() + }; + + make_ext_message(clock, public_key, address, expiration, function, input) + .map(TransferAction::Sign) +} + +pub fn prepare_code_update( + clock: &dyn Clock, + multisig_type: MultisigType, + public_key: &PublicKey, + address: MsgAddressInt, + new_code_hash: &[u8; 32], + expiration: Expiration, +) -> Result> { + use nekoton_contracts::wallets::multisig2; + + if !multisig_type.is_multisig2() { + return Err(MultisigError::UnsupportedUpdate.into()); + } + + make_ext_message( + clock, + public_key, + address, + expiration, + multisig2::submit_update(), + multisig2::SubmitUpdateParams { + code_hash: Some(ton_types::UInt256::from(*new_code_hash)), + owners: None, + req_confirms: None, + lifetime: None, + } + .pack(), + ) +} + +pub fn prepare_confirm_update( + clock: &dyn Clock, + multisig_type: MultisigType, + public_key: &PublicKey, + address: MsgAddressInt, + update_id: u64, + expiration: Expiration, +) -> Result> { + use nekoton_contracts::wallets::multisig2; + + if !multisig_type.is_multisig2() { + return Err(MultisigError::UnsupportedUpdate.into()); + } + + make_ext_message( + clock, + public_key, + address, + expiration, + multisig2::confirm_update(), + multisig2::ConfirmUpdateParams { update_id }.pack(), + ) +} + +pub fn prepare_execute_update( + clock: &dyn Clock, + multisig_type: MultisigType, + public_key: &PublicKey, + address: MsgAddressInt, + update_id: u64, + code: Option, + expiration: Expiration, +) -> Result> { + use nekoton_contracts::wallets::multisig2; + + if !multisig_type.is_multisig2() { + return Err(MultisigError::UnsupportedUpdate.into()); + } + + make_ext_message( + clock, + public_key, + address, + expiration, + multisig2::execute_update(), + multisig2::ExecuteUpdateParams { update_id, code }.pack(), + ) +} + +define_string_enum!( + #[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] + pub enum MultisigType { + SafeMultisigWallet, + SafeMultisigWallet24h, + SetcodeMultisigWallet, + SetcodeMultisigWallet24h, + BridgeMultisigWallet, + SurfWallet, + Multisig2, + Multisig2_1, + } +); + +impl MultisigType { + pub fn is_multisig2(self) -> bool { + matches!(self, Self::Multisig2 | Self::Multisig2_1) + } + + pub fn is_updatable(&self) -> bool { + matches!( + self, + Self::SetcodeMultisigWallet + | Self::SetcodeMultisigWallet24h + | Self::SurfWallet + | Self::Multisig2 + | Self::Multisig2_1 + ) + } + + pub fn state_init(&self) -> ton_block::StateInit { + use nekoton_contracts::wallets; + + let state_init = match self { + MultisigType::SafeMultisigWallet => wallets::code::safe_multisig_wallet(), + MultisigType::SafeMultisigWallet24h => wallets::code::safe_multisig_wallet_24h(), + MultisigType::SetcodeMultisigWallet => wallets::code::setcode_multisig_wallet(), + MultisigType::SetcodeMultisigWallet24h => wallets::code::setcode_multisig_wallet_24h(), + MultisigType::BridgeMultisigWallet => wallets::code::bridge_multisig_wallet(), + MultisigType::SurfWallet => wallets::code::surf_wallet(), + MultisigType::Multisig2 => wallets::code::multisig2(), + MultisigType::Multisig2_1 => wallets::code::multisig2_1(), + }; + let mut state_init = ton_types::SliceData::load_cell(state_init).trust_me(); + + ton_block::StateInit::construct_from(&mut state_init).trust_me() + } + + pub fn code_hash(&self) -> &[u8; 32] { + match self { + Self::SafeMultisigWallet => SAFE_MULTISIG_WALLET_HASH, + Self::SafeMultisigWallet24h => SAFE_MULTISIG_WALLET_24H_HASH, + Self::SetcodeMultisigWallet => SETCODE_MULTISIG_WALLET_HASH, + Self::SetcodeMultisigWallet24h => SETCODE_MULTISIG_WALLET_24H_HASH, + Self::BridgeMultisigWallet => BRIDGE_MULTISIG_WALLET_HASH, + Self::SurfWallet => SURF_WALLET_HASH, + Self::Multisig2 => MULTISIG2_HASH, + Self::Multisig2_1 => MULTISIG2_1_HASH, + } + } + + pub fn code(&self) -> ton_types::Cell { + self.state_init().code.trust_me() + } +} + +static SAFE_MULTISIG_WALLET_HASH: &[u8; 32] = &[ + 0x80, 0xd6, 0xc4, 0x7c, 0x4a, 0x25, 0x54, 0x3c, 0x9b, 0x39, 0x7b, 0x71, 0x71, 0x6f, 0x3f, 0xae, + 0x1e, 0x2c, 0x5d, 0x24, 0x71, 0x74, 0xc5, 0x2e, 0x2c, 0x19, 0xbd, 0x89, 0x64, 0x42, 0xb1, 0x05, +]; +static SAFE_MULTISIG_WALLET_24H_HASH: &[u8; 32] = &[ + 0x7d, 0x09, 0x96, 0x94, 0x34, 0x06, 0xf7, 0xd6, 0x2a, 0x4f, 0xf2, 0x91, 0xb1, 0x22, 0x8b, 0xf0, + 0x6e, 0xbd, 0x3e, 0x04, 0x8b, 0x58, 0x43, 0x6c, 0x5b, 0x70, 0xfb, 0x77, 0xff, 0x8b, 0x4b, 0xf2, +]; +static SETCODE_MULTISIG_WALLET_HASH: &[u8; 32] = &[ + 0xe2, 0xb6, 0x0b, 0x6b, 0x60, 0x2c, 0x10, 0xce, 0xd7, 0xea, 0x8e, 0xde, 0x4b, 0xdf, 0x96, 0x34, + 0x2c, 0x97, 0x57, 0x0a, 0x37, 0x98, 0x06, 0x6f, 0x3f, 0xb5, 0x0a, 0x4b, 0x2b, 0x27, 0xa2, 0x08, +]; +static SETCODE_MULTISIG_WALLET_24H_HASH: &[u8; 32] = &[ + 0xa4, 0x91, 0x80, 0x4c, 0xa5, 0x5d, 0xd5, 0xb2, 0x8c, 0xff, 0xdf, 0xf4, 0x8c, 0xb3, 0x41, 0x42, + 0x93, 0x09, 0x99, 0x62, 0x1a, 0x54, 0xac, 0xee, 0x6b, 0xe8, 0x3c, 0x34, 0x20, 0x51, 0xd8, 0x84, +]; +static BRIDGE_MULTISIG_WALLET_HASH: &[u8; 32] = &[ + 0xf3, 0xa0, 0x7a, 0xe8, 0x4f, 0xc3, 0x43, 0x25, 0x9d, 0x7f, 0xa4, 0x84, 0x7b, 0x86, 0x33, 0x5b, + 0x3f, 0xdc, 0xfc, 0x8b, 0x31, 0xf1, 0xba, 0x4b, 0x7a, 0x94, 0x99, 0xd5, 0x53, 0x0f, 0x0b, 0x18, +]; +static SURF_WALLET_HASH: &[u8; 32] = &[ + 0x20, 0x7d, 0xc5, 0x60, 0xc5, 0x95, 0x6d, 0xe1, 0xa2, 0xc1, 0x47, 0x93, 0x56, 0xf8, 0xf3, 0xee, + 0x70, 0xa5, 0x97, 0x67, 0xdb, 0x2b, 0xf4, 0x78, 0x8b, 0x1d, 0x61, 0xad, 0x42, 0xcd, 0xad, 0x82, +]; +static MULTISIG2_HASH: &[u8; 32] = &[ + 0x29, 0xb2, 0x47, 0x76, 0xb3, 0xdf, 0x6a, 0x05, 0xc5, 0xa1, 0xb8, 0xd8, 0xfd, 0x75, 0xcb, 0x72, + 0xa1, 0xd3, 0x3c, 0x0a, 0x44, 0x38, 0x53, 0x32, 0xa8, 0xbf, 0xc2, 0x72, 0x7f, 0xb6, 0x65, 0x90, +]; +static MULTISIG2_1_HASH: &[u8; 32] = &[ + 0xd6, 0x6d, 0x19, 0x87, 0x66, 0xab, 0xdb, 0xe1, 0x25, 0x3f, 0x34, 0x15, 0x82, 0x6c, 0x94, 0x6c, + 0x37, 0x1f, 0x51, 0x12, 0x55, 0x24, 0x08, 0x62, 0x5a, 0xeb, 0x0b, 0x31, 0xe0, 0xef, 0x2d, 0xf3, +]; + +pub fn guess_multisig_type(code_hash: &UInt256) -> Option { + match code_hash.as_slice() { + s if s == SAFE_MULTISIG_WALLET_HASH => Some(MultisigType::SafeMultisigWallet), + s if s == SAFE_MULTISIG_WALLET_24H_HASH => Some(MultisigType::SafeMultisigWallet24h), + s if s == SETCODE_MULTISIG_WALLET_HASH => Some(MultisigType::SetcodeMultisigWallet), + s if s == BRIDGE_MULTISIG_WALLET_HASH => Some(MultisigType::BridgeMultisigWallet), + s if s == SETCODE_MULTISIG_WALLET_24H_HASH => Some(MultisigType::SetcodeMultisigWallet24h), + s if s == SURF_WALLET_HASH => Some(MultisigType::SurfWallet), + s if s == MULTISIG2_HASH => Some(MultisigType::Multisig2), + s if s == MULTISIG2_1_HASH => Some(MultisigType::Multisig2_1), + _ => None, + } +} + +pub fn compute_contract_address( + public_key: &PublicKey, + multisig_type: MultisigType, + workchain_id: i8, +) -> MsgAddressInt { + let state_init = prepare_state_init(public_key, multisig_type); + let hash = state_init.hash().trust_me(); + + MsgAddressInt::AddrStd(ton_block::MsgAddrStd { + anycast: None, + workchain_id, + address: hash.into(), + }) +} + +pub fn ton_wallet_details(multisig_type: MultisigType) -> TonWalletDetails { + TonWalletDetails { + requires_separate_deploy: true, + min_amount: if multisig_type.is_multisig2() { + 0 + } else { + 1000000 // 0.001 EVER + }, + max_messages: 1, + supports_payload: true, + supports_state_init: multisig_type.is_multisig2(), + supports_multiple_owners: true, + supports_code_update: multisig_type.is_updatable(), + expiration_time: match multisig_type { + MultisigType::SafeMultisigWallet + | MultisigType::SetcodeMultisigWallet + | MultisigType::Multisig2 + | MultisigType::Multisig2_1 => 3600, + MultisigType::SurfWallet => 3601, + MultisigType::SafeMultisigWallet24h + | MultisigType::SetcodeMultisigWallet24h + | MultisigType::BridgeMultisigWallet => 86400, + }, + required_confirmations: None, + } +} + +pub fn prepare_state_init( + public_key: &PublicKey, + multisig_type: MultisigType, +) -> ton_block::StateInit { + let mut state_init = multisig_type.state_init(); + + let new_data = ton_abi::Contract::insert_pubkey( + ton_types::SliceData::load_cell(state_init.data.clone().unwrap_or_default()).trust_me(), + public_key.as_bytes(), + ) + .trust_me(); + state_init.set_data(new_data.into_cell()); + + state_init +} + +fn run_local( + clock: &dyn Clock, + function: &ton_abi::Function, + account_stuff: ton_block::AccountStuff, +) -> Result> { + let ExecutionOutput { + tokens, + result_code, + } = function.run_local(clock, account_stuff, &[], &[])?; + tokens.ok_or_else(|| MultisigError::NonZeroResultCode(result_code).into()) +} + +#[derive(Copy, Clone, UnpackAbiPlain)] +pub struct MultisigParamsPrefix { + #[abi(uint8, name = "maxQueuedTransactions")] + pub max_queued_transactions: u8, + #[abi(uint8, name = "maxCustodianCount")] + pub max_custodian_count: u8, + #[abi(uint64, name = "expirationTime")] + pub expiration_time: u64, + #[abi(uint128, name = "minValue")] + pub min_value: u128, + #[abi(uint8, name = "requiredTxnConfirms")] + pub required_confirms: u8, +} + +pub fn get_params( + clock: &dyn Clock, + multisig_type: MultisigType, + account_stuff: Cow<'_, ton_block::AccountStuff>, +) -> Result { + let function = match multisig_type { + MultisigType::Multisig2 | MultisigType::Multisig2_1 => { + nekoton_contracts::wallets::multisig2::get_parameters() + } + MultisigType::SafeMultisigWallet + | MultisigType::SafeMultisigWallet24h + | MultisigType::BridgeMultisigWallet => { + nekoton_contracts::wallets::multisig::safe_multisig::get_parameters() + } + MultisigType::SetcodeMultisigWallet + | MultisigType::SetcodeMultisigWallet24h + | MultisigType::SurfWallet => { + nekoton_contracts::wallets::multisig::set_code_multisig::get_parameters() + } + }; + + let output: MultisigParamsPrefix = + run_local(clock, function, account_stuff.into_owned())?.unpack()?; + Ok(output) +} + +pub fn get_custodians( + clock: &dyn Clock, + multisig_type: MultisigType, + account_stuff: Cow<'_, ton_block::AccountStuff>, +) -> Result> { + let function = if multisig_type.is_multisig2() { + nekoton_contracts::wallets::multisig2::get_custodians() + } else { + nekoton_contracts::wallets::multisig::get_custodians() + }; + run_local(clock, function, account_stuff.into_owned()) + .and_then(parse_multisig_contract_custodians) +} + +fn parse_multisig_contract_custodians(tokens: Vec) -> Result> { + let array = match tokens.into_unpacker().unpack_next() { + Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + let mut custodians = array + .into_iter() + .map(|item| item.unpack()) + .collect::, _>>()?; + + custodians.sort_by(|a, b| a.index.cmp(&b.index)); + + Ok(custodians.into_iter().map(|item| item.pubkey).collect()) +} + +pub fn find_pending_transaction( + clock: &dyn Clock, + multisig_type: MultisigType, + account_stuff: Cow<'_, ton_block::AccountStuff>, + pending_transaction_id: u64, +) -> Result { + #[derive(Copy, Clone, UnpackAbi)] + pub struct MultisigTransactionId { + #[abi(uint64)] + pub id: u64, + } + + let function = if multisig_type.is_multisig2() { + nekoton_contracts::wallets::multisig2::get_transactions() + } else { + nekoton_contracts::wallets::multisig::get_transactions() + }; + + let tokens = run_local(clock, function, account_stuff.into_owned())?; + + let array = match tokens.into_unpacker().unpack_next() { + Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + for item in array { + let MultisigTransactionId { id } = item.unpack()?; + if pending_transaction_id == id { + return Ok(true); + } + } + Ok(false) +} + +pub fn find_pending_update( + clock: &dyn Clock, + multisig_type: MultisigType, + account_stuff: Cow<'_, ton_block::AccountStuff>, + update_id: u64, +) -> Result> { + use nekoton_contracts::wallets::multisig2; + + let function = match multisig_type { + MultisigType::Multisig2 => multisig2::v2_0::get_update_requests(), + MultisigType::Multisig2_1 => multisig2::v2_1::get_update_requests(), + _ => return Ok(None), + }; + + let tokens = run_local(clock, function, account_stuff.into_owned())?; + + let array = match tokens.into_unpacker().unpack_next() { + Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + for item in array { + let update: multisig2::UpdateTransaction = item.unpack()?; + if update_id == update.id { + return Ok(Some(UpdatedParams { + new_code_hash: update.new_code_hash, + new_custodians: update.new_custodians, + new_req_confirms: update.new_req_confirms, + new_lifetime: update.new_lifetime, + })); + } + } + + Ok(None) +} + +#[derive(Debug, Clone)] +pub struct UpdatedParams { + pub new_code_hash: Option, + pub new_custodians: Option>, + pub new_req_confirms: Option, + pub new_lifetime: Option, +} + +pub fn get_pending_transactions( + clock: &dyn Clock, + multisig_type: MultisigType, + account_stuff: Cow<'_, ton_block::AccountStuff>, + custodians: &[UInt256], +) -> Result> { + let function = if multisig_type.is_multisig2() { + nekoton_contracts::wallets::multisig2::get_transactions() + } else { + nekoton_contracts::wallets::multisig::get_transactions() + }; + run_local(clock, function, account_stuff.into_owned()).and_then(|tokens| { + let array = match tokens.into_unpacker().unpack_next() { + Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + let transactions = array + .into_iter() + .map(|item| Ok(extend_pending_transaction(item.unpack()?, custodians))) + .collect::>>()?; + + Ok(transactions) + }) +} + +pub fn get_pending_updates( + clock: &dyn Clock, + multisig_type: MultisigType, + account_stuff: Cow<'_, ton_block::AccountStuff>, + custodians: &[UInt256], +) -> Result> { + use nekoton_contracts::wallets::multisig2; + + let function = match multisig_type { + MultisigType::Multisig2 => multisig2::v2_0::get_update_requests(), + MultisigType::Multisig2_1 => multisig2::v2_1::get_update_requests(), + _ => return Ok(Vec::new()), + }; + + run_local(clock, function, account_stuff.into_owned()).and_then(|tokens| { + let array = match tokens.into_unpacker().unpack_next() { + Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + let updates = array + .into_iter() + .map(|item| Ok(extend_pending_update(item.unpack()?, custodians))) + .collect::>>()?; + + Ok(updates) + }) +} + +fn extend_pending_transaction( + tx: nekoton_contracts::wallets::multisig::MultisigTransaction, + custodians: &[UInt256], +) -> MultisigPendingTransaction { + let confirmations = custodians + .iter() + .enumerate() + .filter(|(i, _)| (0b1 << i) & tx.confirmation_mask != 0) + .map(|(_, item)| *item) + .collect::>(); + + MultisigPendingTransaction { + id: tx.id, + confirmations, + signs_required: tx.signs_required, + signs_received: tx.signs_received, + creator: tx.creator, + index: tx.index, + dest: tx.dest, + value: tx.value.into(), + send_flags: tx.send_flags, + payload: tx.payload, + bounce: tx.bounce, + } +} + +fn extend_pending_update( + tx: nekoton_contracts::wallets::multisig2::UpdateTransaction, + custodians: &[UInt256], +) -> MultisigPendingUpdate { + let confirmations = custodians + .iter() + .enumerate() + .filter(|(i, _)| (0b1 << i) & tx.confirmations_mask != 0) + .map(|(_, item)| *item) + .collect::>(); + + MultisigPendingUpdate { + id: tx.id, + confirmations, + signs_received: tx.signs, + creator: tx.creator, + index: tx.index, + new_code_hash: tx.new_code_hash, + new_custodians: tx.new_custodians, + new_req_confirms: tx.new_req_confirms, + new_lifetime: tx.new_lifetime, + } +} + +fn make_ext_message( + clock: &dyn Clock, + public_key: &PublicKey, + address: MsgAddressInt, + expiration: Expiration, + function: &'static ton_abi::Function, + input: Vec, +) -> Result> { + let message = ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst: address, + ..Default::default() + }); + + make_labs_unsigned_message( + clock, + message, + expiration, + public_key, + Cow::Borrowed(function), + input, + ) +} + +const DEFAULT_LIFETIME: u32 = 3600; + +#[derive(thiserror::Error, Debug)] +enum MultisigError { + #[error("Non-zero execution result code: {}", .0)] + NonZeroResultCode(i32), + #[error("Unsupported message flags set")] + UnsupportedFlagsSet, + #[error("Custom lifetime is not supported for this contract type")] + CustomExpirationTimeNotSupported, + #[error("Update is not supported or not implemented for this contract type")] + UnsupportedUpdate, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn correct_address() { + let key = PublicKey::from_bytes( + &hex::decode("5ace46d93d8f3932499df9f2bc7ef787385e16965e7797258948febd186de7f6") + .unwrap(), + ) + .unwrap(); + + assert_eq!( + compute_contract_address(&key, MultisigType::SetcodeMultisigWallet24h, 0).to_string(), + "0:3de70f9212154344a3158768b3fed731fc865ca15948b0d6d0d34daf4c6a7a0a" + ); + } +} diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs new file mode 100644 index 0000000..543b478 --- /dev/null +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -0,0 +1,358 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::PublicKey; +use ton_block::{MsgAddrStd, MsgAddressInt, Serializable}; +use ton_types::{BuilderData, Cell, IBitstring, SliceData, UInt256}; + +use nekoton_utils::*; + +use super::{Gift, TonWalletDetails, TransferAction}; +use crate::core::models::{Expiration, ExpireAt, PendingTransaction}; +use crate::crypto::{SignedMessage, UnsignedMessage}; + +pub fn prepare_deploy( + clock: &dyn Clock, + public_key: &PublicKey, + workchain: i8, + expiration: Expiration, +) -> Result> { + let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); + let dst = compute_contract_address(public_key, workchain); + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst, + ..Default::default() + }); + + message.set_state_init(init_data.make_state_init()?); + + let expire_at = ExpireAt::new(clock, expiration); + let (hash, payload) = init_data.make_transfer_payload(None, expire_at.timestamp)?; + + Ok(Box::new(UnsignedWalletV3Message { + init_data, + gifts: Vec::new(), + payload, + message, + expire_at, + hash, + })) +} + +pub fn prepare_state_init(public_key: &PublicKey) -> Result { + let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); + init_data.make_state_init() +} + +/// Adjusts seqno if there are some recent pending transactions that have not expired +pub fn estimate_seqno_offset( + clock: &dyn Clock, + current_state: &ton_block::AccountStuff, + pending_transactions: &[PendingTransaction], +) -> u32 { + const SEQNO_ADJUST_INTERVAL: u32 = 30; // seconds + + #[inline] + fn same_lt(lt_from_pending: u64, lt_from_state: u64) -> bool { + // NOTE: `pending.latest_lt` can be exact transaction lt, or + // `storage.last_trans_lt` which is a bit greater + const ALLOWED_LT_DIFF: u64 = 1 + MAX_MESSAGES as u64; + + (lt_from_pending..=lt_from_pending + ALLOWED_LT_DIFF).contains(<_from_state) + } + + if pending_transactions.is_empty() { + return 0; + } + + let now = clock.now_sec_u64() as u32; + let latest_lt = current_state.storage.last_trans_lt; + + let mut seqno_offset = 0; + for pending in pending_transactions.iter().rev() { + // Adjust only for sufficiently new pending transactions. + if now > pending.created_at + SEQNO_ADJUST_INTERVAL { + break; + } + + // Adjust only if account state hasn't changed + if !same_lt(pending.latest_lt, latest_lt) { + break; + } + + if now < pending.expire_at { + seqno_offset += 1; + } + } + + seqno_offset +} + +pub fn prepare_transfer( + clock: &dyn Clock, + public_key: &PublicKey, + current_state: &ton_block::AccountStuff, + seqno_offset: u32, + gifts: Vec, + expiration: Expiration, +) -> Result { + if gifts.len() > MAX_MESSAGES { + return Err(WalletV3Error::TooManyGifts.into()); + } + + let (mut init_data, with_state_init) = match ¤t_state.storage.state { + ton_block::AccountState::AccountActive { state_init, .. } => match &state_init.data { + Some(data) => (InitData::try_from(data)?, false), + None => return Err(WalletV3Error::InvalidInitData.into()), + }, + ton_block::AccountState::AccountFrozen { .. } => { + return Err(WalletV3Error::AccountIsFrozen.into()) + } + ton_block::AccountState::AccountUninit => ( + InitData::from_key(public_key).with_wallet_id(WALLET_ID), + true, + ), + }; + + init_data.seqno += seqno_offset; + + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst: current_state.addr.clone(), + ..Default::default() + }); + + if with_state_init { + message.set_state_init(init_data.make_state_init()?); + } + + let expire_at = ExpireAt::new(clock, expiration); + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at.timestamp)?; + + Ok(TransferAction::Sign(Box::new(UnsignedWalletV3Message { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }))) +} + +#[derive(Clone)] +struct UnsignedWalletV3Message { + init_data: InitData, + gifts: Vec, + payload: BuilderData, + hash: UInt256, + expire_at: ExpireAt, + message: ton_block::Message, +} + +impl UnsignedMessage for UnsignedWalletV3Message { + fn refresh_timeout(&mut self, clock: &dyn Clock) { + if !self.expire_at.refresh(clock) { + return; + } + + let (hash, payload) = self + .init_data + .make_transfer_payload(self.gifts.clone(), self.expire_at()) + .trust_me(); + self.hash = hash; + self.payload = payload; + } + + fn expire_at(&self) -> u32 { + self.expire_at.timestamp + } + + fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, signature.len() * 8)?; + + let mut message = self.message.clone(); + message.set_body(SliceData::load_builder(payload)?); + + Ok(SignedMessage { + message, + expire_at: self.expire_at(), + }) + } + + fn sign_with_pruned_payload( + &self, + signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH], + prune_after_depth: u16, + ) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, signature.len() * 8)?; + let body = payload.into_cell()?; + + let mut message = self.message.clone(); + message.set_body(prune_deep_cells(&body, prune_after_depth)?); + + Ok(SignedMessage { + message, + expire_at: self.expire_at(), + }) + } +} + +pub static CODE_HASH: &[u8; 32] = &[ + 0x84, 0xda, 0xfa, 0x44, 0x9f, 0x98, 0xa6, 0x98, 0x77, 0x89, 0xba, 0x23, 0x23, 0x58, 0x07, 0x2b, + 0xc0, 0xf7, 0x6d, 0xc4, 0x52, 0x40, 0x02, 0xa5, 0xd0, 0x91, 0x8b, 0x9a, 0x75, 0xd2, 0xd5, 0x99, +]; + +pub fn is_wallet_v3(code_hash: &UInt256) -> bool { + code_hash.as_slice() == CODE_HASH +} + +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> MsgAddressInt { + InitData::from_key(public_key) + .with_wallet_id(WALLET_ID) + .compute_addr(workchain_id) + .trust_me() +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 TON + max_messages: MAX_MESSAGES, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +const MAX_MESSAGES: usize = 4; + +/// `WalletV3` init data +#[derive(Clone, Copy)] +pub struct InitData { + pub seqno: u32, + pub wallet_id: u32, + pub public_key: UInt256, +} + +impl InitData { + pub fn public_key(&self) -> &[u8; 32] { + self.public_key.as_slice() + } + + pub fn from_key(key: &PublicKey) -> Self { + Self { + seqno: 0, + wallet_id: 0, + public_key: key.as_bytes().into(), + } + } + + pub fn with_wallet_id(mut self, id: u32) -> Self { + self.wallet_id = id; + self + } + + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let init_state = self.make_state_init()?.serialize()?; + let hash = init_state.repr_hash(); + Ok(MsgAddressInt::AddrStd(MsgAddrStd { + anycast: None, + workchain_id, + address: hash.into(), + })) + } + + pub fn make_state_init(&self) -> Result { + Ok(ton_block::StateInit { + code: Some(nekoton_contracts::wallets::code::wallet_v3()), + data: Some(self.serialize()?), + ..Default::default() + }) + } + + pub fn serialize(&self) -> Result { + let mut data = BuilderData::new(); + data.append_u32(self.seqno)? + .append_u32(self.wallet_id)? + .append_raw(self.public_key.as_slice(), 256)?; + data.into_cell() + } + + pub fn make_transfer_payload( + &self, + gifts: impl IntoIterator, + expire_at: u32, + ) -> Result<(UInt256, BuilderData)> { + let mut payload = BuilderData::new(); + + // insert prefix + payload + .append_u32(self.wallet_id)? + .append_u32(expire_at)? + .append_u32(self.seqno)?; + + // create internal message + for gift in gifts { + let mut internal_message = + ton_block::Message::with_int_header(ton_block::InternalMessageHeader { + ihr_disabled: true, + bounce: gift.bounce, + dst: gift.destination, + value: ton_block::CurrencyCollection::from_grams(ton_block::Grams::new( + gift.amount, + )?), + ..Default::default() + }); + + if let Some(body) = gift.body { + internal_message.set_body(body); + } + + if let Some(state_init) = gift.state_init { + internal_message.set_state_init(state_init); + } + + // append it to the body + payload + .append_u8(gift.flags)? + .checked_append_reference(internal_message.serialize()?)?; + } + + let hash = payload.clone().into_cell()?.repr_hash(); + + Ok((hash, payload)) + } +} + +impl TryFrom<&Cell> for InitData { + type Error = anyhow::Error; + + fn try_from(data: &Cell) -> Result { + let mut cs = SliceData::load_cell_ref(data)?; + Ok(Self { + seqno: cs.get_next_u32()?, + wallet_id: cs.get_next_u32()?, + public_key: UInt256::from_be_bytes(&cs.get_next_bytes(32)?), + }) + } +} + +const WALLET_ID: u32 = 0x4BA92D8A; + +#[derive(thiserror::Error, Debug)] +enum WalletV3Error { + #[error("Invalid init data")] + InvalidInitData, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, +} diff --git a/src/utils/ton_wallet/wallet_v3v4.rs b/src/utils/ton_wallet/wallet_v3v4.rs new file mode 100644 index 0000000..bae849c --- /dev/null +++ b/src/utils/ton_wallet/wallet_v3v4.rs @@ -0,0 +1,435 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::PublicKey; +use ton_block::{MsgAddrStd, MsgAddressInt, Serializable}; +use ton_types::{BuilderData, Cell, IBitstring, SliceData, UInt256}; + +use nekoton_utils::*; + +use super::{Gift, TonWalletDetails, TransferAction}; +use crate::core::models::{Expiration, ExpireAt}; +use crate::crypto::{SignedMessage, UnsignedMessage}; + +pub fn prepare_deploy( + clock: &dyn Clock, + public_key: &PublicKey, + workchain: i8, + expiration: Expiration, + version: WalletVersion, +) -> Result> { + let init_data = InitData::from_key(public_key).with_subwallet_id(WALLET_ID); + let dst = compute_contract_address(public_key, workchain, version); + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst, + ..Default::default() + }); + + message.set_state_init(init_data.make_state_init(version)?); + + let expire_at = ExpireAt::new(clock, expiration); + let (hash, payload) = init_data.make_transfer_payload(None, expire_at.timestamp, version)?; + + Ok(Box::new(UnsignedWallet { + init_data, + gifts: Vec::new(), + payload, + message, + expire_at, + hash, + version, + })) +} + +pub fn prepare_state_init( + public_key: &PublicKey, + version: WalletVersion, +) -> Result { + let init_data = InitData::from_key(public_key).with_subwallet_id(WALLET_ID); + init_data.make_state_init(version) +} + +pub fn prepare_transfer( + clock: &dyn Clock, + public_key: &PublicKey, + current_state: &ton_block::AccountStuff, + seqno_offset: u32, + gifts: Vec, + expiration: Expiration, + version: WalletVersion, +) -> Result { + if gifts.len() > MAX_MESSAGES { + return Err(WalletV4Error::TooManyGifts.into()); + } + + let (mut init_data, with_state_init) = match ¤t_state.storage.state { + ton_block::AccountState::AccountActive { state_init, .. } => match &state_init.data { + Some(data) => (InitData::try_from(data)?, false), + None => return Err(WalletV4Error::InvalidInitData.into()), + }, + ton_block::AccountState::AccountFrozen { .. } => { + return Err(WalletV4Error::AccountIsFrozen.into()) + } + ton_block::AccountState::AccountUninit => ( + InitData::from_key(public_key).with_subwallet_id(WALLET_ID), + true, + ), + }; + + init_data.seqno += seqno_offset; + + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst: current_state.addr.clone(), + ..Default::default() + }); + + if with_state_init { + message.set_state_init(init_data.make_state_init(version)?); + } + + let expire_at = ExpireAt::new(clock, expiration); + let (hash, payload) = + init_data.make_transfer_payload(gifts.clone(), expire_at.timestamp, version)?; + + Ok(TransferAction::Sign(Box::new(UnsignedWallet { + init_data, + gifts, + payload, + hash, + expire_at, + message, + version, + }))) +} + +#[derive(Clone)] +struct UnsignedWallet { + init_data: InitData, + gifts: Vec, + payload: BuilderData, + hash: UInt256, + expire_at: ExpireAt, + message: ton_block::Message, + version: WalletVersion, +} + +impl UnsignedMessage for UnsignedWallet { + fn refresh_timeout(&mut self, clock: &dyn Clock) { + if !self.expire_at.refresh(clock) { + return; + } + + let (hash, payload) = self + .init_data + .make_transfer_payload(self.gifts.clone(), self.expire_at(), self.version) + .trust_me(); + self.hash = hash; + self.payload = payload; + } + + fn expire_at(&self) -> u32 { + self.expire_at.timestamp + } + + fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, signature.len() * 8)?; + + let mut message = self.message.clone(); + message.set_body(SliceData::load_builder(payload)?); + + Ok(SignedMessage { + message, + expire_at: self.expire_at(), + }) + } + + fn sign_with_pruned_payload( + &self, + signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH], + prune_after_depth: u16, + ) -> Result { + let mut payload = self.payload.clone(); + payload.append_raw(signature, signature.len() * 8)?; + let body = payload.into_cell()?; + + let mut message = self.message.clone(); + message.set_body(prune_deep_cells(&body, prune_after_depth)?); + + Ok(SignedMessage { + message, + expire_at: self.expire_at(), + }) + } +} + +pub static CODE_HASH_V3_R1: &[u8; 32] = &[ + 0xB6, 0x10, 0x41, 0xA5, 0x8A, 0x79, 0x80, 0xB9, 0x46, 0xE8, 0xFB, 0x9E, 0x19, 0x8E, 0x3C, 0x90, + 0x4D, 0x24, 0x79, 0x9F, 0xFA, 0x36, 0x57, 0x4E, 0xA4, 0x25, 0x1C, 0x41, 0xA5, 0x66, 0xF5, 0x81, +]; + +pub static CODE_HASH_V3_R2: &[u8; 32] = &[ + 0x84, 0xDA, 0xFA, 0x44, 0x9F, 0x98, 0xA6, 0x98, 0x77, 0x89, 0xBA, 0x23, 0x23, 0x58, 0x07, 0x2B, + 0xC0, 0xF7, 0x6D, 0xC4, 0x52, 0x40, 0x02, 0xA5, 0xD0, 0x91, 0x8B, 0x9A, 0x75, 0xD2, 0xD5, 0x99, +]; + +pub static CODE_HASH_V4_R1: &[u8; 32] = &[ + 0x64, 0xDD, 0x54, 0x80, 0x55, 0x22, 0xC5, 0xBE, 0x8A, 0x9D, 0xB5, 0x9C, 0xEA, 0x01, 0x05, 0xCC, + 0xF0, 0xD0, 0x87, 0x86, 0xCA, 0x79, 0xBE, 0xB8, 0xCB, 0x79, 0xE8, 0x80, 0xA8, 0xD7, 0x32, 0x2D, +]; + +pub static CODE_HASH_V4_R2: &[u8; 32] = &[ + 0xFE, 0xB5, 0xFF, 0x68, 0x20, 0xE2, 0xFF, 0x0D, 0x94, 0x83, 0xE7, 0xE0, 0xD6, 0x2C, 0x81, 0x7D, + 0x84, 0x67, 0x89, 0xFB, 0x4A, 0xE5, 0x80, 0xC8, 0x78, 0x86, 0x6D, 0x95, 0x9D, 0xAB, 0xD5, 0xC0, +]; + +pub fn is_wallet_v3r1(code_hash: &UInt256) -> bool { + code_hash.as_slice() == CODE_HASH_V3_R1 +} + +pub fn is_wallet_v3r2(code_hash: &UInt256) -> bool { + code_hash.as_slice() == CODE_HASH_V3_R2 +} + +pub fn is_wallet_v4r1(code_hash: &UInt256) -> bool { + code_hash.as_slice() == CODE_HASH_V4_R1 +} + +pub fn is_wallet_v4r2(code_hash: &UInt256) -> bool { + code_hash.as_slice() == CODE_HASH_V4_R2 +} + +pub fn compute_contract_address( + public_key: &PublicKey, + workchain_id: i8, + version: WalletVersion, +) -> MsgAddressInt { + InitData::from_key(public_key) + .with_subwallet_id(WALLET_ID) + .compute_addr(workchain_id, version) + .trust_me() +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 TON + max_messages: MAX_MESSAGES, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +const MAX_MESSAGES: usize = 4; + +/// `Default Wallet` init data +#[derive(Clone, Copy)] +pub struct InitData { + pub seqno: u32, + pub wallet_id: i32, + pub public_key: UInt256, +} + +impl InitData { + pub fn public_key(&self) -> &[u8; 32] { + self.public_key.as_slice() + } + + pub fn from_key(key: &PublicKey) -> Self { + Self { + seqno: 0, + wallet_id: 0, + public_key: key.as_bytes().into(), + } + } + + pub fn with_subwallet_id(mut self, id: i32) -> Self { + self.wallet_id = id; + self + } + + pub fn compute_addr(&self, workchain_id: i8, version: WalletVersion) -> Result { + let init_state = self.make_state_init(version)?.serialize()?; + let hash = init_state.repr_hash(); + Ok(MsgAddressInt::AddrStd(MsgAddrStd { + anycast: None, + workchain_id, + address: hash.into(), + })) + } + + pub fn make_state_init(&self, version: WalletVersion) -> Result { + let code = match version { + WalletVersion::V3R1 => nekoton_contracts::wallets::code::wallet_v3r1(), + WalletVersion::V3R2 => nekoton_contracts::wallets::code::wallet_v3r2(), + WalletVersion::V4R1 => nekoton_contracts::wallets::code::wallet_v4r1(), + WalletVersion::V4R2 => nekoton_contracts::wallets::code::wallet_v4r2(), + }; + + Ok(ton_block::StateInit { + code: Some(code), + data: Some(self.serialize(version)?), + ..Default::default() + }) + } + + pub fn serialize(&self, version: WalletVersion) -> Result { + let mut data = BuilderData::new(); + data.append_u32(self.seqno)? + .append_i32(self.wallet_id)? + .append_raw(self.public_key.as_slice(), 256)?; + + if matches!(version, WalletVersion::V4R1 | WalletVersion::V4R2) { + // empty plugin dict + data.append_bit_zero()?; + } + + data.into_cell() + } + + pub fn make_transfer_payload( + &self, + gifts: impl IntoIterator, + expire_at: u32, + version: WalletVersion, + ) -> Result<(UInt256, BuilderData)> { + let mut payload = BuilderData::new(); + + // insert prefix + payload + .append_i32(self.wallet_id)? + .append_u32(expire_at)? + .append_u32(self.seqno)?; + + // Opcode + if matches!(version, WalletVersion::V4R1 | WalletVersion::V4R2) { + payload.append_u8(0)?; + } + + for gift in gifts { + let mut internal_message = + ton_block::Message::with_int_header(ton_block::InternalMessageHeader { + ihr_disabled: true, + bounce: gift.bounce, + dst: gift.destination, + value: ton_block::CurrencyCollection::from_grams(ton_block::Grams::new( + gift.amount, + )?), + ..Default::default() + }); + + if let Some(body) = gift.body { + internal_message.set_body(body); + } + + if let Some(state_init) = gift.state_init { + internal_message.set_state_init(state_init); + } + + // append it to the body + payload + .append_u8(gift.flags)? + .checked_append_reference(internal_message.serialize()?)?; + } + + let hash = payload.clone().into_cell()?.repr_hash(); + + Ok((hash, payload)) + } +} + +impl TryFrom<&Cell> for InitData { + type Error = anyhow::Error; + + fn try_from(data: &Cell) -> Result { + let mut cs = SliceData::load_cell_ref(data)?; + Ok(Self { + seqno: cs.get_next_u32()?, + wallet_id: cs.get_next_i32()?, + public_key: UInt256::from_be_bytes(&cs.get_next_bytes(32)?), + }) + } +} + +const WALLET_ID: i32 = 0x29A9A317; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum WalletVersion { + V3R1, + V3R2, + V4R1, + V4R2, +} + +#[derive(thiserror::Error, Debug)] +enum WalletV4Error { + #[error("Invalid init data")] + InvalidInitData, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use ton_block::Deserializable; + use ton_types::UInt256; + + use nekoton_contracts::wallets; + + use crate::core::ton_wallet::wallet_v3v4::{ + is_wallet_v4r1, is_wallet_v4r2, InitData, WalletVersion, WALLET_ID, + }; + + #[test] + fn code_hash_v4r1() -> anyhow::Result<()> { + let code_cell = wallets::code::wallet_v4r1(); + + let is_wallet_v4r1 = is_wallet_v4r1(&code_cell.repr_hash()); + assert!(is_wallet_v4r1); + + Ok(()) + } + + #[test] + fn code_hash_v4r2() -> anyhow::Result<()> { + let code_cell = wallets::code::wallet_v4r2(); + + let is_wallet_v4r2 = is_wallet_v4r2(&code_cell.repr_hash()); + assert!(is_wallet_v4r2); + + Ok(()) + } + + #[test] + fn state_init_v4r2() -> anyhow::Result<()> { + let state_init_base64 = "te6ccgECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF2dW1vNw/It5bDWN3jVo5dxzZVk+Q11lVLs3LamPSWAVQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8SExQVAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNCAkCASAKCwB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAMDQBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDg8AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIBARABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVA=="; + + let state_init = ton_block::StateInit::construct_from_base64(state_init_base64)?; + + let init_data_clone = InitData { + seqno: 0, + wallet_id: WALLET_ID, + public_key: UInt256::from_str( + "6756d6f370fc8b796c358dde3568e5dc7365593e435d6554bb372da98f496015", + )?, + }; + + let state_init_clone = init_data_clone.make_state_init(WalletVersion::V4R2)?; + + assert_eq!(state_init, state_init_clone); + + Ok(()) + } +} diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs new file mode 100644 index 0000000..0ea1444 --- /dev/null +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -0,0 +1,468 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::PublicKey; +use ton_block::{MsgAddrStd, MsgAddressInt, Serializable}; +use ton_types::{BuilderData, Cell, IBitstring, SliceData, UInt256}; + +use nekoton_utils::*; + +use super::{Gift, TonWalletDetails, TransferAction}; +use crate::core::models::{Expiration, ExpireAt}; +use crate::crypto::{SignedMessage, UnsignedMessage}; + +const SIGNED_EXTERNAL_PREFIX: u32 = 0x7369676E; +const SIGNED_INTERNAL_PREFIX: u32 = 0x73696E74; + +pub fn prepare_deploy( + clock: &dyn Clock, + public_key: &PublicKey, + workchain: i8, + expiration: Expiration, +) -> Result> { + let init_data = make_init_data(public_key); + let dst = compute_contract_address(public_key, workchain); + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst, + ..Default::default() + }); + + message.set_state_init(init_data.make_state_init()?); + + let expire_at = ExpireAt::new(clock, expiration); + let (hash, payload) = init_data.make_transfer_payload(None, expire_at.timestamp, false)?; + + Ok(Box::new(UnsignedWalletV5 { + init_data, + gifts: Vec::new(), + payload, + message, + expire_at, + hash, + })) +} + +pub fn prepare_state_init(public_key: &PublicKey) -> Result { + let init_data = make_init_data(public_key); + init_data.make_state_init() +} + +pub fn make_init_data(public_key: &PublicKey) -> InitData { + InitData::from_key(public_key) + .with_wallet_id(WALLET_ID) + .with_is_signature_allowed(true) +} + +pub fn get_init_data( + current_state: &ton_block::AccountState, + public_key: &PublicKey, +) -> Result<(InitData, bool)> { + match current_state { + ton_block::AccountState::AccountActive { state_init, .. } => match &state_init.data { + Some(data) => Ok((InitData::try_from(data)?, false)), + None => Err(WalletV5Error::InvalidInitData.into()), + }, + ton_block::AccountState::AccountFrozen { .. } => Err(WalletV5Error::AccountIsFrozen.into()), + ton_block::AccountState::AccountUninit => Ok((make_init_data(public_key), true)), + } +} + +pub fn get_init_data_from_state_init(init: &ton_block::StateInit) -> Result { + match &init.data { + Some(data) => Ok(InitData::try_from(data)?), + None => Err(WalletV5Error::InvalidInitData.into()), + } +} + +pub fn prepare_transfer( + clock: &dyn Clock, + public_key: &PublicKey, + current_state: &ton_block::AccountStuff, + seqno_offset: u32, + gifts: Vec, + expiration: Expiration, +) -> Result { + if gifts.len() > MAX_MESSAGES { + return Err(WalletV5Error::TooManyGifts.into()); + } + let (mut init_data, with_state_init) = + get_init_data(current_state.storage.state(), public_key)?; + + init_data.seqno += seqno_offset; + + let mut message = + ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { + dst: current_state.addr.clone(), + ..Default::default() + }); + + if with_state_init { + message.set_state_init(init_data.make_state_init()?); + } + + let expire_at = ExpireAt::new(clock, expiration); + let (hash, payload) = + init_data.make_transfer_payload(gifts.clone(), expire_at.timestamp, false)?; + + Ok(TransferAction::Sign(Box::new(UnsignedWalletV5 { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }))) +} + +#[derive(Clone)] +struct UnsignedWalletV5 { + init_data: InitData, + gifts: Vec, + payload: BuilderData, + hash: UInt256, + expire_at: ExpireAt, + message: ton_block::Message, +} + +impl UnsignedMessage for UnsignedWalletV5 { + fn refresh_timeout(&mut self, clock: &dyn Clock) { + if !self.expire_at.refresh(clock) { + return; + } + + let (hash, payload) = self + .init_data + .make_transfer_payload(self.gifts.clone(), self.expire_at(), false) + .trust_me(); + self.hash = hash; + self.payload = payload; + } + + fn expire_at(&self) -> u32 { + self.expire_at.timestamp + } + + fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.append_raw(signature, signature.len() * 8)?; + + let mut message = self.message.clone(); + message.set_body(SliceData::load_builder(payload)?); + + Ok(SignedMessage { + message, + expire_at: self.expire_at(), + }) + } + + fn sign_with_pruned_payload( + &self, + signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH], + prune_after_depth: u16, + ) -> Result { + let mut payload = self.payload.clone(); + payload.append_raw(signature, signature.len() * 8)?; + let body = payload.into_cell()?; + + let mut message = self.message.clone(); + message.set_body(prune_deep_cells(&body, prune_after_depth)?); + + Ok(SignedMessage { + message, + expire_at: self.expire_at(), + }) + } +} + +pub static CODE_HASH: &[u8; 32] = &[ + 0x20, 0x83, 0x4b, 0x7b, 0x72, 0xb1, 0x12, 0x14, 0x7e, 0x1b, 0x2f, 0xb4, 0x57, 0xb8, 0x4e, 0x74, + 0xd1, 0xa3, 0x0f, 0x04, 0xf7, 0x37, 0xd4, 0xf6, 0x2a, 0x66, 0x8e, 0x95, 0x52, 0xd2, 0xb7, 0x2f, +]; + +pub fn is_wallet_v5r1(code_hash: &UInt256) -> bool { + code_hash.as_slice() == CODE_HASH +} + +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> MsgAddressInt { + make_init_data(public_key) + .compute_addr(workchain_id) + .trust_me() +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 TON + max_messages: MAX_MESSAGES, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +const MAX_MESSAGES: usize = 250; + +/// `WalletV5` init data +#[derive(Clone)] +pub struct InitData { + pub is_signature_allowed: bool, + pub seqno: u32, + pub wallet_id: u32, + pub public_key: UInt256, + pub extensions: Option, +} + +impl InitData { + pub fn public_key(&self) -> &[u8; 32] { + self.public_key.as_slice() + } + + pub fn from_key(key: &PublicKey) -> Self { + Self { + is_signature_allowed: false, + seqno: 0, + wallet_id: 0, + public_key: key.as_bytes().into(), + extensions: Default::default(), + } + } + + pub fn with_is_signature_allowed(mut self, is_allowed: bool) -> Self { + self.is_signature_allowed = is_allowed; + self + } + + pub fn with_wallet_id(mut self, id: u32) -> Self { + self.wallet_id = id; + self + } + + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let init_state = self.make_state_init()?.serialize()?; + let hash = init_state.repr_hash(); + Ok(MsgAddressInt::AddrStd(MsgAddrStd { + anycast: None, + workchain_id, + address: hash.into(), + })) + } + + pub fn make_state_init(&self) -> Result { + Ok(ton_block::StateInit { + code: Some(nekoton_contracts::wallets::code::wallet_v5r1()), + data: Some(self.serialize()?), + ..Default::default() + }) + } + + pub fn serialize(&self) -> Result { + let mut data = BuilderData::new(); + data.append_bit_bool(self.is_signature_allowed)? + .append_u32(self.seqno)? + .append_u32(self.wallet_id)? + .append_raw(self.public_key.as_slice(), 256)?; + + if let Some(extensions) = &self.extensions { + data.append_bit_one()? + .checked_append_reference(extensions.clone())?; + } else { + data.append_bit_zero()?; + } + + data.into_cell() + } + + pub fn make_transfer_payload( + &self, + gifts: impl IntoIterator, + expire_at: u32, + is_internal_flow: bool, + ) -> Result<(UInt256, BuilderData)> { + // Check if signatures are allowed + if !self.is_signature_allowed { + return if self.extensions.is_none() { + Err(WalletV5Error::WalletLocked.into()) + } else { + Err(WalletV5Error::SignaturesDisabled.into()) + }; + } + + let mut payload = BuilderData::new(); + + // insert prefix + if is_internal_flow { + payload.append_u32(SIGNED_INTERNAL_PREFIX)?; + } else { + payload.append_u32(SIGNED_EXTERNAL_PREFIX)?; + }; + + payload + .append_u32(self.wallet_id)? + .append_u32(expire_at)? + .append_u32(self.seqno)?; + + let mut actions = ton_block::OutActions::new(); + + for gift in gifts { + let mut internal_message = + ton_block::Message::with_int_header(ton_block::InternalMessageHeader { + ihr_disabled: true, + bounce: gift.bounce, + dst: gift.destination, + value: ton_block::CurrencyCollection::from_grams(ton_block::Grams::new( + gift.amount, + )?), + ..Default::default() + }); + + if let Some(body) = gift.body { + internal_message.set_body(body); + } + + if let Some(state_init) = gift.state_init { + internal_message.set_state_init(state_init); + } + + let action = ton_block::OutAction::SendMsg { + mode: gift.flags, + out_msg: internal_message, + }; + + actions.push_back(action); + } + + payload.append_bit_one()?; + payload.checked_append_reference(actions.serialize()?)?; + + // has_other_actions + payload.append_bit_zero()?; + + let hash = payload.clone().into_cell()?.repr_hash(); + + Ok((hash, payload)) + } +} + +impl TryFrom<&Cell> for InitData { + type Error = anyhow::Error; + + fn try_from(data: &Cell) -> Result { + let mut cs = SliceData::load_cell_ref(data)?; + Ok(Self { + is_signature_allowed: cs.get_next_bit()?, + seqno: cs.get_next_u32()?, + wallet_id: cs.get_next_u32()?, + public_key: UInt256::from_be_bytes(&cs.get_next_bytes(32)?), + extensions: cs.get_next_dictionary()?, + }) + } +} + +const WALLET_ID: u32 = 0x7FFFFF11; + +#[derive(thiserror::Error, Debug)] +enum WalletV5Error { + #[error("Invalid init data")] + InvalidInitData, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, + #[error("Signatures are disabled")] + SignaturesDisabled, + #[error("Wallet locked")] + WalletLocked, +} + +#[cfg(test)] +mod tests { + use crate::core::ton_wallet::wallet_v5r1::{ + compute_contract_address, is_wallet_v5r1, InitData, WALLET_ID, + }; + use crate::crypto::extend_with_signature_id; + use ed25519_dalek::{PublicKey, Signature, Verifier}; + use nekoton_contracts::wallets; + use ton_block::AccountState; + use ton_types::SliceData; + + #[test] + fn state_init() -> anyhow::Result<()> { + let cell = ton_types::deserialize_tree_of_cells(&mut base64::decode("te6ccgECFgEAAucAAm6ADZRqTnEksRaYvpXRMbgzB92SzFv/19WbfQQgdDo7lYwEWQnKBnPzD1AAAXPmjwdAEj9i9OgmAgEAUYAAAAG///+IyIPTKTihvw1MFdzCAl7NQWIaeY9xhjENsss4FdrN+FAgART/APSkE/S88sgLAwIBIAYEAQLyBQEeINcLH4IQc2lnbrry4Ip/EQIBSBAHAgEgCQgAGb5fD2omhAgKDrkPoCwCASANCgIBSAwLABGyYvtRNDXCgCAAF7Ml+1E0HHXIdcLH4AIBbg8OABmvHfaiaEAQ65DrhY/AABmtznaiaEAg65Drhf/AAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hIRAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEgP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKFRQTABCTW9sx4ddM0AByMNcsCCSOLSHy4JLSAO1E0NIAURO68tCPVFAwkTGcAYEBQNch1woA8uCO4sjKAFjPFsntVJPywI3iAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQ=").unwrap().as_slice()).unwrap(); + let state = nekoton_utils::deserialize_account_stuff(cell)?; + + 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_eq!( + init_data.public_key.to_hex_string(), + "9107a65271437e1a982bb98404bd9a82c434f31ee30c621b6596702bb59bf0a0" + ); + assert_eq!(init_data.wallet_id, WALLET_ID); + assert_eq!(init_data.extensions, None); + + let public_key = PublicKey::from_bytes(init_data.public_key.as_slice())?; + let address = compute_contract_address(&public_key, 0); + assert_eq!( + address.to_string(), + "0:6ca35273892588b4c5f4ae898dc1983eec9662dffebeacdbe82103a1d1dcac60" + ); + } + + Ok(()) + } + + #[test] + fn code_hash() -> anyhow::Result<()> { + let code_cell = wallets::code::wallet_v5r1(); + + let is_wallet_v5r1 = is_wallet_v5r1(&code_cell.repr_hash()); + assert!(is_wallet_v5r1); + + Ok(()) + } + + #[test] + fn check_signature_test() -> anyhow::Result<()> { + let public_key_bytes = + hex::decode("6c2f9514c1c0f2ec54cffe1ac2ba0e85268e76442c14205581ebc808fe7ee52c")?; + //let payload = base64::decode("te6ccgECCQEAAWMAASFzaW50f///EWjJNSIAAAABoAECCg7DyG0DBQIB80IAEiSxvuIkjLwTZ/69OCTi5io4ZpgjPKnD56XnecGH1Q0gcJ32yAAAAAAAAAAAAAAAAABz4iFDAAAAAAAAAAAAAAAAO5rKAIAfPq6ksCQX/kNfsY8xS5PTRd4WSjwjs5C/fod9ktFK+MAAAAAAAAAAAAAAAAD39JAwAwFDgBI2HlLkTtTC7ntWgsSS4jmXMUkhy2OTDHvAO1YAIIdyCAQBCAAAAAAIAgoOw8htAwgGAdNCABIksb7iJIy8E2f+vTgk4uYqOGaYIzypw+el53nBh9UNIC+vCAAAAAAAAAAAAAAAAAAARqnX7AAAAAAAAAAAAAAAAAIA9mKAH6YK7ZtGhTyJBnq9b54dnz07z830q8r/r5MBXJdSioIQBwFDgBhcpJ/VWhGKPK44GyznIrRqKDcoivK5/ZanRrMrFKCjiAgAAA==")?; + let payload = base64::decode("te6ccgECCQEAAaMAAaFzaW50f///EWjJNSIAAAABr9SYdbfeTOkhxaWVTsB40YIzxnswT6p7oxjydvTUZ0afi8fq5F2NvuyGho+YxBUC2NPkhtL3+tuMa5CfUwJMg2ABAgoOw8htAwUCAfNCABIksb7iJIy8E2f+vTgk4uYqOGaYIzypw+el53nBh9UNIHCd9sgAAAAAAAAAAAAAAAAAc+IhQwAAAAAAAAAAAAAAADuaygCAHz6upLAkF/5DX7GPMUuT00XeFko8I7OQv36HfZLRSvjAAAAAAAAAAAAAAAAA9/SQMAMBQ4ASNh5S5E7Uwu57VoLEkuI5lzFJIctjkwx7wDtWACCHcggEAQgAAAAACAIKDsPIbQMIBgHTQgASJLG+4iSMvBNn/r04JOLmKjhmmCM8qcPnped5wYfVDSAvrwgAAAAAAAAAAAAAAAAAAEap1+wAAAAAAAAAAAAAAAACAPZigB+mCu2bRoU8iQZ6vW+eHZ89O8/N9KvK/6+TAVyXUoqCEAcBQ4AYXKSf1VoRijyuOBss5yK0aig3KIryuf2Wp0azKxSgo4gIAAA=")?; + let in_msg_body = ton_types::deserialize_tree_of_cells(&mut payload.as_slice())?; + let in_msg_body_slice = SliceData::load_cell(in_msg_body)?; + + let public_key = PublicKey::from_bytes(public_key_bytes.as_slice())?; + + let result = check_signature(in_msg_body_slice, public_key, Some(2000))?; + assert!(result); + Ok(()) + } + + fn check_signature( + mut in_msg_body: SliceData, + public_key: PublicKey, + signature_id: Option, + ) -> anyhow::Result { + let signature_binding = in_msg_body + .get_slice(in_msg_body.remaining_bits() - 512, 512)? + .remaining_data(); + let sig = signature_binding.data(); + + let payload = in_msg_body + .shrink_data(in_msg_body.remaining_bits() - 512..) + .into_cell(); + + let hash = payload.repr_hash(); + + let data = extend_with_signature_id(hash.as_ref(), signature_id); + + Ok(public_key + .verify(&*data, &Signature::from_bytes(sig)?) + .is_ok()) + } +} diff --git a/src/utils/wallets/code/BridgeMultisigWallet.tvc b/src/utils/wallets/code/BridgeMultisigWallet.tvc new file mode 100644 index 0000000000000000000000000000000000000000..7d8917fbb963feba9091161ea78bcc5b9221b23f GIT binary patch literal 4368 zcmcIn4Nz3q6~6Z^WPuH^h(9Q#-22}0BWSY=OH5mp1(LM&C&n>Y8JH}I20JRe$B))# zQuje(CQT;mqS%^=HqbE2bZiDfArK6+LNhEO$rIO^jarv=`vZ2ybi4CxZ%}!?4<`5% z39=pU&YpYrzI*T6bI*6ab8g31N2?(7Bm{9`fPrQWgwV-=f-_*2hL}_Z$jyj#!h>?- zguJh8!-C9Rk@&pO%wyfm8^D7ocd7V}c)hbP9PvAX^1eMpyXV z07if=`y;gs@FbwGA*2HhZbbtRF(luQ$cta=gDr>YVDKfsI~S4y+>PEfBEr>b##-tk z5`ST7?Dif)WNU+H4hH;2cQPd0sbDwl1Caccok}-X#-D-knI;x41W9BH_b7 zl_VBBdn2xH@QKfsrk6%!u8v3gRrtNv#2QH+P}izEOgPLVa{slQJ~6Rx#23p<*t3PH z_EtpcEbakeg5Vkyc%9&{M|N?{8s@6gSI>L2s!lJ`rEQWf8S^YNlq>^R-IhCclXPWR zGSzC1bpNJOvS4Y^;YukFf9^+ddQdZ1c^woRor--K$#)GYq(IW;lBXmcAZy54@-*2% z){*tkJu{@xk9J5a0~rineb#Fu+u>Q(aLR5muzaQ~ldj63e_^Sl)27pJnxs=wX`do3 zG)OudC+T8YX+D;sfX=FeJd2w(MIw?5cqYfol64dYFWZ+u3Z-NfMk<)bEpD4T_M9gQ z@H*Q(P!rlwF*(^LhS{@dKGe2u>=|AFZ;-qSdj`eteFE1zJn5j_IU+LG!Hr^eT|{iY zC5vn@(c>>qzchC1_*1C8)L1PwHkYMFW2)cniiMzMMsG}d!WtfuE3~aE&;Bw2j;q!^ z_h6&bOP)Fy%sx~}%KYwRIjo3hpj$gko!R?`Qg|pP(;cy3W~Z!u`7re8 zw6;SOdVUqbV^$4CTQT^u=oZ@M9PEP#RD~Yv+E&-%D=eItYxtZSrvLm`RY$3`DT>mn z(v&p+?y&gom@wXQBnQ^aPEIN}^>n6b3igG<;zyJ2gS%7U-Vh1NY&6xQRQMmez_PBP z+&lcAjc2FA&|f=NOr7w|(dg(j@X!ATN1=l#visO)Z#kCEorK5p4xbx7Gj192MB7x& zZ5#1C3{>qbpkvy}G405?Ve+&S27S9!HkzcOH7XSmz@->m(thz%P%T-$fEnQZaYo!h zQ{frc7m%h#EajYPkY17E8l+k&-ap7*2chvW1Ak&!Mkyp`ITgn5!=a8qdgYY^S6&(G zii^`b99Cfr5eSf=t z^oXr~l-it+f^8wB@5qi#jeDFXX3jC~&Yh%t z^?;}8xj|*DdclwBd7Dgj@yjVF#~}OS8Pj@wxjMaUWfr`F_&5$@T-lM#o^0(0CfeGA zzU|a=*ns)?%0`x-*K7Y7<`=R2LXKZ@n>VLi$?}V5aweZ}9gwvwzW`V6K04Q~`%<2vL zc2X#jwHWVZTmD!Jl-3Td$+WvW=&K|kHKnSu4Iitj1!9|tr1AO=TecT*h1jYg8;}#O zd>rnvn{j5wx4@D%15`K~_d4OC(JuZq?EDYl9hv^|DXnct z;Dn`1H^(i_+^pXm&+9Nwo5t!7;Rg>4u0H~<2MbpmZH>NYgkKSAv0C&tsb;H1`fJ}+ zq=%N75Na^8h8{0!Z?J}6H2Me-R>7Cv#0B^_0+W)mvPu&B zT>I2(r{;B(PZ@1i;ziko%w%O5b-vMldqt307&WI0-Mz}W0`R+g-Z?|RZ-N~c1v~(b pv1h71*rNkZ=y3Rb*b|-XxV$OO2^_xD8g?=^Z=Py%_`bR8{}0sESG)iK literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/Multisig2.tvc b/src/utils/wallets/code/Multisig2.tvc new file mode 100644 index 0000000000000000000000000000000000000000..e37b2ecfe12bc34db456c2c7bc74a4eb6ff0993c GIT binary patch literal 4073 zcmc&%eNYtV8Gqls!&5;x5W$Ga?d~Z>P3R%`k>=uEK&>Jl2g`P_bi7tAofz16541X| z9Z`vP+R1eUjWfnrX`PA5m>Eb)r+(C`>5SkR?(pp34DCVh{^1|e&JBM&qT}`1J;C$56XkwHN!q^&M~e)U(rX+k36L57c;k}aj!JyQz+a0+ zIR4tB2p3Iu%}z2BLdh)d;pqCnz~(P0)W1Lbr!!1NjPSNFXb%$V6%WrISja^YHWg=IHqocrp5JIOw2IB3z9AnFs4Z!^rtVvvc-3&*C}k2wLb0 z6I}?_E76M>589*lIfm-T3lalC!?@E1Z-26u{7bk=BAhTV&l0^NL=5`a%E~!L7k|iX z8Foc3V`_)YRlDX!;}KhTb9p>^cf}hYT#9h7=A_Lv?6$$i=x`YnD-qH^XcxSqH4vou zj)ETlQNHhxc^n7(lMhlRaz4u#=~W+)W43UFL`g+*K3A$XG7hXDb#yyEwRAh6l5cU2 z9cgcL^O1S&qVk2eWyGD7n!@EtnOD3mA<2rj_s4HH6wXVfOC;Bz9VZvg$$SexR>HW| zg`|nE4LX7*JS@T39YWck+Jdo=7Id5pTE>i985g1x*G>9$9N-A`vv~IM%kS^O{4o1E za&K!qs{gAeB_3&Yz-VD+hS?BE$X;l_+aCx89eNcuJyivdKlx|0$KQVC!QxLTdv#l< z&*8K9OnAh6rasx(<2&F>yM2pPnU!{%|KM@zKYTm<)mMqNO3$acP=?8;v5pAgm1v+I z{~fR!S`(Ilug>)@%S&uP%XYtW+gtwjQ~&wLO8t!vd>6i1o4d_DE8d9t`6P2s3lEYC z)lfBaPY1Cq&*|xce7a8WX*qw`G`!>2 zp)i}6nMixILK&_mBr13_vDo9z;DI~j4!Nth^4Q&wH~pDQVZ9xTpK8Y;^FOh}n1IhQ zQDWo?sdz~?K7tLr(q>abJe1>NQdvhkEg=HEGk4|Bs@XGWo7roGC-qQqjYfwXCYu4Jm!iWvvC^EJ(c^!w86Dm;r|-_Gg1YXjvAgC{TwHNT|6^4LZf<8q8FE3=R3VNiA%Wu zxR?drOldu)uv&ofT9k^dNZb-=9I~AoaT`Dx5|j_2LK1QQzs!cFA*i@+XoHI7)O8vv zKIXbj_-OMd#xQtz<&0Y+Ie*-CFx5N+%|mX3CJZv&ryJ-E>Mc+)8cS07MqX`H-8N{3 zCh%b087R;Q4aoRiT3Mri(=ynu!j8zi3Z2VYf47gsdAsOi=V(02X`{c1N1ESaRI;{Yv+8y>x@~OEH>SH5fhSU*%0$~LX~X4kq0LFnRJR_+71I=o3&MbG#O)vQ2aj0LH10&53LXfi z;XXmvdE6=VvlPRR6AAH=HpVe|`_z+A(|esG>t*A^sh}x9X?qScc(y<}At=$|UwgCh zE3j8eN50(G`l8hrMR9K!wHKPk2N za0B;Ct!6ySR@$KYBz|j?g>`x39~cAt@78|yMeWU`_JQ(12DnuuxRK3+>%RHob`U66ITN;-5xjrH)9gnn$KVWcd~@3&=5q=ZIF=GMP5H z#pksGJrt*T28%^SQqkpbgHTR(({H(C#t~&gz=Na!Q5(}Z7Bt}}j$=pvbY25HU^|(s z{{rLYj-BJjI;yKkW;1KxZTYK<@@>?07Jq9rP#7PeEw}rh=-1M};O8n$he; zMfz@RxQ5oe2o$u|pb?V>khEVY;DSo1fO05(2P^>$iW*NTN7#Slz6Mo{F>OdR( EKlB4DE&u=k literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/Multisig2_1.tvc b/src/utils/wallets/code/Multisig2_1.tvc new file mode 100644 index 0000000000000000000000000000000000000000..2419b75ee2cbdb92b5e75288bea807286421593f GIT binary patch literal 4286 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 literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/SafeMultisigWallet.tvc b/src/utils/wallets/code/SafeMultisigWallet.tvc new file mode 100644 index 0000000000000000000000000000000000000000..758490719fc07d672b146b094db3a8fbb5f129f0 GIT binary patch literal 4352 zcmcgv4^R}>8GmmNJmdl#e^eALyL*ZVTK3=&+eY~pHEN7;P8=*uj;KMS!0zFnCJyly ztWKIt&O@O!jaEz;bsUp{QV@zVr#9m$F$*4@^Y3w9+i*;4=X$eFpLnu;3&uZFL651u zoBQ5(Z};uK@B4n=@B3c!KM$5eVhIFl5fD%}4nk-p2!%ifDCKS4x- z1jKrhw!(d4-R+O>=<^xz$ap0T#gpQZ5P%;|z7h&;l8L(WS{Lb9at@kj+9i9Y9bhTh zm3E~VBnG2WRDxJkhBl*3CiD&}M(fa8bCMX$p-mjxjIWlWQVx}Ih~*%kC5HRXI8+i^ z%b|4~D#k0^b^ukN)jX||0FGGl9)x(J#x5z~egfsVN%ZbLweNT>p7iZ-+cP1`rP|2D z42d+A3eWMXkhouD)wMbIlW1KHuk(4`j6EJA&1qj79=#APF0(kXB`IN8#D`-sDFTZ~ zH5h-Y@K`DdFSq>Zlbaptf-gUAU#Zhcq99tal*F^**#~ew+!?nsZb95&T*s8bDTfDR zcTVnz9gIEohr+l5Ye&eP6|UT0CKiPxFNh}9^QkZQ@LL7(g1l6Iz@SCfE1tibevXej z*XxRc8_g-4q3F|)qR|5bKoG~-E^@4jb61nm#Gsk?7=mn7>McOX zybK~4N&{JTlRcux5eit9X(A{I+*3q)nwB88$HhWr!^-yV$#4K=mzk3=a$gX#&hBkI zSsAXb3W>A?@a9wu>dp{=#*dLap_Bc+%u>2sF zCP|VSWL6SlMox-nWd!p$kTb#3=kpPcC2Vh*smR?q7Ro+2^#z!E7UagjKRQNRO(sFV zxx_%*Iy0Y&eH!(A)*IU^Jq6hLZygK^dkCInaP$9J8*nhjw;lTSQ_CFa4-`EE*|k00 zT?1LYjxdv~*-gEUSAeWt0eDV1G^gzC=|*o_q0dz!bI=e0EtLrffmK^_Y{G8-64)YS zeFuLGz8)a>O}OH9*}j9g0#ZWy=^9~|5LqKs3eoP1^nEZk_9fu!uQ8Z9exk)fmkKX5 zdlQZQd;52JT2rc14zyn1TfI8}9X_cprRu~-bN-qg5`nLu4avfFI=^rCUQ6F@us(Y> zga1DIY}nnT?mJ+u{I;-jUSFNM?~_ZdeRY=a|D|5v`hcHvpvYuR1M35=(>NAiHS6oP z`^i&_Cv5*><9ky7;eH-Vsef1ht^r~P;h*cDTan?@H55ZtDWnTba+?y+S*gC~ASNyDQ$ULzN`YEDMM zdaX8LsF~{Uxi)LMN+D-7N79a^HTi%3d3`P6SW*YwblLa$3{^>jHnN5x&xYVe;)7R- zo7f<TNF8FzD*@k#X0qyLQ<_sx&^qO zQwAJcy{6qsHtjP6yAa?$(?`}aB%j#k%)!a}VLe(l{i$T0KC}xtnTNGePYZ$Xhsa1z z)6;lQ3)`KkxnQm5g|hk~@KJvC21$Vc{z1`WccX})#|DCUcUAsXty*iHh=T{yP_<|k*bV-<)}dyB z!RfWr7!>Yos});D9mjUyI1~{+A!Nuh8ivQ4pg{FrhFcX?`@Ro=1JTliwnU=j65W69z51Uh(Yhv@?9{ znO;{E++a@T422(u6pkDj0D?HqcAjHZoV$jMCI-#C#}H(zRBw(-E~`}Ycy*JYilbPX z=!_GAtupV42MVepO3+p)_O|`bC}dK?#5$vpMO}20=Z$wkK4X>4vrx9JQ$jg{Dg!MP zQ~)hOOVR6SIeHB(d-IJ>34fKHN)d?Q{H>Kv)A(^92(5>g^qQ0AOnnxcWJ|&;6Y&;i z3Myp+{>>0nQam1~3lpXZDw9S~MbN@HN(coig9@@V)u33-lVT1_q&aEy8b(36c{0RE zijz?!V`(7MZn8)8I6?udGED%*fqRNbOVtv@w%8b`Y+TXaJqh|zR;f7=L-#o`>+Igj zla+74*+()vhn3h z?@fUNvQ2Nk*mQKFg}Z&JdyJ^aZI2U!Rlm6R+ni>dKIri~NVKd10x_Jd z(K?&W1UBx zBv1q_noh${8ewCzxAMl6+^fGmcvw1QIs^|(c#Jp{;Th{ud9Qh|czeA!ybf=V@xvG-Z0a@y_2b z+lQq|lB7nNmV|M`C&kk;f_W6k8DPn?2?@s%wjZCN$k{O((%v`aIjDLj>;%k01jcOa{- zr@Lz)v)2)3k~O=j*YOgNwMzicDTn5iy*=INbu09_ie(NOBA_KQ0U@w*bGA*W<}ZNF zLgshy=iuuBg5QWsUYG4Vh)WR?BCU2N#5Ifbyv-*ytnwo`sB)^AI$z+R!9WCemW!*7wWve>Rpz;YOpSA z7K8sj{H(8TR`>0-R(xB~Ik&G~-S^Rj*1men_y1O}YrV(M-dkw0rh;{W7HS-euUho= z+x+B-MdP=9vEd!5|3E*FrPN>5Uo}8%C;W5#bIQ~G>E&tuvTv*Rh_yP6}%4r;Y%)BV08pFzdxn2U2MHj=`xr;gE0DGUwS6)E~i{9;z0f^XCW z+QY2IElUn6b{mwB$Z7SgIOSYYURbpN(gRKM$N<{828AsU%{h4rljFv9j}#(TMZ|p zV4YSQKh#Wh_*|PbT_un+nj>k)lG?n#{JNoza4fEeZo2gQJcgCTB>K>rH_170i?zjrB=JO&F3;Apx;?$Y@+rvWfg#LLNi!(iz(HqCvqu+0xc)4 z(44Z;c;SD*G;rKR`Z;FpR;4;ME22+VSHXGy$I-1+idak0v|jVKEp+fm5cW zq!h%SvF(r#mx>R_h1gZkX5Q3XVyK}Ey;5&}j1AbN+|Yo|ZBOHlMd0(F^k|9JgN@fX s>KxclcWri14i&IMi^c7t9AR;moBBvAu((bu=%M7hce2Ui`td~nUlQkB`Tzg` literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/SetcodeMultisigWallet.tvc b/src/utils/wallets/code/SetcodeMultisigWallet.tvc new file mode 100644 index 0000000000000000000000000000000000000000..4e2eef2958de2ada00c7072025b21889f0fb6a1f GIT binary patch literal 6672 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 XN@T(z7x}6qCJe=+_=?}34ekFgPj|3j literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/SetcodeMultisigWallet24h.tvc b/src/utils/wallets/code/SetcodeMultisigWallet24h.tvc new file mode 100644 index 0000000000000000000000000000000000000000..b70936da4452ec6d0a5da8dd9235127196aa0633 GIT binary patch literal 6730 zcmc&&4Ommx89wI%O&~%7h=O3bKLJsyO(1ADD+FnE^$)>np|FOErJV}*25oEmv?fqZ zyLL@MadS`Yik3%h=Q@yKr7A;hXDn+YVjpUq0pnEM9=kM8+8%$(-FFjE`~d~(cJthO zPtLtJH|IO=`+o0t8ot?7im4?SjY&lm)=?N?Jryq^PYumB<8_|uQ?FlGkfZTz4)DW4 z(COLikKjgHeh>DDCg#FJN9guP4q&UUO>9eRLtlCV2oFuMP1Y6*yUgPcJ;h`>DJ4cC zkP@=9?0D&wO$TGO2m=weHV!{hV5QtGAr2;nVJt6LW?Fx}B7D%B$ zir`Ua8^SWQ+d8(4LhX_CMU0`Ns!jC6>nSqbNt358s@&T*K&SgfXImO38B8Pnu7)OD znP1oTa(VRg`szl>5Co|qa=tEMsUSaNgr zcn*G-a=;vYprbPc3)W=8x4kJZK)2ca1;KV$;1z}j%T;E0 zxX!MKWq6iqiS-~`c}_v!-?J-<7`wL2Z)4+cjdnd_AHBh7PiH=N(ua+gQJ$t*RO#_i zR=wA%SF?PgEzzz>n*cvI-mZ`(z&_5d5aZz-XCI}uD@>eSp=9kN8G8^?7!;UinHs-b zGwsRXsT2oG=D-xcWF3P6JYKv=$ho9%Gqov?wFeOVm0f|*bHa7pb;fntb;@QNcO7zlx^JuL{r6n2>rbi+PVIBOO;NZjCY>$Zcc%Gp1a-w~{)!=)5?*QFXaM^# zB8dsH9{*gP!tDwfFRv_iGVab5>_x=z5g)rdpQrgSlGWtyTtyJa@S(zj7Re@a$lNE)_Rtm)KC+fr*kvB%L5D9qY9dc#S?z;IUx>urB)!DC^8Z0- z2brR7)6MbpX-7)4&oTI>H1`nokX&Z{{6pmH%6wlgN-49(vZ9Gr+P!$DvV{PYwq(N4tws=xR7)M4SLRVd~u!ZoBx!n4^- z@9<_qi#1wG>$gsXrb#&dU7uzN($fMjm(0rjz|I#XZ!s0TMS>0dL>l-BS&v-Bf?SCp zS0c-^xd2iRk_1a`kY6_klC6+3@k;}~|E8Dw2=1@JRS@J%2+2JY>}B?F=VA6TG6eiYvi@KS4*WQb z`#53pFg2<@So984?4S?f^)PftuTi)bfL5V*Om1t$_W zVZsDnPBPVCB?f`s+!;T^R{*dX1l)0ti?l}dOPzOmXyapz6C7j>pH1`OY~Mf0V19v? zWODe-Jx}^^hfRBSn3xmiBQ<0`Wg9^SNe;OR-rNSXS z4YxG75{&2HI{!-7?$A(GMaAx~->O`c^Q1X)BWxeMHu0b7awXicU#^F$CgTTQ*C$KLeb+9LsMjgmd}NyeFG+v38E!PX#v8181kuLQN=hJ?Wx z58>`%LDyFq0#0Z;396)LLZT|< zxZ6~0+>jE{WL**iujJzPIJ8~;da7Tbd3R9Y5v3sj@HemQ#9hYUEULy^14{)x)WA{w zR`k9SxQOOMZvnrjr7~E}@ZrX4mJe%sY&PC$eGF<{mLJ9OV{Xb|EFaZh4&x&)BQl%i zBN*cW@d=^BX53k`DBuV#kgI(hADd=*i z!SA-{os0Cht}xvE`rtFJ%8l&Ci;TR96=!J{TaIdsBJ5x5kCllV#kIVk{V~p=|rCs+=z$uh~Gg zKT?gIY{}Ui4O2#=M&J{@Uk-}~FJx0r04kZyF{{|^e=0*_ZG%j!H7gsv**;-hsj-fE zr?d>2>$GGXuWB$QJ7{hpWAG!{bTce;G86B+35VnRK_LfDDp)w7d29g=vYJo(AAb}- zM`p+SAX)8&aZ&jA#6@9?B6tPEjK4~Ug?Et+i;(T^vVl<=lqWR!o9onA zQVYvK%YQ>FmTp#TAqS8AJp*e(v^k;Ck&7*`CfcvG-kGl6^EkdzLuRXQkeIkW5~IFr z)!>wrWR}iDAypc6{2oUVIHQ7J;>){sJ;M!eZQAb1kC8TRdSqX_bgMRwiAhSzk3L{q zEv;Ysy+%I)f_i)1PhLoQmr;U_S6ll?44=3#Xi!VZxALI|)HLAA^-o~vsN;oCVsWO@ zvQ25()nWNthoxOMN&%re-bAOe-$lT;=h#z?xYge z1umcxD8)Nnr?aU=eeQ58*L?$w=XsbP~3Dm}} zZ6mO@y0*t^Ww&~cwpgq6-2inB<+O=L5B3!@SS_AYET-vt#p>)GK*iQa>DfJ-Irq+= zJCA$k|Nj5`zq|kP+vSj$1wr^nfPgv?gwRd^g9|+5M-sz%;IAWTJFN9@q=>j=E{)T2t!<)%L`O5@Xb zm&hebQQ~G!QKl$I?9U}2DGP*t-b{5)a*?gm8=+0-=G_@?fP3CA_KV#T+>~xft4xB* zQ5j+pgGx~eDn`rEGBa9)icle1Y}5J!ShS2q%kh;GRKlXt62!2GWlb%=aNO(IpLsAFf9H0C+|wnLs;ROq$ne7bXPB}A&DzC-xj1@Whi48~oI9CzU% z@chBWlP{a3qfb9Y#C(-1-{UojpM9^3YSgL4L*5Cjmd00~YSNEi+FjUPfDdzb0Y7aR z)4iyB3#XTJ%#{4vnr~+DTmJXAcvh%i7mCFsg^=gg7 zxsLqeb#ea}DKzxeB-Ro^Chsbl{%@7mF|UjJ=E0e7tHErNg@v6*z?u?zO*A@B#A zmDKLF%6HM@^@vsXyl{aUG4ypY?dpv0G?EPVY-AT<5_c)EJI6vv{e;u2BjHVyS!!GH z|A>1Jn&PV{i-KEG(XB6aOHQH5WNEFM^`I>LkxM8hv=NY-)c9;6k9d6El?ddstcnT4 zaEACk6jEs!&4jXBjPMY-QB60Ho@nr)O=A@IyM;uvDj~oQuF-gG5k_s@7z)kGbF@h2 z3&RFf(x$HKP#eRK)KE2aJ1VEjv9*bMkEpBIC2mJ$YE;_iHgNXi_hStcLl@K?6_5L9 zVS$u1tSzV#xvD1)K0bJQea86>>vcN&n(E--axLf3?#S#PGomfrr|H@gf8E#E&|HC= zw$bVMsjKHLM-m6I06=t@$gtZtl!rcQtx`d z@&xq6(k1yQ(fdO~_~BkJcY@B0E!uf8v3l~>p&^E?kvA7FCOnf2(+2GWnH^S-_?gMQ z;dkxMBcFDBpO7QFh6I5O=e|htL6BL;09nqKimbYuiFsZlyw@*AYdr4-^ z+lH+pYtR21Eb_+lT~7Grfe|9Znm%Xd$1JeF9Xm0C41J8+VYRGq`cL{B@WJ~VZdU8s z{7pZPX|%*Nww-_Vo5*vjtf>BPzn2YTW7E^~V;aAC#PWJ@@RDgH4KLb?O|+vW<1R+f zQV%DDKfne$wWebMraaZdoEm6%ox87730Rtx2siJGe{Leq)7q|3&ndQqA&_c+MrBCY zb_=?SPBoOIM%gVxT1dXMx|h5#Pa^pHHgH(jmK zUi24mB+Dk6#xqhC%E=o5w(~KR23#xhQO?*D_=`}BY|;0Pr@!OM z*c(pmxCYfRxg--o-(DGG#GyeCd~%a$E^P|H2{RfL*+xA;CIn#=1et*2wpSwb)H4jx zlr-E;P~$FL1NQ9iN-Ddw;nGVT2h(cOHXrS<~|i&usaHMJ(v!l#$$h~da&*n04oq!*!PM7Lo;>^unFow2%J9* zkEF-V@XbF?JtkbAjjAIavep#9Ay7k(19sb!3-2pka1alSV#eyyjbV5W-4Tzbqf3Dl zrT~o8?8>iyFH(OZw)UtoK_Fr^7JXlZ*ek}0NG9wzKrIz$g`k*-I+_VTZWxcnZzdLA zG&5Ai47&&0&`h+zH%!!ZfM(Ln5UOmIYUl3lIKh|qDpjoFiXR2XZF4CmjJlm>G-T%B zuf@|a#&mq6YM`pPH|LAnO?|*FKX9^OUtcXgUj6h+-HM6T<9=>3UBH&E?ds1>AW^U^ zcN%O@X$<$}www0}^}25Qcw2u1GhoSg`F-eP3aZHcx~kh?W(JLc;pn&DMaMV20m z+~q1)31rJ`VRY+^jd^GPT=xdiI(-{#r%Ny9si{g52;z@ykYa_nI)kVeICQ2_y^7ws zrxNh>`z0DpLx9hVS=4APFWXDKr7o`omc3{M6RPzXiQ;sWOWL80e4^~Ldq6Z21Ej(Q zLCzZVFvw;(Fa9zUB0NDs4_4a*tbRkrB&%Xg$@3zNTjqt&8^Xx)jn#cI@n49CEO7XL z77wC$QwZ$ppXNe3SRb@s8n`_A6&J9Zg~TNrl01{(P1}ogXlBA)nUK&U(S%IswH~;8 z8~AF7abvVb8Z*b_BcAlhAf%T(2H{FFeJX4_aNrr$@jj92^yf#O8G!h7tP%9A=oOV! ze?LL{-GWuGyl^o3mEg1bO?C&H;hhGK+UDF<04<9IUE*-oCKpwWrv!Z$L|g|qMmye+t6Y2bwb$xbZpNx-Qik>0pMqw; z6rf2y6doO(nzoLP#8%Wyu=&?}ER^MF;gJE;1e)TM0W<^6MA>K-nvHVM<7f`beah0p zxA1=82Q3oZlr54LNpt5DNZ|uu=2T*|yg(I4isX&4th_OfJ$2)(ChnWitRzZ-1=80o Jadst$`Vae(n4kav literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/ever_wallet_code.boc b/src/utils/wallets/code/ever_wallet_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..5a5b0cc9c6b26029c781055153cab51f0be13b34 GIT binary patch literal 267 zcmdn`ZcdRSBO4>b9|lH|{|sN22!Gl0=>#_;6QcqXGXu*d1Ey!63a>LV9$@%X)y#2S z;;qYtX7=lh*SYOa{LnZrrhH!Pkcz_$vp_O|!n-~_NV(VGaGkM10fIxsN`DG` zTJb}9_ovFVeLs{N**O+8GQU_6q_Ah>rxGTGCIO#!DLK0y^D~|}{r|N6$+saatPC>@ zn6CR=_~p>dcAXJudpC1eb4FW8ym_#<7IL#WsdR{Dm`4`jaOr;Bo=M5FFdoF0< z+&ROzfbld##Cfsl28BR|(3wXk8Gkb{*fj}=&9f8S)hGCafoZbBb+73_1FtasW_WRN Ove74-M|Njh_5%QGjB^zL literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/highload_wallet_v2_code.boc b/src/utils/wallets/code/highload_wallet_v2_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..7e9ced8707b20da4ce224c49ad0bb38c10d90408 GIT binary patch literal 240 zcmV(x zQy{tWW9>xH(;w46)Bp4Y(Nkc6K=cl8p)vAcQFFTTq6hf_R1k;x5b~n}^aRoP0Dq1a zA%GC{cyFa3m;%!W)Gz`20Fg4{0kh^?gC(KJKs10r^h1i~F~}0j9}~+z%m4HM$?a4F z05l+gK=hVxr7}?vlrU2Rx!xd?Gd2N|GBDz^0s%+}1p)ye2L=E^zF(JWCWe^#j+y?S q`H!F`ijf2Y2cUC(35=54{FU7w6aW{!oabsLhMM_~xL^DL1kf;vBxwZz literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/mod.rs b/src/utils/wallets/code/mod.rs new file mode 100644 index 0000000..0e65b05 --- /dev/null +++ b/src/utils/wallets/code/mod.rs @@ -0,0 +1,34 @@ +use ton_types::Cell; + +macro_rules! declare_tvc { + ($($contract:ident => $source:literal ($const_bytes:ident)),*$(,)?) => {$( + const $const_bytes: &[u8] = include_bytes!($source); + + pub fn $contract() -> Cell { + load($const_bytes) + } + )*}; +} + +declare_tvc! { + safe_multisig_wallet => "./SafeMultisigWallet.tvc" (SAFE_MULTISIG_WALLET_CODE), + safe_multisig_wallet_24h => "./SafeMultisigWallet24h.tvc" (SAFE_MULTISIG_WALLET24H_CODE), + setcode_multisig_wallet => "./SetcodeMultisigWallet.tvc" (SETCODE_MULTISIG_WALLET_CODE), + setcode_multisig_wallet_24h => "./SetcodeMultisigWallet24h.tvc" (SETCODE_MULTISIG_WALLET24H_CODE), + bridge_multisig_wallet => "./BridgeMultisigWallet.tvc" (BRIDGE_MULTISIG_WALLET_CODE), + multisig2 => "./Multisig2.tvc" (MULTISIG2_CODE), + multisig2_1 => "./Multisig2_1.tvc" (MULTISIG2_1_CODE), + surf_wallet => "./Surf.tvc" (SURF_WALLET_CODE), + wallet_v3 => "./wallet_v3_code.boc" (WALLET_V3_CODE), + wallet_v3r1 => "./wallet_v3r1_code.boc" (WALLET_V3R1_CODE), + wallet_v3r2 => "./wallet_v3r2_code.boc" (WALLET_V3R2_CODE), + wallet_v4r1 => "./wallet_v4r1_code.boc" (WALLET_V4R1_CODE), + wallet_v4r2 => "./wallet_v4r2_code.boc" (WALLET_V4R2_CODE), + wallet_v5r1 => "./wallet_v5r1_code.boc" (WALLET_V5R1_CODE), + highload_wallet_v2 => "./highload_wallet_v2_code.boc" (HIGHLOAD_WALLET_V2_CODE), + ever_wallet => "./ever_wallet_code.boc" (EVER_WALLET_CODE), +} + +fn load(mut data: &[u8]) -> Cell { + ton_types::deserialize_tree_of_cells(&mut data).expect("Trust me") +} diff --git a/src/utils/wallets/code/wallet_v3_code.boc b/src/utils/wallets/code/wallet_v3_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..2600b43d29e98fae95eb11f796479db59934f9d7 GIT binary patch literal 124 zcmdn`ZcY&+5HJ)nFx>mkpm0~AiP2~JF2yEB<2k!F&M(~X*5$%w`OET#*SY^cW_hsW zQ$jPxbqNKK&}I1_%EG%pB|}wQ{(mvhXxFEOfiAl~En@k}7$Ok*Q{dC$9}JTfu6s?p Y%zlOGH^YmIj7v_OmIuQBC*Otu00$*KtN;K2 literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/wallet_v3r1_code.boc b/src/utils/wallets/code/wallet_v3r1_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..e5cea099a044f9609a978eead53422d00b92c000 GIT binary patch literal 109 zcmdn`ZcY&+5HKV$FdX>Lpm0~AiP2~JuIUDET`pYbmVdD1Q$jPxbqR&b@*wy_S$OxS zWS|t7`~PB~(XLMm16_7~TEy~`F+?Err@*JhKNuz}T=$xGnf(gWZ-y5a8JCmkpm0~AiP2~JF2yEB<2k!F&M(~X*5$%w`OET#*SY^cW_hsW zQ$jPxbqNKK&}I1_%EG%pB|}wQ{(mvhXxFEOfiAl~En@k}7$Ok*Q{dC$9}JTfu6s?p Y%zlOGH^YmIj7v_OmIuQBC*Otu00$*KtN;K2 literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/wallet_v4r1_code.boc b/src/utils/wallets/code/wallet_v4r1_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..542d420ca47f234c94593c4e194a46a9282b2742 GIT binary patch literal 769 zcmY+CUr19?9LIm>>|VE~%j-7BBx-kSCeh0beY9Ol=nnuL}}=)+~kcK5K(-4+su!}c{&%MZJFFi}EGq?;pj+`}) z@?eeQJfOHL;>hb%ids)Bq|%}{7*%^IluE16_`EFs5%`s`G z*k0=tjdbR9_FA`S9+(EThH^Nh1s9Tm3Tsuf$2I~Xvx_BcN7Cd{$+!S|a+}_iBTn!X zJN$J&|JB)LY$RSom4_248d#{gjXv&X6hi}!x64MBU-M^{O0!e?(2*(5 z_~>ElQs*2-{kp@@@*#0Qb43@}C_>NX%d)$R7hK)qG-`s)(=$><3c`?Zwg*4oxjaM< zY388q&DXmtw*-E4{`Z4Q9NOL<{X^4nfifDGzS`jZ>QSef;XrU?!o6d;r{Vi}Vp)Lmk z33Y`jqkeX%%&IR-(6K5j-ae6RK{5JR4N`mkr6!cM@<~`44J>l`i%+uF9In8wP~elO z2z(M30VSygq3EDY+NpRIgKc)U;GGQ0WGVjUdOSoIeEAEXIrD(CUVes+jNZDo<(pxL zCZgk8^Xz6I!CrI{X{*zjeYS8TAjY!NR(F)Uux&DAA^8P^fpo$tdlvhJnV{AGOyE>q3meNL1S0uHzPEn9T04YQ#g-57LRf z0fJi{$HT*&b2hu>ebWnmE|Rm^dRG^xjx99q%%52*&rUjH2PawAgZr&ZU2_->G&-R9 zUFvS`vLmulg6_@dmB2|(lupT0s1Y_#Osj?(g+cLbH-5T%X^@!gZ-Ta0UvIBm7rBx7 zpZAO;w7ov`o0P0sK2E~ecdHJ$oUaTFNw?LE_f9nCkrhV4zO-9nbnH?_oZH)}i3-S~ z%bY!+u3?CfQ{JNkiM*fdtzXZuW8vu4$X@};LKJ9IVc7h7zo~&?^dBkob(mmKU8}|R RA7ltf)njwkK(JR+{sYpxENTD% literal 0 HcmV?d00001 diff --git a/src/utils/wallets/code/wallet_v5r1_code.boc b/src/utils/wallets/code/wallet_v5r1_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..56c9a34f87d3b9fbc895cc06d4e864c1e0f20f76 GIT binary patch literal 657 zcmXw0Ur19?7(eH1b89Piox>WaJ6AJ&P`HB%0v{&%P~pQg17VO*M1PPqOrq|D`~gTkeoy^$qxP`Gh5(z`U9 z=(6a4?-t~_7-b^XdYM=VN`;nueW69_TvEb%u0@OEW+oc8A-hp}{b@jvwqijhMh7;U z^Z)?FNo-z`^k_zK1Ga5Hn38=2N9+#dd-kb+`bv;y$Fd`PD)K$A+J;mx(@_;TI$h{s zvTnESXk}ljZ+TTmWiKy4c5FFDk?&2H``VFVhqH9v{iFdCUW%*DDNOR0BMV%=H{ixU zNJB=Tqz&kUsAqYr+(R`D*^K@)lRW^|+Ji(uI{F^ORnBZ+QN zZY3f+Aw?&|Rg{z)F{`Kj)hD|_7}de`amvo9r0A`|rtQAV|%+GGmuze9; z;S&OvJC{i#tRj#-%NiVBszfI-1eKmlyYS+>8=94Q%F!e)1o8hJ0cqvmjt&_%>tmC1 z&kn!`0x^P { + static ONCE: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE.get_or_init(|| { + tycho_types::abi::Function::builder($crate::utils::wallets::ever_wallet::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) + .with_headers($crate::utils::wallets::ever_wallet::utils::declare_function!(@header $($($header),+)?)) + .with_inputs($inputs) + .with_outputs($outputs) + .build() + }) + }; + + (@abi_version) => { tycho_types::abi::AbiVersion::V2_2 }; + (@abi_version v2_0) => { tycho_types::abi::AbiVersion::V2_0 }; + (@abi_version v2_1) => { tycho_types::abi::AbiVersion::V2_1 }; + (@abi_version v2_2) => { tycho_types::abi::AbiVersion::V2_2 }; + (@abi_version v2_3) => { tycho_types::abi::AbiVersion::V2_3 }; + (@abi_version v2_7) => { tycho_types::abi::AbiVersion::V2_7 }; + + (@header) => { Vec::new() }; + (@header $($header:ident),+) => { + vec![$($crate::utils::wallets::ever_wallet::utils::declare_function!(@header_item $header)),+] + }; + (@header_item pubkey) => { + tycho_types::abi::AbiHeaderType::Pubkey + }; + (@header_item time) => { + tycho_types::abi::AbiHeaderType::Time + }; + (@header_item expire) => { + tycho_types::abi::AbiHeaderType::Expire + }; + } + + pub(crate) use declare_function; +} + + +pub fn send_transaction() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "sendTransaction", + inputs: vec![ + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Uint(8).named("flags"), + AbiType::Cell.named("payload"), + ], + outputs: Vec::new(), + } +} + +macro_rules! declare_send_transaction_raw { + ($($name:ident => [$($inputs:tt)*]),*,) => { + $(pub fn $name() -> &'static Function { + declare_function! { + abi: v2_3, + function_id: 0x169e3e11, + header: [pubkey, time, expire], + name: "sendTransactionRaw", + inputs: declare_send_transaction_raw!(@inputs [$($inputs)*] []), + outputs: Vec::new(), + } + })* + }; + + (@inputs [] [$($inputs:tt)*]) => { + vec![$($inputs)*] + }; + (@inputs [$(,)? _ $($rest:tt)*] [$($inputs:tt)*]) => { + declare_send_transaction_raw!(@inputs [$($rest)*] [ + $($inputs)* + tycho_types::abi::AbiType::Uint(8).named("flags"), + tycho_types::abi::AbiType::Cell.named("message"), + ]) + }; +} + +declare_send_transaction_raw! { + send_transaction_raw_0 => [], + send_transaction_raw_1 => [_], + send_transaction_raw_2 => [_, _], + send_transaction_raw_3 => [_, _, _], + send_transaction_raw_4 => [_, _, _, _], +} diff --git a/src/utils/wallets/mod.rs b/src/utils/wallets/mod.rs new file mode 100644 index 0000000..10c4b1a --- /dev/null +++ b/src/utils/wallets/mod.rs @@ -0,0 +1,5 @@ +pub mod code; +pub mod ever_wallet; +pub mod multisig; +pub mod multisig2; +pub mod notifications; diff --git a/src/utils/wallets/multisig.rs b/src/utils/wallets/multisig.rs new file mode 100644 index 0000000..e356240 --- /dev/null +++ b/src/utils/wallets/multisig.rs @@ -0,0 +1,192 @@ +use nekoton_abi::*; +use ton_abi::{Param, ParamType}; + +use crate::utils::declare_function; + +pub fn constructor() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "constructor", + inputs: vec![ + Param::new("owners", ParamType::Array(Box::new(ParamType::Uint(256)))), + Param::new("reqConfirms", ParamType::Uint(8)), + ], + outputs: Vec::new(), + } +} + +pub fn send_transaction() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "sendTransaction", + inputs: vec![ + Param::new("dest", ParamType::Address), + Param::new("value", ParamType::Uint(128)), + Param::new("bounce", ParamType::Bool), + Param::new("flags", ParamType::Uint(8)), + Param::new("payload", ParamType::Cell), + ], + outputs: Vec::new(), + } +} + +pub fn submit_transaction() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "submitTransaction", + inputs: vec![ + Param::new("dest", ParamType::Address), + Param::new("value", ParamType::Uint(128)), + Param::new("bounce", ParamType::Bool), + Param::new("allBalance", ParamType::Bool), + Param::new("payload", ParamType::Cell) + ], + outputs: vec![Param::new("transId", ParamType::Uint(64))], + } +} + +pub fn confirm_transaction() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "confirmTransaction", + inputs: vec![Param::new("transactionId", ParamType::Uint(64))], + outputs: Vec::new(), + } +} + +#[derive(Debug, UnpackAbi, KnownParamType)] +pub struct MultisigTransaction { + #[abi(uint64)] + pub id: u64, + #[abi(uint32, name = "confirmationsMask")] + pub confirmation_mask: u32, + #[abi(uint8, name = "signsRequired")] + pub signs_required: u8, + #[abi(uint8, name = "signsReceived")] + pub signs_received: u8, + #[abi(uint256)] + pub creator: ton_types::UInt256, + #[abi(uint8)] + pub index: u8, + #[abi(address)] + pub dest: ton_block::MsgAddressInt, + #[abi(uint128)] + pub value: u128, + #[abi(uint16, name = "sendFlags")] + pub send_flags: u16, + #[abi(cell)] + pub payload: ton_types::Cell, + #[abi(bool)] + pub bounce: bool, +} + +pub fn get_transactions() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "getTransactions", + inputs: Vec::new(), + outputs: vec![ + Param::new("transactions", ParamType::Array(Box::new(MultisigTransaction::param_type()))) + ] + } +} + +#[derive(Debug, UnpackAbi, KnownParamType, Copy, Clone)] +pub struct MultisigCustodian { + #[abi(uint8)] + pub index: u8, + #[abi(uint256)] + pub pubkey: ton_types::UInt256, +} + +pub fn get_custodians() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "getCustodians", + inputs: Vec::new(), + outputs: vec![ + Param::new("custodians", ParamType::Array(Box::new(MultisigCustodian::param_type()))) + ] + } +} + +pub mod safe_multisig { + use super::*; + + #[derive(Debug, Clone, Copy, UnpackAbiPlain, KnownParamTypePlain)] + pub struct SafeMultisigParams { + #[abi(uint8, name = "maxQueuedTransactions")] + pub max_queued_transactions: u8, + #[abi(uint8, name = "maxCustodianCount")] + pub max_custodian_count: u8, + #[abi(uint64, name = "expirationTime")] + pub expiration_time: u64, + #[abi(uint128, name = "minValue")] + pub min_value: u128, + #[abi(uint8, name = "requiredTxnConfirms")] + pub required_txn_confirms: u8, + } + + pub fn get_parameters() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "getParameters", + inputs: Vec::new(), + outputs: SafeMultisigParams::param_type(), + } + } +} + +pub mod set_code_multisig { + use super::*; + + #[derive(Debug, Clone, Copy, UnpackAbiPlain, KnownParamTypePlain)] + pub struct SetCodeMultisigParams { + #[abi(uint8, name = "maxQueuedTransactions")] + pub max_queued_transactions: u8, + #[abi(uint8, name = "maxCustodianCount")] + pub max_custodian_count: u8, + #[abi(uint64, name = "expirationTime")] + pub expiration_time: u64, + #[abi(uint128, name = "minValue")] + pub min_value: u128, + #[abi(uint8, name = "requiredTxnConfirms")] + pub required_txn_confirms: u8, + #[abi(uint8, name = "requiredUpdConfirms")] + pub required_upd_confirms: u8, + } + + pub fn get_parameters() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "getParameters", + inputs: Vec::new(), + outputs: SetCodeMultisigParams::param_type(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn correct_function_ids() { + assert_eq!(constructor().input_id, 0x6c1e693c); + assert_eq!(send_transaction().input_id, 0x4cee646c); + assert_eq!(submit_transaction().input_id, 0x131d82cd); + assert_eq!(confirm_transaction().input_id, 0x1aa740ed); + assert_eq!(safe_multisig::get_parameters().input_id, 0x6d28dde8); + assert_eq!(set_code_multisig::get_parameters().input_id, 0x66b8710c); + assert_eq!(get_transactions().input_id, 0x73122f72); + assert_eq!(get_custodians().input_id, 0x5b00d859); + } +} diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs new file mode 100644 index 0000000..01d137c --- /dev/null +++ b/src/utils/wallets/multisig2.rs @@ -0,0 +1,303 @@ +use nekoton_abi::*; +use ton_abi::{Param, ParamType}; + +use crate::utils::declare_function; + +pub fn constructor() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "constructor", + inputs: vec![ + Param::new("owners", ParamType::Array(Box::new(ParamType::Uint(256)))), + Param::new("reqConfirms", ParamType::Uint(8)), + Param::new("lifetime", ParamType::Uint(32)), + ], + outputs: Vec::new(), + } +} + +pub fn send_transaction() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "sendTransaction", + inputs: vec![ + Param::new("dest", ParamType::Address), + Param::new("value", ParamType::Uint(128)), + Param::new("bounce", ParamType::Bool), + Param::new("flags", ParamType::Uint(8)), + Param::new("payload", ParamType::Cell), + ], + outputs: Vec::new(), + } +} + +pub fn submit_transaction() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "submitTransaction", + inputs: vec![ + Param::new("dest", ParamType::Address), + Param::new("value", ParamType::Uint(128)), + Param::new("bounce", ParamType::Bool), + Param::new("allBalance", ParamType::Bool), + Param::new("payload", ParamType::Cell), + Param::new("stateInit", ParamType::Optional(Box::new(ParamType::Cell))), + ], + outputs: vec![Param::new("transId", ParamType::Uint(64))], + } +} + +pub fn confirm_transaction() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "confirmTransaction", + inputs: vec![Param::new("transactionId", ParamType::Uint(64))], + outputs: Vec::new(), + } +} + +#[derive(Debug, UnpackAbi, KnownParamType)] +pub struct MultisigTransaction { + #[abi(uint64)] + pub id: u64, + #[abi(uint32, name = "confirmationsMask")] + pub confirmation_mask: u32, + #[abi(uint8, name = "signsRequired")] + pub signs_required: u8, + #[abi(uint8, name = "signsReceived")] + pub signs_received: u8, + #[abi(uint256)] + pub creator: ton_types::UInt256, + #[abi(uint8)] + pub index: u8, + #[abi(address)] + pub dest: ton_block::MsgAddressInt, + #[abi(uint128)] + pub value: u128, + #[abi(uint16, name = "sendFlags")] + pub send_flags: u16, + #[abi(cell)] + pub payload: ton_types::Cell, + #[abi(bool)] + pub bounce: bool, + #[abi] + pub state_init: Option, +} + +pub fn get_transactions() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getTransactions", + inputs: Vec::new(), + outputs: vec![ + Param::new("transactions", ParamType::Array(Box::new(MultisigTransaction::param_type()))) + ] + } +} + +#[derive(Debug, Clone, Copy, UnpackAbi, KnownParamType)] +pub struct MultisigCustodian { + #[abi(uint8)] + pub index: u8, + #[abi(uint256)] + pub pubkey: ton_types::UInt256, +} + +pub fn get_custodians() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getCustodians", + inputs: Vec::new(), + outputs: vec![ + Param::new("custodians", ParamType::Array(Box::new(MultisigCustodian::param_type()))) + ] + } +} + +#[derive(Debug, Clone, UnpackAbiPlain, PackAbiPlain, KnownParamTypePlain)] +pub struct SubmitUpdateParams { + #[abi] + pub code_hash: Option, + #[abi] + pub owners: Option>, + #[abi] + pub req_confirms: Option, + #[abi] + pub lifetime: Option, +} + +#[derive(Debug, Copy, Clone, UnpackAbiPlain, KnownParamTypePlain)] +pub struct SubmitUpdateOutput { + #[abi(uint64)] + pub update_id: u64, +} + +pub fn submit_update() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "submitUpdate", + inputs: SubmitUpdateParams::param_type(), + outputs: SubmitUpdateOutput::param_type(), + } +} + +#[derive(Debug, Copy, Clone, UnpackAbiPlain, PackAbiPlain, KnownParamTypePlain)] +pub struct ConfirmUpdateParams { + #[abi(uint64)] + pub update_id: u64, +} + +pub fn confirm_update() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "confirmUpdate", + inputs: ConfirmUpdateParams::param_type(), + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone, UnpackAbiPlain, PackAbiPlain, KnownParamTypePlain)] +pub struct ExecuteUpdateParams { + #[abi(uint64)] + pub update_id: u64, + #[abi] + pub code: Option, +} + +pub fn execute_update() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "executeUpdate", + inputs: ExecuteUpdateParams::param_type(), + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone, Copy, UnpackAbiPlain, KnownParamTypePlain)] +pub struct SetCodeMultisigParams { + #[abi(uint8, name = "maxQueuedTransactions")] + pub max_queued_transactions: u8, + #[abi(uint8, name = "maxCustodianCount")] + pub max_custodian_count: u8, + #[abi(uint64, name = "expirationTime")] + pub expiration_time: u64, + #[abi(uint128, name = "minValue")] + pub min_value: u128, + #[abi(uint8, name = "requiredTxnConfirms")] + pub required_txn_confirms: u8, + #[abi(uint8, name = "requiredUpdConfirms")] + pub required_upd_confirms: u8, +} + +pub fn get_parameters() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getParameters", + inputs: Vec::new(), + outputs: SetCodeMultisigParams::param_type(), + } +} + +#[derive(Debug, Clone, UnpackAbi, KnownParamType)] +pub struct UpdateTransaction { + #[abi(uint64)] + pub id: u64, + #[abi(uint8)] + pub index: u8, + #[abi(uint8)] + pub signs: u8, + #[abi(uint32)] + pub confirmations_mask: u32, + #[abi(uint256)] + pub creator: ton_types::UInt256, + #[abi] + pub new_code_hash: Option, + #[abi] + pub new_custodians: Option>, + #[abi] + pub new_req_confirms: Option, + #[abi(with = "updated_lifetime")] + pub new_lifetime: Option, +} + +mod updated_lifetime { + use super::*; + use num_traits::cast::ToPrimitive; + + pub fn unpack(value: &ton_abi::TokenValue) -> UnpackerResult> { + let value = match value { + ton_abi::TokenValue::Optional(_, None) => return Ok(None), + ton_abi::TokenValue::Optional(_, Some(value)) => value, + _ => return Err(UnpackerError::InvalidAbi), + }; + + match value.as_ref() { + ton_abi::TokenValue::Uint(ton_abi::Uint { number, size: 32 }) => { + Ok(Some(number.to_u32().ok_or(UnpackerError::InvalidAbi)?)) + } + ton_abi::TokenValue::Uint(ton_abi::Uint { number, size: 64 }) => { + let lifetime = number.to_u64().ok_or(UnpackerError::InvalidAbi)?; + Ok(Some(lifetime as u32)) + } + _ => Err(UnpackerError::InvalidAbi), + } + } + + pub fn param_type() -> ParamType { + Option::::param_type() + } +} + +pub mod v2_0 { + use super::*; + + pub fn get_update_requests() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getUpdateRequests", + inputs: Vec::new(), + outputs: { + let mut param_types = UpdateTransaction::param_type(); + if let ton_abi::ParamType::Tuple(params) = &mut param_types { + if let Some(ton_abi::Param { + kind: ton_abi::ParamType::Optional(param), + .. + }) = params.last_mut() { + if let ton_abi::ParamType::Uint(size) = param.as_mut() { + *size = 64; + } + } + } + + vec![Param::new("updates", ParamType::Array(Box::new(param_types)))] + }, + } + } +} + +pub mod v2_1 { + use super::*; + + pub fn get_update_requests() -> &'static ton_abi::Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getUpdateRequests", + inputs: Vec::new(), + outputs: vec![ + Param::new("updates", ParamType::Array(Box::new(UpdateTransaction::param_type()))) + ], + } + } +} diff --git a/src/utils/wallets/notifications.rs b/src/utils/wallets/notifications.rs new file mode 100644 index 0000000..9f7dc55 --- /dev/null +++ b/src/utils/wallets/notifications.rs @@ -0,0 +1,41 @@ +use ton_abi::{Param, ParamType}; + +use crate::utils::declare_function; + +pub fn notify_wallet_deployed() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + name: "notifyWalletDeployed", + inputs: vec![Param::new("root", ParamType::Address)], + outputs: Vec::new(), + } +} + +pub fn depool_on_round_complete() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + name: "onRoundComplete", + inputs: vec![ + Param::new("roundId", ParamType::Uint(64)), + Param::new("reward", ParamType::Uint(64)), + Param::new("ordinaryStake", ParamType::Uint(64)), + Param::new("vestingStake", ParamType::Uint(64)), + Param::new("lockStake", ParamType::Uint(64)), + Param::new("reinvest", ParamType::Bool), + Param::new("reason", ParamType::Uint(8)), + ], + outputs: Vec::new(), + } +} + +pub fn depool_receive_answer() -> &'static ton_abi::Function { + declare_function! { + abi: v2_0, + name: "receiveAnswer", + inputs: vec![ + Param::new("errcode", ParamType::Uint(32)), + Param::new("comment", ParamType::Uint(64)), + ], + outputs: Vec::new(), + } +} From 6130a2fe1ff71c8ded5d42bc90dc6fc504a08d61 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 26 Jan 2026 17:52:58 +0300 Subject: [PATCH 02/59] fix ever_wallet --- src/utils/mod.rs | 2 +- src/utils/ton_wallet/ever_wallet.rs | 48 ++++++++++++++--------------- src/utils/wallets/ever_wallet.rs | 13 +++++--- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a7affee..87e198e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -19,8 +19,8 @@ mod existing_contract; mod pending_messages_queue; mod shard_utils; mod token_wallet; -mod tx_context; mod ton_wallet; +mod tx_context; mod wallets; pub type FxDashMap = dashmap::DashMap>; diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs index e769503..80af2dc 100644 --- a/src/utils/ton_wallet/ever_wallet.rs +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -1,10 +1,12 @@ - use anyhow::Result; use ed25519_dalek::PublicKey; -use tycho_types::{abi::{AbiHeaderType, AbiVersion, Function, NamedAbiValue, UnsignedExternalMessage}, cell::{CellBuilder, HashBytes}, models::{Account, AccountState, IntAddr, IntMsgInfo, Message, MessageLayout, MsgInfo, StateInit, StdAddr}}; - -use crate::utils::ton_wallet::{Gift, TonWalletDetails, ever_wallet}; +use tycho_types::{ + abi::{AbiHeaderType, AbiVersion, Function, NamedAbiValue, UnsignedExternalMessage}, + cell::{CellBuilder, HashBytes}, + models::{Account, AccountState, IntAddr, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, +}; +use crate::utils::ton_wallet::{ever_wallet, Gift, TonWalletDetails}; pub fn prepare_deploy( public_key: &PublicKey, @@ -14,12 +16,13 @@ pub fn prepare_deploy( let state_init = prepare_state_init(public_key)?; let hash = CellBuilder::build_from(&state_init)?.repr_hash(); - let dst = StdAddr::new( - workchain, - hash.into(), - ); - - let headers = vec![AbiHeaderType::Time, AbiHeaderType::Expire, AbiHeaderType::Pubkey]; + let dst = StdAddr::new(workchain, hash.into()); + + let headers = vec![ + AbiHeaderType::Time, + AbiHeaderType::Expire, + AbiHeaderType::Pubkey, + ]; let function = Function::builder(AbiVersion::V2_3, "sendTransactionRaw") .with_headers(headers) .with_inputs(vec![]) @@ -27,7 +30,10 @@ pub fn prepare_deploy( .with_id(0x169e3e11) .build(); - let unsigned_body = function.encode_external(&[]).with_expire_at(expire_at).build_input()?; + let unsigned_body = function + .encode_external(&[]) + .with_expire_at(expire_at) + .build_input()?; let mut unsigned_message = unsigned_body.with_dst(dst); unsigned_message.set_state_init(Some(state_init)); Ok(unsigned_message) @@ -47,7 +53,7 @@ pub fn prepare_transfer( } let mut gifts = gifts.into_iter(); - let body = match (gifts.len(), gifts.next()) { + let external_input = match (gifts.len(), gifts.next()) { (1, Some(gift)) if gift.state_init.is_none() => { let function = ever_wallet::send_transaction(); function.encode_external(&[ @@ -56,7 +62,7 @@ pub fn prepare_transfer( NamedAbiValue::from(("bounce", gift.bounce)), NamedAbiValue::from(("flags", gift.flags)), NamedAbiValue::from(("body", gift.body.unwrap_or_default().into_cell())), - ])? + ]) } (len, gift) => { let function = match len { @@ -85,22 +91,19 @@ pub fn prepare_transfer( tokens.push(NamedAbiValue::from(("flags", gift.flags.token_value()))); tokens.push(NamedAbiValue::from(( "message", - CellBuilder::build_from(internal_message.borrow())? + CellBuilder::build_from(internal_message.borrow())?, ))); } - function.encode_external(&tokens)? + function.encode_external(&tokens) } }; - - let unsigned_body = function.encode_external(&[]).with_expire_at(expire_at).build_input()?; + let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; let mut unsigned_message = unsigned_body.with_dst(address); match ¤t_state.state { AccountState::Active { .. } => {} - AccountState::Frozen { .. } => { - return Err(EverWalletError::AccountIsFrozen.into()) - } + AccountState::Frozen { .. } => return Err(EverWalletError::AccountIsFrozen.into()), AccountState::Uninit => { unsigned_message.set_state_init(Some(prepare_state_init(public_key)?)); } @@ -122,10 +125,7 @@ pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Int let hash = prepare_state_init(public_key) .and_then(|state| state.hash()) .trust_me(); - IntAddr::Std(StdAddr::new( - workchain_id, - hash.into(), - )) + IntAddr::Std(StdAddr::new(workchain_id, hash.into())) } pub fn prepare_state_init(public_key: &PublicKey) -> Result { diff --git a/src/utils/wallets/ever_wallet.rs b/src/utils/wallets/ever_wallet.rs index 604be76..e551ca6 100644 --- a/src/utils/wallets/ever_wallet.rs +++ b/src/utils/wallets/ever_wallet.rs @@ -2,7 +2,6 @@ use tycho_types::abi::{AbiType, Function}; use crate::utils::wallets::ever_wallet::utils::declare_function; - pub mod utils { macro_rules! declare_function { ( @@ -15,14 +14,21 @@ pub mod utils { ) => { static ONCE: std::sync::OnceLock = std::sync::OnceLock::new(); ONCE.get_or_init(|| { - tycho_types::abi::Function::builder($crate::utils::wallets::ever_wallet::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) + let mut builder = tycho_types::abi::Function::builder($crate::utils::wallets::ever_wallet::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) .with_headers($crate::utils::wallets::ever_wallet::utils::declare_function!(@header $($($header),+)?)) .with_inputs($inputs) - .with_outputs($outputs) + .with_outputs($outputs); + + $crate::utils::wallets::ever_wallet::utils::declare_function!(@function_id builder $($id)?); + + builder .build() }) }; + (@function_id $builder:ident $id:literal) => { $builder.with_id($id) }; + (@function_id $builder:ident ) => {}; + (@abi_version) => { tycho_types::abi::AbiVersion::V2_2 }; (@abi_version v2_0) => { tycho_types::abi::AbiVersion::V2_0 }; (@abi_version v2_1) => { tycho_types::abi::AbiVersion::V2_1 }; @@ -48,7 +54,6 @@ pub mod utils { pub(crate) use declare_function; } - pub fn send_transaction() -> &'static Function { declare_function! { abi: v2_3, From 29ba418870bb6ff134ccf556fa7775e5e108cb06 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 26 Jan 2026 17:52:58 +0300 Subject: [PATCH 03/59] fix highload_wallet --- src/utils/ton_wallet/highload_wallet_v2.rs | 307 ++++++++------------- src/utils/wallets/code/mod.rs | 4 +- src/utils/wallets/ever_wallet.rs | 2 +- 3 files changed, 118 insertions(+), 195 deletions(-) diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 2966fb0..823ea81 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -2,69 +2,61 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::PublicKey; -use ton_block::{MsgAddrStd, MsgAddressInt, Serializable}; -use ton_types::{BuilderData, Cell, HashmapE, HashmapType, IBitstring, SliceData, UInt256}; +use tycho_types::{ + abi::{ + AbiVersion, UnsignedBody, UnsignedExternalMessage, + }, + cell::{Cell, CellBuilder, HashBytes}, + dict::Dict, + models::{Account, AccountState, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, +}; -use nekoton_utils::*; +use crate::utils::wallets::code::highload_wallet_v2; use super::{Gift, TonWalletDetails, TransferAction}; -use crate::core::models::{Expiration, ExpireAt}; -use crate::crypto::{SignedMessage, UnsignedMessage}; pub fn prepare_deploy( - clock: &dyn Clock, public_key: &PublicKey, workchain: i8, - expiration: Expiration, -) -> Result> { + expire_at: u32, +) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); let dst = compute_contract_address(public_key, workchain); - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst, - ..Default::default() - }); - - message.set_state_init(init_data.make_state_init()?); - - let expire_at = ExpireAt::new(clock, expiration); - let (hash, payload) = init_data.make_deploy_payload(expire_at.timestamp)?; - - Ok(Box::new(UnsignedHighloadWalletV2Message { - init_data, - gifts: Vec::new(), + let (hash, payload) = init_data.make_deploy_payload(expire_at)?; + let unsigned_body = UnsignedBody { payload, - message, - expire_at, hash, - })) + abi_version: AbiVersion::V2_3, + expire_at, + }; + let mut unsigned_message = unsigned_body.with_dst(dst); + let state_init = init_data.make_state_init()?; + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) } -pub fn prepare_state_init(public_key: &PublicKey) -> Result { +pub fn prepare_state_init(public_key: &PublicKey) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); init_data.make_state_init() } pub fn prepare_transfer( - clock: &dyn Clock, public_key: &PublicKey, - current_state: &ton_block::AccountStuff, + current_state: &Account, gifts: Vec, - expiration: Expiration, -) -> Result { + expire_at: u32, +) -> Result { if gifts.len() > DETAILS.max_messages { return Err(HighloadWalletV2Error::TooManyGifts.into()); } - let (init_data, with_state_init) = match ¤t_state.storage.state { - ton_block::AccountState::AccountActive { state_init, .. } => match &state_init.data { + let (init_data, with_state_init) = match ¤t_state.state { + AccountState::Active(state_init) => match &state_init.data { Some(data) => (InitData::try_from(data)?, false), None => return Err(HighloadWalletV2Error::InvalidInitData.into()), }, - ton_block::AccountState::AccountFrozen { .. } => { - return Err(HighloadWalletV2Error::AccountIsFrozen.into()) - } - ton_block::AccountState::AccountUninit => ( + AccountState::Frozen { .. } => return Err(HighloadWalletV2Error::AccountIsFrozen.into()), + AccountState::Uninit => ( InitData::from_key(public_key).with_wallet_id(WALLET_ID), true, ), @@ -74,99 +66,36 @@ pub fn prepare_transfer( return Err(HighloadWalletV2Error::InitDataTooLarge.into()); } - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst: current_state.addr.clone(), - ..Default::default() - }); - + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at)?; + let unsigned_body = UnsignedBody { + payload, + hash, + abi_version: AbiVersion::V2_3, + expire_at, + }; + let mut unsigned_message = unsigned_body.with_dst( + current_state + .address + .as_std() + .ok_or_else(|| HighloadWalletV2Error::InvalidAddress)? + .clone(), + ); if with_state_init { - message.set_state_init(init_data.make_state_init()?); + let state_init = init_data.make_state_init()?; + unsigned_message.set_state_init(Some(state_init)); } - let expire_at = ExpireAt::new(clock, expiration); - let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at.timestamp)?; - - Ok(TransferAction::Sign(Box::new( - UnsignedHighloadWalletV2Message { - init_data, - gifts, - payload, - hash, - expire_at, - message, - }, - ))) + Ok(unsigned_message) } #[derive(Clone)] struct UnsignedHighloadWalletV2Message { init_data: InitData, gifts: Vec, - payload: BuilderData, - hash: UInt256, - expire_at: ExpireAt, - message: ton_block::Message, -} - -impl UnsignedMessage for UnsignedHighloadWalletV2Message { - fn refresh_timeout(&mut self, clock: &dyn Clock) { - if !self.expire_at.refresh(clock) { - return; - } - - let expire_at = self.expire_at(); - - let (hash, payload) = if self.gifts.is_empty() { - self.init_data.make_deploy_payload(expire_at) - } else { - self.init_data - .make_transfer_payload(self.gifts.clone(), expire_at) - } - .trust_me(); - - self.hash = hash; - self.payload = payload; - } - - fn expire_at(&self) -> u32 { - self.expire_at.timestamp - } - - fn hash(&self) -> &[u8] { - self.hash.as_slice() - } - - fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { - let mut payload = self.payload.clone(); - payload.prepend_raw(signature, signature.len() * 8)?; - - let mut message = self.message.clone(); - message.set_body(ton_types::SliceData::load_builder(payload)?); - - Ok(SignedMessage { - message, - expire_at: self.expire_at(), - }) - } - - fn sign_with_pruned_payload( - &self, - signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH], - prune_after_depth: u16, - ) -> Result { - let mut payload = self.payload.clone(); - payload.prepend_raw(signature, signature.len() * 8)?; - let body = payload.into_cell()?; - - let mut message = self.message.clone(); - message.set_body(prune_deep_cells(&body, prune_after_depth)?); - - Ok(SignedMessage { - message, - expire_at: self.expire_at(), - }) - } + payload: Cell, + hash: HashBytes, + expire_at: u32, + message: UnsignedExternalMessage, } pub static CODE_HASH: &[u8; 32] = &[ @@ -174,11 +103,11 @@ pub static CODE_HASH: &[u8; 32] = &[ 0x6a, 0x02, 0x90, 0x65, 0xae, 0xfb, 0x6d, 0x6b, 0x58, 0x37, 0x35, 0xd5, 0x8d, 0xa9, 0xd5, 0xbe, ]; -pub fn is_highload_wallet_v2(code_hash: &UInt256) -> bool { +pub fn is_highload_wallet_v2(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> MsgAddressInt { +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> StdAddr { InitData::from_key(public_key) .with_wallet_id(WALLET_ID) .compute_addr(workchain_id) @@ -202,13 +131,13 @@ pub static DETAILS: TonWalletDetails = TonWalletDetails { pub struct InitData { pub wallet_id: u32, pub last_cleaned: u64, - pub public_key: UInt256, - pub data: HashmapE, + pub public_key: HashBytes, + pub data: Dict, } impl InitData { pub fn public_key(&self) -> &[u8; 32] { - self.public_key.as_slice() + &self.public_key.0 } pub fn from_key(key: &PublicKey) -> Self { @@ -216,7 +145,7 @@ impl InitData { wallet_id: 0, last_cleaned: 0, public_key: key.as_bytes().into(), - data: HashmapE::default(), + data: Dict::default(), } } @@ -225,99 +154,90 @@ impl InitData { self } - pub fn compute_addr(&self, workchain_id: i8) -> Result { - let init_state = self.make_state_init()?.serialize()?; - let hash = init_state.repr_hash(); - Ok(MsgAddressInt::AddrStd(MsgAddrStd { - anycast: None, - workchain_id, - address: hash.into(), - })) + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let state_init = self.make_state_init()?; + let hash = CellBuilder::build_from(&state_init)?.repr_hash(); + Ok(StdAddr::new(workchain_id, hash.into())) } - pub fn make_state_init(&self) -> Result { - Ok(ton_block::StateInit { - code: Some(nekoton_contracts::wallets::code::highload_wallet_v2()), + pub fn make_state_init(&self) -> Result { + Ok(StateInit { + code: Some(highload_wallet_v2()), data: Some(self.serialize()?), ..Default::default() }) } pub fn serialize(&self) -> Result { - let mut data = BuilderData::new(); - data.append_u32(self.wallet_id)? - .append_u64(self.last_cleaned)? - .append_raw(self.public_key.as_slice(), 256)?; - self.data.write_hashmap_data(&mut data)?; - data.into_cell() + let mut builder = CellBuilder::new(); + builder.store_u32(self.wallet_id)?; + builder.store_u64(self.last_cleaned)?; + builder.store_u256(self.public_key.as_bytes())?; + let data = builder.build()?; + self.data.add(0, data.clone())?; + Ok(data) } - pub fn make_deploy_payload(&self, expire_at: u32) -> Result<(UInt256, BuilderData)> { - let mut payload = BuilderData::new(); - payload - .append_u32(self.wallet_id)? - .append_u32(expire_at)? - .append_u32(u32::MAX)? - .append_bit_zero()?; + pub fn make_deploy_payload(&self, expire_at: u32) -> Result<(HashBytes, Cell)> { + let mut builder = CellBuilder::new(); - let hash = payload.clone().into_cell()?.repr_hash(); + builder.store_u32(self.wallet_id)?; + builder.store_u32(expire_at)?; + builder.store_u32(u32::MAX)?; + builder.store_bit_zero()?; - Ok((hash, payload)) + let payload = builder.build()?; + + let hash = payload.clone().repr_hash(); + + Ok((*hash, payload)) } pub fn make_transfer_payload( &self, gifts: impl IntoIterator, expire_at: u32, - ) -> Result<(UInt256, BuilderData)> { + ) -> Result<(HashBytes, Cell)> { // Prepare messages array - let mut messages = ton_types::HashmapE::with_bit_len(16); + let mut messages = Dict::::new(); for (i, gift) in gifts.into_iter().enumerate() { - let mut internal_message = - ton_block::Message::with_int_header(ton_block::InternalMessageHeader { + let internal_message = Message { + info: MsgInfo::Int(IntMsgInfo { ihr_disabled: true, bounce: gift.bounce, dst: gift.destination, - value: ton_block::CurrencyCollection::from_grams(ton_block::Grams::new( - gift.amount, - )?), + value: gift.amount.into(), ..Default::default() - }); - - if let Some(body) = gift.body { - internal_message.set_body(body); - } + }), + init: gift.state_init, + body: gift.body.unwrap_or(Default::default()).as_slice()?, + layout: None, + }; - if let Some(state_init) = gift.state_init { - internal_message.set_state_init(state_init); - } + let cell = CellBuilder::build_from(internal_message.borrow())?; - let mut item = BuilderData::new(); - item.append_u8(gift.flags)? - .checked_append_reference(internal_message.serialize()?)?; + let mut item = CellBuilder::new(); + item.store_u8(gift.flags)?; + item.store_reference(cell)?; - let key = (i as u16) - .serialize() - .and_then(SliceData::load_cell) - .trust_me(); - - messages.set_builder(key, &item)?; + let key = i as u16; + messages.set(key, item.build()?)?; } - let messages = messages.serialize()?; + let messages = CellBuilder::build_from(messages.borrow())?; let messages_hash = messages.repr_hash(); // Build payload - let mut payload = BuilderData::new(); - payload - .append_u32(self.wallet_id)? - .append_u32(expire_at)? - .append_raw(&messages_hash.as_slice()[28..32], 32)? - .append_builder(&messages.into())?; + let mut payload = CellBuilder::new(); + payload.store_u32(self.wallet_id)?; + payload.store_u32(expire_at)?; + payload.store_raw(&messages_hash.as_slice()[28..32], 32)?; + payload.store_builder(&messages.into())?; - let hash = payload.clone().into_cell()?.repr_hash(); + let payload = payload.build()?; + let hash = payload.repr_hash(); - Ok((hash, payload)) + Ok((*hash, payload)) } } @@ -325,15 +245,15 @@ impl TryFrom<&Cell> for InitData { type Error = anyhow::Error; fn try_from(data: &Cell) -> Result { - let mut cs = SliceData::load_cell_ref(data)?; + let mut slice = data.as_slice()?; Ok(Self { - wallet_id: cs.get_next_u32()?, - last_cleaned: cs.get_next_u64()?, - public_key: UInt256::from_be_bytes(&cs.get_next_bytes(32)?), + wallet_id: slice.get_next_u32()?, + last_cleaned: slice.get_next_u64()?, + public_key: HashBytes::from_be_bytes(&slice.get_next_bytes(32)?), data: { - let mut map = HashmapE::with_bit_len(64); - map.read_hashmap_data(&mut cs)?; + let mut map = Dict::::new(); + map.load_from(&mut slice)?; map }, }) @@ -352,15 +272,18 @@ enum HighloadWalletV2Error { TooManyGifts, #[error("Account init data is too large")] InitDataTooLarge, + #[error("Account address is not valid")] + InvalidAddress, } #[cfg(test)] pub mod tests { - use crate::core::ton_wallet::highload_wallet_v2::InitData; use anyhow::Result; use ton_block::Deserializable; use ton_types::HashmapType; + use crate::utils::ton_wallet::highload_wallet_v2::InitData; + #[tokio::test] async fn check_state() -> Result<()> { let data = "te6ccgICCBAAAQAAOOsAAAIBmggHAAEBWQAAAABij1ipvO9y33lafOQTY2Zjcpu/tM7FomMbSFDp4+8Ei8aUcpwDouTBwAACAgiLsUesAl4AAwIBYgA1AAQCAnAAFAAFAgFIABEABgIBIAAKAAcCAW4ACQAIAAm3P2/0YAAJty45OOACASAADAALAAm7gCmMCAIBIAAQAA0CASAADwAOAAm3SNaR4AAJt2IEcmAACbn59J8wAgEgABMAEgAJvH0s0sQACb0fFP0kAgEgACYAFQIBIAAfABYCASAAGgAXAgEgABkAGAAJulOvR9gACbr90p9IAgFYABwAGwAJuaPhPtACASAAHgAdAAm29FO6oAAJt4e9WeACASAAJQAgAgEgACQAIQIBIAAjACIACbg4HOPQAAm5N8GT0AAJumQHj3gACbxuQUbMAgEgACwAJwIBIAApACgACbxoeVqsAgEgACsAKgAJu3kh5AgACbuT7z2oAgEgADIALQIBIAAxAC4CAUgAMAAvAAm3D/yF4AAJttuTf6AACboR+FvYAgEgADQAMwAJu/xtoCgACbswljEoAgEgAVEANgIBIADGADcCASAAfQA4AgEgAFwAOQIBIABLADoCASAASgA7AgEgAD8APAIBIAA+AD0ACbv67XUoAAm728BYuAIBIABDAEACASAAQgBBAAm4XHbOEAAJuEX0C9ACASAASQBEAgEgAEgARQIBSABHAEYACLJFqpcACLIZDQgACbb/vM5gAAm5tBZx8AAJv6qZvYYCASAAVQBMAgEgAFIATQIBWABRAE4CAWYAUABPAAizva6wAAizJMbvAAm4yrfY0AIBIABUAFMACbv97cQIAAm7rFAKCAIBIABXAFYACbwdUD3MAgEgAFkAWAAJuqMTi4gCA5B3AFsAWgAHqXo6sAAHqTSr0AIBIABsAF0CASAAYwBeAgEgAGIAXwICcQBhAGAACbU816HAAAm0goAhwAAJvMcETxwCASAAawBkAgEgAGoAZQIBIABnAGYACbjQvmEQAgJxAGkAaAAHsBsUkQAHsGq37wAJu6lVp4gACb01zX/cAgEgAHYAbQIBIABzAG4CASAAcABvAAm6QVAyuAIBSAByAHEACbd6cIWgAAm2kdMmYAIBIAB1AHQACbotzlT4AAm66wR8CAIBIAB8AHcCASAAewB4AgEgAHoA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trax3CACASADAgMBAAm7WoeimAIBIAMEAwMACbm8yrWwAAm5HOVjEAIBSAMHAwYACbpktfloAAm7LsDquAIBIAMUAwkCAVgDCwMKAAm6hR6B6AIBIAMPAwwCASADDgMNAAm2f4hroAAJtllCn+ACASADEQMQAAm2Yx6y4AIBIAMTAxIACbSflolAAAm0QerswAIBIAMYAxUCASADFwMWAAm7tkoeSAAJuknHOugACb2TEThEAgEgAzkDGgIBIAMoAxsCASADIQMcAgEgAyADHQIBIAMfAx4ACbuaE9GIAAm76TwyuAAJvTF5SlwCASADJQMiAgFmAyQDIwAJt+MXWCAACbfdIXagAgEgAycDJgAJu+jFS4gACbqhfTLYAgEgAzIDKQIBIAMvAyoCAVgDLgMrAgFYAy0DLAAJtOPh2kAACbTQbPDAAAm5hunfsAIBIAMxAzAACbqF6XDoAAm7UeL9mAIBIAM2AzMCASADNQM0AAm7xZq5KAAJuxrvMUgCASADOAM3AAm7OY2quAAJumXrW7gCASADSwM6AgEgA0YDOwIBIANBAzwCASADPgM9AAm7wUzueAIBIANAAz8ACbmxiDdwAAm5KDyKEAIBIANDA0IACbupeOFIAgEgA0UDRAAJuNLLTRAACbir+oZQAgEgA0oDRwIBIANJA0gACbonQw4YAAm6iJr9KAAJvAwRrswCASADVQNMAgEgA1QDTQIBIANPA04ACbo2gb2IAgFqA1EDUAAJtAxQcEACAVgDUwNSAAex5r87AAex7i7vAAm8a0P6FAIBIANXA1YACb0WUg0sAgEgA1kDWAAJupoiEwgACbsZNqNoAgEgA94DWwIBIAObA1wCASADeANdAgEgA3EDXgIBIANmA18CASADYwNgAgEgA2IDYQAJu3UoScgACbtyxfUoAgEgA2UDZAAJu7/lGDgACbugeIXoAgEgA24DZwIBIANrA2gCASADagNpAAm5vLA10AAJubPahdACASADbQNsAAm5tK+QUAAJuEioGPACAUgDcANvAAm5CURc8AAJuCyslfACAUgDcwNyAAm8IYMwTAIBSAN3A3QCASADdgN1AAm3DbyIoAAJti1cfKAACbgyOlvwAgEgA4oDeQIBIAOBA3oCASADgAN7AgEgA30DfAAJu0UfkFgCASADfwN+AAm4pvk5sAAJua8OZHAA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"; diff --git a/src/utils/wallets/code/mod.rs b/src/utils/wallets/code/mod.rs index 0e65b05..4b27b57 100644 --- a/src/utils/wallets/code/mod.rs +++ b/src/utils/wallets/code/mod.rs @@ -1,4 +1,4 @@ -use ton_types::Cell; +use tycho_types::{boc::Boc, cell::Cell}; macro_rules! declare_tvc { ($($contract:ident => $source:literal ($const_bytes:ident)),*$(,)?) => {$( @@ -30,5 +30,5 @@ declare_tvc! { } fn load(mut data: &[u8]) -> Cell { - ton_types::deserialize_tree_of_cells(&mut data).expect("Trust me") + Boc::decode(&mut data).expect("Trust me") } diff --git a/src/utils/wallets/ever_wallet.rs b/src/utils/wallets/ever_wallet.rs index e551ca6..11190ba 100644 --- a/src/utils/wallets/ever_wallet.rs +++ b/src/utils/wallets/ever_wallet.rs @@ -19,7 +19,7 @@ pub mod utils { .with_inputs($inputs) .with_outputs($outputs); - $crate::utils::wallets::ever_wallet::utils::declare_function!(@function_id builder $($id)?); + $crate::utils::wallets::ever_wallet::utils::declare_function!(@function_id builder $($id)?); builder .build() From 1e80a5ed5240b76210d8dad26f96dc7fcafb8a56 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 26 Jan 2026 17:52:58 +0300 Subject: [PATCH 04/59] fix multisig --- src/utils/mod.rs | 50 + src/utils/ton_wallet/ever_wallet.rs | 3 +- src/utils/ton_wallet/highload_wallet_v2.rs | 6 +- src/utils/ton_wallet/mod.rs | 1085 ++------------------ src/utils/ton_wallet/multisig.rs | 374 ++++--- src/utils/wallets/ever_wallet.rs | 54 +- src/utils/wallets/multisig.rs | 102 +- src/utils/wallets/multisig2.rs | 219 ++-- 8 files changed, 427 insertions(+), 1466 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 87e198e..2c9a291 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -32,3 +32,53 @@ pub fn conver_to_old_transaction(transaction: &Transaction) -> Result { + static ONCE: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE.get_or_init(|| { + let mut builder = tycho_types::abi::Function::builder($crate::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) + .with_headers($crate::utils::declare_function!(@header $($($header),+)?)) + .with_inputs($inputs) + .with_outputs($outputs); + + $crate::utils::declare_function!(@function_id builder $($id)?); + + builder + .build() + }) + }; + + (@function_id $builder:ident $id:literal) => { $builder.with_id($id) }; + (@function_id $builder:ident ) => {}; + + (@abi_version) => { tycho_types::abi::AbiVersion::V2_2 }; + (@abi_version v2_0) => { tycho_types::abi::AbiVersion::V2_0 }; + (@abi_version v2_1) => { tycho_types::abi::AbiVersion::V2_1 }; + (@abi_version v2_2) => { tycho_types::abi::AbiVersion::V2_2 }; + (@abi_version v2_3) => { tycho_types::abi::AbiVersion::V2_3 }; + (@abi_version v2_7) => { tycho_types::abi::AbiVersion::V2_7 }; + + (@header) => { Vec::new() }; + (@header $($header:ident),+) => { + vec![$($crate::utils::declare_function!(@header_item $header)),+] + }; + (@header_item pubkey) => { + tycho_types::abi::AbiHeaderType::Pubkey + }; + (@header_item time) => { + tycho_types::abi::AbiHeaderType::Time + }; + (@header_item expire) => { + tycho_types::abi::AbiHeaderType::Expire + }; +} + +pub(crate) use declare_function; diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs index 80af2dc..001fde1 100644 --- a/src/utils/ton_wallet/ever_wallet.rs +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -6,7 +6,8 @@ use tycho_types::{ models::{Account, AccountState, IntAddr, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, }; -use crate::utils::ton_wallet::{ever_wallet, Gift, TonWalletDetails}; +use crate::utils::ton_wallet::{Gift, TonWalletDetails}; +use crate::utils::wallets::code::ever_wallet; pub fn prepare_deploy( public_key: &PublicKey, diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 823ea81..0e3bfaf 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -3,9 +3,7 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::PublicKey; use tycho_types::{ - abi::{ - AbiVersion, UnsignedBody, UnsignedExternalMessage, - }, + abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes}, dict::Dict, models::{Account, AccountState, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, @@ -13,7 +11,7 @@ use tycho_types::{ use crate::utils::wallets::code::highload_wallet_v2; -use super::{Gift, TonWalletDetails, TransferAction}; +use super::{Gift, TonWalletDetails}; pub fn prepare_deploy( public_key: &PublicKey, diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs index 849f16f..f4d62a2 100644 --- a/src/utils/ton_wallet/mod.rs +++ b/src/utils/ton_wallet/mod.rs @@ -1,33 +1,15 @@ -use std::borrow::Cow; -use std::convert::TryFrom; use std::num::NonZeroU8; use std::str::FromStr; -use std::sync::Arc; use anyhow::Result; use ed25519_dalek::PublicKey; use serde::{Deserialize, Serialize}; -use ton_block::MsgAddressInt; -use ton_types::{BuilderData, SliceData, UInt256}; -use nekoton_abi::*; use nekoton_utils::*; -use tycho_types::cell::Cell; +use tycho_types::cell::{Cell, HashBytes}; use tycho_types::models::{IntAddr, StateInit, StdAddr}; pub use self::multisig::MultisigType; -use super::models::{ - ContractState, Expiration, MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate, - PendingTransaction, Transaction, TransactionAdditionalInfo, TransactionWithData, - TransactionsBatchInfo, -}; -use super::{ContractSubscription, PollingMethod}; -use crate::core::parsing::*; -use crate::core::InternalMessage; -use crate::crypto::UnsignedMessage; -use crate::models::ExpireAt; -use crate::transport::models::{ExistingContract, RawContractState, RawTransaction}; -use crate::transport::Transport; pub mod ever_wallet; pub mod highload_wallet_v2; @@ -38,807 +20,6 @@ pub mod wallet_v5r1; pub const DEFAULT_WORKCHAIN: i8 = 0; -pub struct TonWallet { - clock: Arc, - public_key: PublicKey, - wallet_type: WalletType, - contract_subscription: ContractSubscription, - handler: Arc, - wallet_data: WalletData, -} - -impl TonWallet { - pub async fn subscribe( - clock: Arc, - transport: Arc, - workchain: i8, - public_key: PublicKey, - wallet_type: WalletType, - handler: Arc, - ) -> Result { - let address = compute_address(&public_key, wallet_type, workchain); - - let mut wallet_data = WalletData::default(); - - let contract_subscription = ContractSubscription::subscribe( - clock.clone(), - transport, - address, - &mut make_contract_state_handler( - clock.as_ref(), - handler.as_ref(), - &public_key, - wallet_type, - &mut wallet_data, - ), - Some(&mut make_transactions_handler( - handler.as_ref(), - wallet_type, - )), - ) - .await?; - - Ok(Self { - clock, - public_key, - wallet_type, - contract_subscription, - handler, - wallet_data, - }) - } - - pub async fn subscribe_by_address( - clock: Arc, - transport: Arc, - address: MsgAddressInt, - handler: Arc, - ) -> Result { - let (public_key, wallet_type) = match transport.get_contract_state(&address).await? { - RawContractState::Exists(contract) => extract_wallet_init_data(&contract)?, - RawContractState::NotExists { .. } => { - return Err(TonWalletError::AccountNotExists.into()) - } - }; - - let mut wallet_data = WalletData::default(); - - let contract_subscription = ContractSubscription::subscribe( - clock.clone(), - transport, - address, - &mut make_contract_state_handler( - clock.as_ref(), - handler.as_ref(), - &public_key, - wallet_type, - &mut wallet_data, - ), - Some(&mut make_transactions_handler( - handler.as_ref(), - wallet_type, - )), - ) - .await?; - - Ok(Self { - clock, - public_key, - wallet_type, - contract_subscription, - handler, - wallet_data, - }) - } - - pub async fn subscribe_by_existing( - clock: Arc, - transport: Arc, - existing_wallet: ExistingWalletInfo, - handler: Arc, - ) -> Result { - let mut wallet_data = WalletData::default(); - - let contract_subscription = ContractSubscription::subscribe( - clock.clone(), - transport, - existing_wallet.address, - &mut make_contract_state_handler( - clock.as_ref(), - handler.as_ref(), - &existing_wallet.public_key, - existing_wallet.wallet_type, - &mut wallet_data, - ), - Some(&mut make_transactions_handler( - handler.as_ref(), - existing_wallet.wallet_type, - )), - ) - .await?; - - Ok(Self { - clock, - public_key: existing_wallet.public_key, - wallet_type: existing_wallet.wallet_type, - contract_subscription, - handler, - wallet_data, - }) - } - - pub fn make_state_init(&self) -> Result { - match self.wallet_type { - WalletType::Multisig(multisig_type) => Ok(multisig::prepare_state_init( - &self.public_key, - multisig_type, - )), - WalletType::WalletV3 => wallet_v3::prepare_state_init(&self.public_key), - WalletType::WalletV3R1 => { - wallet_v3v4::prepare_state_init(&self.public_key, wallet_v3v4::WalletVersion::V3R1) - } - WalletType::WalletV3R2 => { - wallet_v3v4::prepare_state_init(&self.public_key, wallet_v3v4::WalletVersion::V3R2) - } - WalletType::WalletV4R1 => { - wallet_v3v4::prepare_state_init(&self.public_key, wallet_v3v4::WalletVersion::V4R1) - } - WalletType::WalletV4R2 => { - wallet_v3v4::prepare_state_init(&self.public_key, wallet_v3v4::WalletVersion::V4R2) - } - WalletType::WalletV5R1 => wallet_v5r1::prepare_state_init(&self.public_key), - WalletType::EverWallet => ever_wallet::prepare_state_init(&self.public_key), - WalletType::HighloadWalletV2 => { - highload_wallet_v2::prepare_state_init(&self.public_key) - } - } - } - - pub fn contract_subscription(&self) -> &ContractSubscription { - &self.contract_subscription - } - - pub fn workchain(&self) -> i8 { - self.contract_subscription.address().workchain_id() as i8 - } - - pub fn address(&self) -> &MsgAddressInt { - self.contract_subscription.address() - } - - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } - - pub fn wallet_type(&self) -> WalletType { - self.wallet_type - } - - pub fn contract_state(&self) -> &ContractState { - self.contract_subscription.contract_state() - } - - pub fn pending_transactions(&self) -> &[PendingTransaction] { - self.contract_subscription.pending_transactions() - } - - pub fn polling_method(&self) -> PollingMethod { - self.contract_subscription.polling_method() - } - - pub fn details(&self) -> TonWalletDetails { - self.wallet_data - .details - .unwrap_or_else(|| self.wallet_type.details()) - } - - pub fn get_unconfirmed_transactions(&self) -> &[MultisigPendingTransaction] { - &self.wallet_data.unconfirmed_transactions - } - - pub fn get_unconfirmed_updates(&self) -> &[MultisigPendingUpdate] { - &self.wallet_data.unconfirmed_updates - } - - pub fn get_custodians(&self) -> &Option> { - &self.wallet_data.custodians - } - - pub fn prepare_deploy(&self, expiration: Expiration) -> Result> { - match self.wallet_type { - WalletType::Multisig(multisig_type) => multisig::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - multisig_type, - self.workchain(), - expiration, - multisig::DeployParams::single_custodian(&self.public_key), - ), - WalletType::WalletV3 => wallet_v3::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - self.workchain(), - expiration, - ), - WalletType::WalletV3R1 => wallet_v3v4::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - self.workchain(), - expiration, - wallet_v3v4::WalletVersion::V3R1, - ), - WalletType::WalletV3R2 => wallet_v3v4::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - self.workchain(), - expiration, - wallet_v3v4::WalletVersion::V3R2, - ), - WalletType::WalletV4R1 => wallet_v3v4::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - self.workchain(), - expiration, - wallet_v3v4::WalletVersion::V4R1, - ), - WalletType::WalletV4R2 => wallet_v3v4::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - self.workchain(), - expiration, - wallet_v3v4::WalletVersion::V4R2, - ), - WalletType::WalletV5R1 => wallet_v5r1::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - self.workchain(), - expiration, - ), - WalletType::EverWallet => ever_wallet::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - self.workchain(), - expiration, - ), - WalletType::HighloadWalletV2 => highload_wallet_v2::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - self.workchain(), - expiration, - ), - } - } - - pub fn prepare_deploy_with_multiple_owners( - &self, - expiration: Expiration, - custodians: &[PublicKey], - req_confirms: u8, - expiration_time: Option, - ) -> Result> { - match self.wallet_type { - WalletType::Multisig(multisig_type) => multisig::prepare_deploy( - self.clock.as_ref(), - &self.public_key, - multisig_type, - self.workchain(), - expiration, - multisig::DeployParams { - owners: custodians, - req_confirms, - expiration_time, - }, - ), - // Non-multisig wallets doesn't support multiple owners - _ => Err(TonWalletError::InvalidContractType.into()), - } - } - - pub fn make_unsigned_wallet_v5_transfer_payload( - &self, - current_state: &ton_block::AccountStuff, - public_key: &PublicKey, - gifts: Vec, - expiration: Expiration, - is_internal_flow: bool, - ) -> Result<(UInt256, BuilderData)> { - if !matches!(self.wallet_type, WalletType::WalletV5R1) { - return Err(TonWalletError::InvalidContractType.into()); - } - let (init_data, _) = wallet_v5r1::get_init_data(current_state.storage.state(), public_key)?; - - let expire_at = ExpireAt::new(self.clock.as_ref(), expiration); - let (hash, builder_data) = - init_data.make_transfer_payload(gifts, expire_at.timestamp, is_internal_flow)?; - Ok((hash, builder_data)) - } - - pub fn make_unsigned_new_wallet_v5_transfer_payload( - &self, - state_init: &ton_block::StateInit, - gifts: Vec, - expiration: Expiration, - is_internal_flow: bool, - ) -> Result<(UInt256, BuilderData)> { - if !matches!(self.wallet_type, WalletType::WalletV5R1) { - return Err(TonWalletError::InvalidContractType.into()); - } - let init_data = wallet_v5r1::get_init_data_from_state_init(state_init)?; - - let expire_at = ExpireAt::new(self.clock.as_ref(), expiration); - let (hash, builder_data) = - init_data.make_transfer_payload(gifts, expire_at.timestamp, is_internal_flow)?; - Ok((hash, builder_data)) - } - - pub fn get_wallet_v5_seqno( - &self, - current_state: &ton_block::AccountStuff, - public_key: &PublicKey, - ) -> Result { - if !matches!(self.wallet_type, WalletType::WalletV5R1) { - return Err(TonWalletError::InvalidContractType.into()); - } - - let (init_data, _) = wallet_v5r1::get_init_data(current_state.storage.state(), public_key)?; - Ok(init_data.seqno) - } - - pub fn prepare_transfer( - &mut self, - current_state: &ton_block::AccountStuff, - public_key: &PublicKey, - gifts: Vec, - expiration: Expiration, - ) -> Result { - match self.wallet_type { - WalletType::Multisig(multisig_type) => { - anyhow::ensure!( - gifts.len() == 1, - "Multiple outgoing messages are not supported by multisig contract" - ); - let gift = gifts.into_iter().next().unwrap(); - - match ¤t_state.storage.state { - ton_block::AccountState::AccountFrozen { .. } => { - return Err(TonWalletError::AccountIsFrozen.into()) - } - ton_block::AccountState::AccountUninit => { - return Ok(TransferAction::DeployFirst) - } - ton_block::AccountState::AccountActive { .. } => {} - }; - - self.wallet_data.update( - self.clock.as_ref(), - &self.public_key, - self.wallet_type, - current_state, - self.handler.as_ref(), - )?; - - let has_multiple_owners = match &self.wallet_data.custodians { - Some(custodians) => custodians.len() > 1, - None => return Err(TonWalletError::CustodiansNotFound.into()), - }; - - multisig::prepare_transfer( - self.clock.as_ref(), - multisig_type, - public_key, - has_multiple_owners, - self.address().clone(), - gift, - expiration, - ) - } - WalletType::WalletV3 => wallet_v3::prepare_transfer( - self.clock.as_ref(), - public_key, - current_state, - 0, - gifts, - expiration, - ), - WalletType::WalletV3R1 => wallet_v3v4::prepare_transfer( - self.clock.as_ref(), - public_key, - current_state, - 0, - gifts, - expiration, - wallet_v3v4::WalletVersion::V3R1, - ), - WalletType::WalletV3R2 => wallet_v3v4::prepare_transfer( - self.clock.as_ref(), - public_key, - current_state, - 0, - gifts, - expiration, - wallet_v3v4::WalletVersion::V3R2, - ), - WalletType::WalletV4R1 => wallet_v3v4::prepare_transfer( - self.clock.as_ref(), - public_key, - current_state, - 0, - gifts, - expiration, - wallet_v3v4::WalletVersion::V4R1, - ), - WalletType::WalletV4R2 => wallet_v3v4::prepare_transfer( - self.clock.as_ref(), - public_key, - current_state, - 0, - gifts, - expiration, - wallet_v3v4::WalletVersion::V4R2, - ), - WalletType::WalletV5R1 => wallet_v5r1::prepare_transfer( - self.clock.as_ref(), - public_key, - current_state, - 0, - gifts, - expiration, - ), - WalletType::EverWallet => ever_wallet::prepare_transfer( - self.clock.as_ref(), - public_key, - current_state, - self.address().clone(), - gifts, - expiration, - ), - WalletType::HighloadWalletV2 => highload_wallet_v2::prepare_transfer( - self.clock.as_ref(), - public_key, - current_state, - gifts, - expiration, - ), - } - } - - pub fn prepare_confirm_transaction( - &self, - current_state: &ton_block::AccountStuff, - public_key: &PublicKey, - transaction_id: u64, - expiration: Expiration, - ) -> Result> { - match self.wallet_type { - WalletType::Multisig(multisig_type) => { - let has_pending_transaction = multisig::find_pending_transaction( - self.clock.as_ref(), - multisig_type, - Cow::Borrowed(current_state), - transaction_id, - )?; - if !has_pending_transaction { - return Err(TonWalletError::PendingTransactionNotFound.into()); - } - - multisig::prepare_confirm_transaction( - self.clock.as_ref(), - multisig_type, - public_key, - self.address().clone(), - transaction_id, - expiration, - ) - } - // Non-multisig wallets doesn't support pending transactions - _ => Err(TonWalletError::PendingTransactionNotFound.into()), - } - } - - pub fn prepare_code_update( - &self, - public_key: &PublicKey, - new_code_hash: &[u8; 32], - expiration: Expiration, - ) -> Result> { - match self.wallet_type { - WalletType::Multisig(multisig_type) if multisig_type.is_multisig2() => { - multisig::prepare_code_update( - self.clock.as_ref(), - multisig_type, - public_key, - self.address().clone(), - new_code_hash, - expiration, - ) - } - _ => Err(TonWalletError::UpdateNotSupported.into()), - } - } - - pub fn prepare_confirm_update( - &self, - current_state: &ton_block::AccountStuff, - public_key: &PublicKey, - update_id: u64, - expiration: Expiration, - ) -> Result> { - match self.wallet_type { - WalletType::Multisig(multisig_type) => { - let pending_update = multisig::find_pending_update( - self.clock.as_ref(), - multisig_type, - Cow::Borrowed(current_state), - update_id, - )?; - if pending_update.is_none() { - return Err(TonWalletError::PendingUpdateNotFound.into()); - } - - multisig::prepare_confirm_update( - self.clock.as_ref(), - multisig_type, - public_key, - self.address().clone(), - update_id, - expiration, - ) - } - _ => Err(TonWalletError::PendingUpdateNotFound.into()), - } - } - - pub fn prepare_execute_code_update( - &self, - current_state: &ton_block::AccountStuff, - public_key: &PublicKey, - update_id: u64, - new_code: ton_types::Cell, - expiration: Expiration, - ) -> Result> { - match self.wallet_type { - WalletType::Multisig(multisig_type) => { - let update = match multisig::find_pending_update( - self.clock.as_ref(), - multisig_type, - Cow::Borrowed(current_state), - update_id, - )? { - Some(update) => update, - None => return Err(TonWalletError::PendingUpdateNotFound.into()), - }; - - if !matches!(update.new_code_hash, Some(hash) if new_code.repr_hash() == hash) { - return Err(TonWalletError::UpdatedDataMismatch.into()); - } - - multisig::prepare_execute_update( - self.clock.as_ref(), - multisig_type, - public_key, - self.address().clone(), - update_id, - Some(new_code), - expiration, - ) - } - _ => Err(TonWalletError::PendingUpdateNotFound.into()), - } - } - - pub async fn send( - &mut self, - message: &ton_block::Message, - expire_at: u32, - ) -> Result { - self.contract_subscription.send(message, expire_at).await - } - - pub async fn refresh(&mut self) -> Result<()> { - let handler = self.handler.as_ref(); - self.contract_subscription - .refresh( - &mut make_contract_state_handler( - self.clock.as_ref(), - handler, - &self.public_key, - self.wallet_type, - &mut self.wallet_data, - ), - &mut make_transactions_handler(handler, self.wallet_type), - &mut make_message_sent_handler(handler), - &mut make_message_expired_handler(handler), - ) - .await - } - - pub async fn handle_block(&mut self, block: &ton_block::Block) -> Result<()> { - // TODO: update wallet data here - - let handler = self.handler.as_ref(); - let new_account_state = self.contract_subscription.handle_block( - block, - &mut make_transactions_handler(handler, self.wallet_type), - &mut make_message_sent_handler(handler), - &mut make_message_expired_handler(handler), - )?; - - if let Some(account_state) = new_account_state { - handler.on_state_changed(account_state); - } - - Ok(()) - } - - pub async fn preload_transactions(&mut self, from_lt: u64) -> Result<()> { - let handler = self.handler.as_ref(); - self.contract_subscription - .preload_transactions( - from_lt, - &mut make_transactions_handler(handler, self.wallet_type), - ) - .await - } - - pub async fn estimate_fees(&mut self, message: &ton_block::Message) -> Result { - self.contract_subscription.estimate_fees(message).await - } -} - -#[derive(Default)] -struct WalletData { - custodians: Option>, - unconfirmed_transactions: Vec, - unconfirmed_updates: Vec, - details: Option, -} - -impl WalletData { - fn update( - &mut self, - clock: &dyn Clock, - public_key: &PublicKey, - wallet_type: WalletType, - account_stuff: &ton_block::AccountStuff, - handler: &dyn TonWalletSubscriptionHandler, - ) -> Result<()> { - // Extract details - if self.details.is_none() { - let mut details = wallet_type.details(); - - if let WalletType::Multisig(multisig_type) = wallet_type { - let params = - multisig::get_params(clock, multisig_type, Cow::Borrowed(account_stuff))?; - details.expiration_time = params.expiration_time.try_into().unwrap_or(u32::MAX); - details.required_confirmations = - NonZeroU8::new(std::cmp::max(params.required_confirms, 1)); - } - - self.details = Some(details); - handler.on_details_changed(details); - } - - // Extract custodians - let multisig_type = match wallet_type { - WalletType::Multisig(multisig_type) => multisig_type, - // Simple path for wallets with single custodian - _ => { - if self.custodians.is_none() { - let custodians = self.custodians.insert(vec![public_key.to_bytes().into()]); - handler.on_custodians_changed(custodians); - } - return Ok(()); - } - }; - - // Extract custodians - let custodians = match &mut self.custodians { - Some(custodians) => custodians, - None => { - let custodians = self.custodians.insert(multisig::get_custodians( - clock, - multisig_type, - Cow::Borrowed(account_stuff), - )?); - handler.on_custodians_changed(custodians); - custodians - } - }; - - if multisig_type.is_updatable() { - let pending_updates = multisig::get_pending_updates( - clock, - multisig_type, - Cow::Borrowed(account_stuff), - custodians, - )?; - if self.unconfirmed_updates != pending_updates { - self.unconfirmed_updates = pending_updates; - handler.on_unconfirmed_updates_changed(&self.unconfirmed_updates); - } - } - - // Skip pending transactions extraction for single custodian - if custodians.len() < 2 { - return Ok(()); - } - - // Extract pending transactions - let pending_transactions = multisig::get_pending_transactions( - clock, - multisig_type, - Cow::Borrowed(account_stuff), - custodians, - )?; - - if self.unconfirmed_transactions != pending_transactions { - self.unconfirmed_transactions = pending_transactions; - handler.on_unconfirmed_transactions_changed(&self.unconfirmed_transactions); - } - - Ok(()) - } -} - -pub fn extract_wallet_init_data(contract: &ExistingContract) -> Result<(PublicKey, WalletType)> { - let (code, data) = match &contract.account.storage.state { - ton_block::AccountState::AccountActive { - state_init: - ton_block::StateInit { - code: Some(code), - data: Some(data), - .. - }, - .. - } => (code, data), - _ => return Err(TonWalletError::AccountNotExists.into()), - }; - - let code_hash = code.repr_hash(); - if let Some(multisig_type) = multisig::guess_multisig_type(&code_hash) { - let public_key = extract_public_key(&contract.account)?; - Ok((public_key, WalletType::Multisig(multisig_type))) - } else if wallet_v3::is_wallet_v3(&code_hash) { - let public_key = PublicKey::from_bytes(wallet_v3::InitData::try_from(data)?.public_key())?; - Ok((public_key, WalletType::WalletV3)) - } else if wallet_v3v4::is_wallet_v4r1(&code_hash) { - let public_key = - PublicKey::from_bytes(wallet_v3v4::InitData::try_from(data)?.public_key())?; - Ok((public_key, WalletType::WalletV4R1)) - } else if wallet_v3v4::is_wallet_v4r2(&code_hash) { - let public_key = - PublicKey::from_bytes(wallet_v3v4::InitData::try_from(data)?.public_key())?; - Ok((public_key, WalletType::WalletV4R2)) - } else if wallet_v5r1::is_wallet_v5r1(&code_hash) { - let public_key = - PublicKey::from_bytes(wallet_v5r1::InitData::try_from(data)?.public_key())?; - Ok((public_key, WalletType::WalletV5R1)) - } else if ever_wallet::is_ever_wallet(&code_hash) { - let public_key = extract_public_key(&contract.account)?; - Ok((public_key, WalletType::EverWallet)) - } else if highload_wallet_v2::is_highload_wallet_v2(&code_hash) { - let public_key = - PublicKey::from_bytes(highload_wallet_v2::InitData::try_from(data)?.public_key())?; - Ok((public_key, WalletType::HighloadWalletV2)) - } else { - Err(TonWalletError::InvalidContractType.into()) - } -} - -pub fn get_wallet_custodians( - clock: &dyn Clock, - contract: &ExistingContract, - public_key: &PublicKey, - wallet_type: WalletType, -) -> Result> { - match wallet_type { - WalletType::Multisig(multisig_type) => { - multisig::get_custodians(clock, multisig_type, Cow::Borrowed(&contract.account)) - } - _ => Ok(vec![public_key.to_bytes().into()]), - } -} - pub const WALLET_TYPES_BY_POPULARITY: [WalletType; 10] = [ WalletType::Multisig(MultisigType::SafeMultisigWallet), WalletType::Multisig(MultisigType::SurfWallet), @@ -852,162 +33,6 @@ pub const WALLET_TYPES_BY_POPULARITY: [WalletType; 10] = [ WalletType::HighloadWalletV2, ]; -pub async fn find_existing_wallets( - transport: &dyn Transport, - public_key: &PublicKey, - workchain_id: i8, - wallet_types: &[WalletType], -) -> Result> { - use futures_util::stream::{FuturesUnordered, TryStreamExt}; - - wallet_types - .iter() - .map(|&wallet_type| async move { - let address = compute_address(public_key, wallet_type, workchain_id); - - let contract_state = transport.get_contract_state(&address).await?; - - Ok(ExistingWalletInfo { - address, - public_key: *public_key, - wallet_type, - contract_state: contract_state.brief(), - }) - }) - .collect::>() - .try_collect::>() - .await -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ExistingWalletInfo { - #[serde(with = "serde_address")] - pub address: MsgAddressInt, - #[serde(with = "serde_public_key")] - pub public_key: PublicKey, - pub wallet_type: WalletType, - pub contract_state: ContractState, -} - -pub trait InternalMessageSender { - fn prepare_transfer( - &mut self, - current_state: &ton_block::AccountStuff, - public_key: &PublicKey, - message: InternalMessage, - expiration: Expiration, - ) -> Result; -} - -impl InternalMessageSender for TonWallet { - fn prepare_transfer( - &mut self, - current_state: &ton_block::AccountStuff, - public_key: &PublicKey, - message: InternalMessage, - expiration: Expiration, - ) -> Result { - if matches!(message.source, Some(source) if &source != self.address()) { - return Err(InternalMessageSenderError::InvalidSender.into()); - } - - let gift = Gift { - flags: MessageFlags::default().into(), - bounce: message.bounce, - destination: message.destination, - amount: message.amount, - body: Some(message.body), - state_init: None, - }; - - self.prepare_transfer(current_state, public_key, vec![gift], expiration) - } -} - -#[derive(thiserror::Error, Debug)] -enum InternalMessageSenderError { - #[error("Invalid sender")] - InvalidSender, -} - -#[derive(thiserror::Error, Debug)] -enum TonWalletError { - #[error("Account not exists")] - AccountNotExists, - #[error("Account is frozen")] - AccountIsFrozen, - #[error("Invalid contract type")] - InvalidContractType, - #[error("Custodians not found")] - CustodiansNotFound, - #[error("Pending transaction not found")] - PendingTransactionNotFound, - #[error("Update not supported")] - UpdateNotSupported, - #[error("Pending update not found")] - PendingUpdateNotFound, - #[error("Updated data mismatch")] - UpdatedDataMismatch, -} - -fn make_contract_state_handler<'a>( - clock: &'a dyn Clock, - handler: &'a dyn TonWalletSubscriptionHandler, - public_key: &'a PublicKey, - wallet_type: WalletType, - wallet_data: &'a mut WalletData, -) -> impl FnMut(&RawContractState) + 'a { - move |contract_state| { - if let RawContractState::Exists(contract_state) = contract_state { - if let Err(e) = wallet_data.update( - clock, - public_key, - wallet_type, - &contract_state.account, - handler, - ) { - log::error!("{e}"); - } - } - handler.on_state_changed(contract_state.brief()) - } -} - -fn make_transactions_handler( - handler: &'_ dyn TonWalletSubscriptionHandler, - wallet_type: WalletType, -) -> impl FnMut(Vec, TransactionsBatchInfo) + '_ { - move |transactions, batch_info| { - let transactions = transactions - .into_iter() - .filter_map(move |transaction| { - let data = parse_transaction_additional_info(&transaction.data, wallet_type); - let transaction = - Transaction::try_from((transaction.hash, transaction.data)).ok()?; - Some(TransactionWithData { transaction, data }) - }) - .collect(); - - handler.on_transactions_found(transactions, batch_info) - } -} - -fn make_message_sent_handler( - handler: &'_ dyn TonWalletSubscriptionHandler, -) -> impl FnMut(PendingTransaction, RawTransaction) + '_ { - move |pending_transaction, transaction| { - let transaction = Transaction::try_from((transaction.hash, transaction.data)).ok(); - handler.on_message_sent(pending_transaction, transaction); - } -} - -fn make_message_expired_handler( - handler: &'_ dyn TonWalletSubscriptionHandler, -) -> impl FnMut(PendingTransaction) + '_ { - move |pending_transaction| handler.on_message_expired(pending_transaction) -} - #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct TonWalletDetails { pub requires_separate_deploy: bool, @@ -1033,12 +58,6 @@ pub struct Gift { pub state_init: Option, } -#[derive(Clone)] -pub enum TransferAction { - DeployFirst, - Sign(Box), -} - #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum WalletType { Multisig(MultisigType), @@ -1089,7 +108,7 @@ impl WalletType { } } - pub fn code(&self) -> ton_types::Cell { + pub fn code(&self) -> Cell { use nekoton_contracts::wallets; match self { Self::Multisig(multisig_type) => multisig_type.code(), @@ -1167,7 +186,7 @@ pub fn compute_address( public_key: &PublicKey, wallet_type: WalletType, workchain_id: i8, -) -> MsgAddressInt { +) -> Result { match wallet_type { WalletType::Multisig(multisig_type) => { multisig::compute_contract_address(public_key, multisig_type, workchain_id) @@ -1201,55 +220,67 @@ pub fn compute_address( } } -pub trait TonWalletSubscriptionHandler: Send + Sync { - /// Called when found transaction which is relative with one of the pending transactions - fn on_message_sent( - &self, - pending_transaction: PendingTransaction, - transaction: Option, - ); +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MultisigPendingTransaction { + pub id: u64, + pub confirmations: Vec, + pub signs_required: u8, + pub signs_received: u8, + pub creator: HashBytes, + pub index: u8, + pub dest: StdAddr, + pub value: u128, + pub send_flags: u16, + pub payload: Cell, + pub bounce: bool, +} - /// Called when no transactions produced for the specific message before some expiration time - fn on_message_expired(&self, pending_transaction: PendingTransaction); +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MultisigPendingUpdate { + pub id: u64, + pub confirmations: Vec, + pub signs_received: u8, + pub creator: HashBytes, + pub index: u8, + pub new_code_hash: Option, + pub new_custodians: Option>, + pub new_req_confirms: Option, + pub new_lifetime: Option, +} - /// Called every time a new state is detected - fn on_state_changed(&self, new_state: ContractState) { - let _ = new_state; - } +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub enum MessageFlags { + #[default] + Normal, + AllBalance, + AllBalanceDeleteNetworkAccount, +} - /// Called every time new transactions are detected. - /// - When new block found - /// - When manually requesting the latest transactions (can be called several times) - /// - When preloading transactions - fn on_transactions_found( - &self, - transactions: Vec>, - batch_info: TransactionsBatchInfo, - ) { - let _ = transactions; - let _ = batch_info; - } +impl TryFrom for MessageFlags { + type Error = MessageFlagsError; - /// Called when wallet details changed (e.g. expiration time or required confirms) - fn on_details_changed(&self, details: TonWalletDetails) { - let _ = details; - } - - /// Called when wallet custodians changed (e.g. on code upgrade or first refresh) - fn on_custodians_changed(&self, custodians: &[UInt256]) { - let _ = custodians; + fn try_from(value: u8) -> Result { + match value { + 3 => Ok(MessageFlags::Normal), + 128 => Ok(MessageFlags::AllBalance), + 160 => Ok(MessageFlags::AllBalanceDeleteNetworkAccount), + _ => Err(MessageFlagsError::UnknownMessageFlags), + } } +} - /// Called when wallet has new pending transactions set - fn on_unconfirmed_transactions_changed( - &self, - unconfirmed_transactions: &[MultisigPendingTransaction], - ) { - let _ = unconfirmed_transactions; +impl From for u8 { + fn from(value: MessageFlags) -> u8 { + match value { + MessageFlags::Normal => 3, + MessageFlags::AllBalance => 128, + MessageFlags::AllBalanceDeleteNetworkAccount => 128 + 32, + } } +} - /// Called when wallet has new pending updates set - fn on_unconfirmed_updates_changed(&self, unconfirmed_updates: &[MultisigPendingUpdate]) { - let _ = unconfirmed_updates; - } +#[derive(thiserror::Error, Debug, Copy, Clone)] +pub enum MessageFlagsError { + #[error("Unknown message flags combination")] + UnknownMessageFlags, } diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index 5704d9c..a14e5b8 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -3,18 +3,19 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::PublicKey; -use ton_block::{Deserializable, GetRepresentationHash, MsgAddressInt, Serializable}; -use ton_types::UInt256; +use tycho_types::{ + abi::{AbiValue, Function, NamedAbiValue, UnsignedExternalMessage}, + cell::{Cell, CellBuilder, CellDataBuilder, HashBytes}, + dict::RawDict, + models::{Account, StateInit, StdAddr}, +}; use nekoton_abi::*; use nekoton_utils::*; -use super::{Gift, TonWalletDetails, TransferAction}; -use crate::core::models::{ - Expiration, MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate, -}; -use crate::core::utils::*; -use crate::crypto::UnsignedMessage; +use crate::utils::ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}; + +use super::{Gift, TonWalletDetails}; #[derive(Copy, Clone, Debug)] pub struct DeployParams<'a> { @@ -34,81 +35,70 @@ impl<'a> DeployParams<'a> { } pub fn prepare_deploy( - clock: &dyn Clock, public_key: &PublicKey, multisig_type: MultisigType, workchain: i8, - expiration: Expiration, + expire_at: u32, params: DeployParams<'_>, -) -> Result> { +) -> Result { let state_init = prepare_state_init(public_key, multisig_type); - let hash = state_init.hash().trust_me(); + let hash = CellBuilder::build_from(&state_init)?.repr_hash(); - let dst = MsgAddressInt::AddrStd(ton_block::MsgAddrStd { - anycast: None, - workchain_id: workchain, - address: hash.into(), - }); - - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst, - ..Default::default() - }); - - message.set_state_init(state_init); + let dst = StdAddr::new(workchain, hash.into()); let owners = params .owners .iter() - .map(|public_key| UInt256::from(public_key.as_bytes())) - .collect::>(); + .map(|public_key| HashBytes::from(public_key.as_bytes())) + .collect::>(); let is_new_multisig = multisig_type.is_multisig2(); let function = if is_new_multisig { - nekoton_contracts::wallets::multisig2::constructor() + crate::utils::wallets::multisig2::constructor() } else if params.expiration_time.is_none() { - nekoton_contracts::wallets::multisig::constructor() + crate::utils::wallets::multisig::constructor() } else { return Err(MultisigError::CustomExpirationTimeNotSupported.into()); }; - let (function, input) = { - let mut message = MessageBuilder::new(function) - .arg(owners) - .arg(params.req_confirms); + let external_input = { + let mut abi_values = vec![ + NamedAbiValue::from(("owners", owners)), + NamedAbiValue::from(("reqConfirms", params.req_confirms)), + ]; if is_new_multisig { - message = message.arg(params.expiration_time.unwrap_or(DEFAULT_LIFETIME)); + abi_values.push(NamedAbiValue::from(("lifetime", DEFAULT_LIFETIME))); } - message.build() + + function.encode_external(&abi_values) }; - make_labs_unsigned_message( - clock, - message, - expiration, - public_key, - Cow::Borrowed(function), - input, - ) + let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; + let mut unsigned_message = unsigned_body.with_dst(dst); + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) } pub fn prepare_confirm_transaction( - clock: &dyn Clock, multisig_type: MultisigType, public_key: &PublicKey, - address: MsgAddressInt, + address: StdAddr, transaction_id: u64, - expiration: Expiration, -) -> Result> { + expire_at: u32, +) -> Result { let function = if multisig_type.is_multisig2() { - nekoton_contracts::wallets::multisig2::confirm_transaction() + crate::utils::wallets::multisig2::confirm_transaction() } else { - nekoton_contracts::wallets::multisig::confirm_transaction() + crate::utils::wallets::multisig::confirm_transaction() }; - let (function, input) = MessageBuilder::new(function).arg(transaction_id).build(); - make_ext_message(clock, public_key, address, expiration, function, input) + make_ext_message( + public_key, + address, + expire_at, + function, + vec![NamedAbiValue::from(("transactionId", transaction_id))], + ) } pub fn prepare_transfer( @@ -116,10 +106,10 @@ pub fn prepare_transfer( multisig_type: MultisigType, public_key: &PublicKey, has_multiple_owners: bool, - address: MsgAddressInt, + address: StdAddr, gift: Gift, - expiration: Expiration, -) -> Result { + expire_at: u32, +) -> Result { let is_new_multisig = multisig_type.is_multisig2(); let (function, input) = if has_multiple_owners || is_new_multisig && gift.state_init.is_some() { @@ -130,70 +120,67 @@ pub fn prepare_transfer( }; let function = if is_new_multisig { - nekoton_contracts::wallets::multisig2::submit_transaction() + crate::utils::wallets::multisig2::submit_transaction() } else { - nekoton_contracts::wallets::multisig::submit_transaction() + crate::utils::wallets::multisig::submit_transaction() }; - let message = MessageBuilder::new(function) - .arg(gift.destination) - .arg(BigUint128(gift.amount.into())) - .arg(gift.bounce) - .arg(all_balance) - .arg(gift.body.unwrap_or_default().into_cell()); + let mut named_abi_values = vec![ + NamedAbiValue::from(("destination", gift.destination)), + NamedAbiValue::from(("amount", gift.amount.into())), + NamedAbiValue::from(("bounce", gift.bounce)), + NamedAbiValue::from(("flags", all_balance)), + NamedAbiValue::from(("body", gift.body.unwrap_or_default().into_cell())), + ]; if is_new_multisig { - message - .arg( - gift.state_init - .map(|state_init| state_init.serialize()) - .transpose()?, - ) - .build() - } else { - message.build() + named_abi_values.push(NamedAbiValue::from(( + "stateInit", + gift.state_init + .map(|state_init| CellBuilder::build_from(&state_init)) + .transpose()?, + ))); } + (function, named_abi_values) } else { let function = if is_new_multisig { - nekoton_contracts::wallets::multisig2::send_transaction() + crate::utils::wallets::multisig2::send_transaction() } else { - nekoton_contracts::wallets::multisig::send_transaction() + crate::utils::wallets::multisig::send_transaction() }; - MessageBuilder::new(function) - .arg(gift.destination) - .arg(BigUint128(gift.amount.into())) - .arg(gift.bounce) - .arg(gift.flags) - .arg(gift.body.unwrap_or_default().into_cell()) - .build() + let mut named_abi_values = vec![ + NamedAbiValue::from(("destination", gift.destination)), + NamedAbiValue::from(("amount", gift.amount.into())), + NamedAbiValue::from(("bounce", gift.bounce)), + NamedAbiValue::from(("flags", gift.flags)), + NamedAbiValue::from(("body", gift.body.unwrap_or_default().into_cell())), + ]; + (function, named_abi_values) }; - make_ext_message(clock, public_key, address, expiration, function, input) - .map(TransferAction::Sign) + make_ext_message(public_key, address, expire_at, function, input) } pub fn prepare_code_update( - clock: &dyn Clock, multisig_type: MultisigType, public_key: &PublicKey, - address: MsgAddressInt, + address: StdAddr, new_code_hash: &[u8; 32], - expiration: Expiration, -) -> Result> { - use nekoton_contracts::wallets::multisig2; + expire_at: u32, +) -> Result { + use crate::utils::wallets::multisig2; if !multisig_type.is_multisig2() { return Err(MultisigError::UnsupportedUpdate.into()); } make_ext_message( - clock, public_key, address, - expiration, + expire_at, multisig2::submit_update(), multisig2::SubmitUpdateParams { - code_hash: Some(ton_types::UInt256::from(*new_code_hash)), + code_hash: Some(HashBytes::from(*new_code_hash)), owners: None, req_confirms: None, lifetime: None, @@ -203,49 +190,45 @@ pub fn prepare_code_update( } pub fn prepare_confirm_update( - clock: &dyn Clock, multisig_type: MultisigType, public_key: &PublicKey, - address: MsgAddressInt, + address: StdAddr, update_id: u64, - expiration: Expiration, -) -> Result> { - use nekoton_contracts::wallets::multisig2; + expire_at: u32, +) -> Result { + use crate::utils::wallets::multisig2; if !multisig_type.is_multisig2() { return Err(MultisigError::UnsupportedUpdate.into()); } make_ext_message( - clock, public_key, address, - expiration, + expire_at, multisig2::confirm_update(), multisig2::ConfirmUpdateParams { update_id }.pack(), ) } pub fn prepare_execute_update( - clock: &dyn Clock, multisig_type: MultisigType, public_key: &PublicKey, - address: MsgAddressInt, update_id: u64, - code: Option, - expiration: Expiration, -) -> Result> { - use nekoton_contracts::wallets::multisig2; + code: Option, + address: StdAddr, + expire_at: u32, +) -> Result { + use crate::utils::wallets::multisig2; if !multisig_type.is_multisig2() { return Err(MultisigError::UnsupportedUpdate.into()); } make_ext_message( - clock, public_key, address, - expiration, + expire_at, multisig2::execute_update(), multisig2::ExecuteUpdateParams { update_id, code }.pack(), ) @@ -281,8 +264,8 @@ impl MultisigType { ) } - pub fn state_init(&self) -> ton_block::StateInit { - use nekoton_contracts::wallets; + pub fn state_init(&self) -> StateInit { + use crate::utils::wallets; let state_init = match self { MultisigType::SafeMultisigWallet => wallets::code::safe_multisig_wallet(), @@ -294,9 +277,7 @@ impl MultisigType { MultisigType::Multisig2 => wallets::code::multisig2(), MultisigType::Multisig2_1 => wallets::code::multisig2_1(), }; - let mut state_init = ton_types::SliceData::load_cell(state_init).trust_me(); - - ton_block::StateInit::construct_from(&mut state_init).trust_me() + StateInit::load_from(&mut state_init.as_slice()?).trust_me() } pub fn code_hash(&self) -> &[u8; 32] { @@ -312,7 +293,7 @@ impl MultisigType { } } - pub fn code(&self) -> ton_types::Cell { + pub fn code(&self) -> Cell { self.state_init().code.trust_me() } } @@ -350,7 +331,7 @@ static MULTISIG2_1_HASH: &[u8; 32] = &[ 0x37, 0x1f, 0x51, 0x12, 0x55, 0x24, 0x08, 0x62, 0x5a, 0xeb, 0x0b, 0x31, 0xe0, 0xef, 0x2d, 0xf3, ]; -pub fn guess_multisig_type(code_hash: &UInt256) -> Option { +pub fn guess_multisig_type(code_hash: &HashBytes) -> Option { match code_hash.as_slice() { s if s == SAFE_MULTISIG_WALLET_HASH => Some(MultisigType::SafeMultisigWallet), s if s == SAFE_MULTISIG_WALLET_24H_HASH => Some(MultisigType::SafeMultisigWallet24h), @@ -368,15 +349,10 @@ pub fn compute_contract_address( public_key: &PublicKey, multisig_type: MultisigType, workchain_id: i8, -) -> MsgAddressInt { +) -> Result { let state_init = prepare_state_init(public_key, multisig_type); - let hash = state_init.hash().trust_me(); - - MsgAddressInt::AddrStd(ton_block::MsgAddrStd { - anycast: None, - workchain_id, - address: hash.into(), - }) + let hash = CellBuilder::build_from(&state_init)?.repr_hash(); + Ok(StdAddr::new(workchain_id, hash.into())) } pub fn ton_wallet_details(multisig_type: MultisigType) -> TonWalletDetails { @@ -406,27 +382,37 @@ pub fn ton_wallet_details(multisig_type: MultisigType) -> TonWalletDetails { } } -pub fn prepare_state_init( - public_key: &PublicKey, - multisig_type: MultisigType, -) -> ton_block::StateInit { +pub fn prepare_state_init(public_key: &PublicKey, multisig_type: MultisigType) -> StateInit { let mut state_init = multisig_type.state_init(); - let new_data = ton_abi::Contract::insert_pubkey( - ton_types::SliceData::load_cell(state_init.data.clone().unwrap_or_default()).trust_me(), - public_key.as_bytes(), - ) - .trust_me(); - state_init.set_data(new_data.into_cell()); + let mut result = if state_init.data.is_none() { + RawDict::new() + } else { + state_init.data.parse::>()? + }; + + let context = Cell::empty_context(); + let mut key_builder = CellDataBuilder::new(); + + key_builder.store_u64(0)?; + result.set_ext( + key_builder.as_data_slice(), + &CellBuilder::from_raw_data(public_key.as_bytes(), 256)?.as_data_slice(), + context, + )?; + + // Encode init data as mapping + let cell = CellBuilder::build_from_ext(result, context)?; + state_init.data = Some(cell); state_init } fn run_local( clock: &dyn Clock, - function: &ton_abi::Function, - account_stuff: ton_block::AccountStuff, -) -> Result> { + function: &Function, + account_stuff: Account, +) -> Result> { let ExecutionOutput { tokens, result_code, @@ -434,70 +420,64 @@ fn run_local( tokens.ok_or_else(|| MultisigError::NonZeroResultCode(result_code).into()) } -#[derive(Copy, Clone, UnpackAbiPlain)] +#[derive(Copy, Clone)] pub struct MultisigParamsPrefix { - #[abi(uint8, name = "maxQueuedTransactions")] pub max_queued_transactions: u8, - #[abi(uint8, name = "maxCustodianCount")] pub max_custodian_count: u8, - #[abi(uint64, name = "expirationTime")] pub expiration_time: u64, - #[abi(uint128, name = "minValue")] pub min_value: u128, - #[abi(uint8, name = "requiredTxnConfirms")] pub required_confirms: u8, } pub fn get_params( clock: &dyn Clock, multisig_type: MultisigType, - account_stuff: Cow<'_, ton_block::AccountStuff>, + account: Cow<'_, Account>, ) -> Result { let function = match multisig_type { MultisigType::Multisig2 | MultisigType::Multisig2_1 => { - nekoton_contracts::wallets::multisig2::get_parameters() + crate::utils::wallets::multisig2::get_parameters() } MultisigType::SafeMultisigWallet | MultisigType::SafeMultisigWallet24h | MultisigType::BridgeMultisigWallet => { - nekoton_contracts::wallets::multisig::safe_multisig::get_parameters() + crate::utils::wallets::multisig::safe_multisig::get_parameters() } MultisigType::SetcodeMultisigWallet | MultisigType::SetcodeMultisigWallet24h | MultisigType::SurfWallet => { - nekoton_contracts::wallets::multisig::set_code_multisig::get_parameters() + crate::utils::wallets::multisig::set_code_multisig::get_parameters() } }; let output: MultisigParamsPrefix = - run_local(clock, function, account_stuff.into_owned())?.unpack()?; + run_local(clock, function, account.into_owned())?.unpack()?; Ok(output) } pub fn get_custodians( clock: &dyn Clock, multisig_type: MultisigType, - account_stuff: Cow<'_, ton_block::AccountStuff>, -) -> Result> { + account: Cow<'_, Account>, +) -> Result> { let function = if multisig_type.is_multisig2() { - nekoton_contracts::wallets::multisig2::get_custodians() + crate::utils::wallets::multisig2::get_custodians() } else { - nekoton_contracts::wallets::multisig::get_custodians() + crate::utils::wallets::multisig::get_custodians() }; - run_local(clock, function, account_stuff.into_owned()) - .and_then(parse_multisig_contract_custodians) + run_local(clock, function, account.into_owned()).and_then(parse_multisig_contract_custodians) } -fn parse_multisig_contract_custodians(tokens: Vec) -> Result> { +fn parse_multisig_contract_custodians(tokens: Vec) -> Result> { let array = match tokens.into_unpacker().unpack_next() { - Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + Ok(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; let mut custodians = array .into_iter() .map(|item| item.unpack()) - .collect::, _>>()?; + .collect::, _>>()?; custodians.sort_by(|a, b| a.index.cmp(&b.index)); @@ -507,25 +487,24 @@ fn parse_multisig_contract_custodians(tokens: Vec) -> Result, + account: Cow<'_, Account>, pending_transaction_id: u64, ) -> Result { - #[derive(Copy, Clone, UnpackAbi)] + #[derive(Copy, Clone)] pub struct MultisigTransactionId { - #[abi(uint64)] pub id: u64, } let function = if multisig_type.is_multisig2() { - nekoton_contracts::wallets::multisig2::get_transactions() + crate::utils::wallets::multisig2::get_transactions() } else { - nekoton_contracts::wallets::multisig::get_transactions() + crate::utils::wallets::multisig::get_transactions() }; - let tokens = run_local(clock, function, account_stuff.into_owned())?; + let tokens = run_local(clock, function, account.into_owned())?; let array = match tokens.into_unpacker().unpack_next() { - Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + Ok(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; @@ -541,10 +520,10 @@ pub fn find_pending_transaction( pub fn find_pending_update( clock: &dyn Clock, multisig_type: MultisigType, - account_stuff: Cow<'_, ton_block::AccountStuff>, + account: Cow<'_, Account>, update_id: u64, ) -> Result> { - use nekoton_contracts::wallets::multisig2; + use crate::utils::wallets::multisig2; let function = match multisig_type { MultisigType::Multisig2 => multisig2::v2_0::get_update_requests(), @@ -552,10 +531,10 @@ pub fn find_pending_update( _ => return Ok(None), }; - let tokens = run_local(clock, function, account_stuff.into_owned())?; + let tokens = run_local(clock, function, account.into_owned())?; let array = match tokens.into_unpacker().unpack_next() { - Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + Ok(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; @@ -576,8 +555,8 @@ pub fn find_pending_update( #[derive(Debug, Clone)] pub struct UpdatedParams { - pub new_code_hash: Option, - pub new_custodians: Option>, + pub new_code_hash: Option, + pub new_custodians: Option>, pub new_req_confirms: Option, pub new_lifetime: Option, } @@ -585,17 +564,17 @@ pub struct UpdatedParams { pub fn get_pending_transactions( clock: &dyn Clock, multisig_type: MultisigType, - account_stuff: Cow<'_, ton_block::AccountStuff>, - custodians: &[UInt256], + account: Cow<'_, Account>, + custodians: &[HashBytes], ) -> Result> { let function = if multisig_type.is_multisig2() { - nekoton_contracts::wallets::multisig2::get_transactions() + crate::utils::wallets::multisig2::get_transactions() } else { - nekoton_contracts::wallets::multisig::get_transactions() + crate::utils::wallets::multisig::get_transactions() }; - run_local(clock, function, account_stuff.into_owned()).and_then(|tokens| { + run_local(clock, function, account.into_owned()).and_then(|tokens| { let array = match tokens.into_unpacker().unpack_next() { - Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + Ok(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; @@ -611,10 +590,10 @@ pub fn get_pending_transactions( pub fn get_pending_updates( clock: &dyn Clock, multisig_type: MultisigType, - account_stuff: Cow<'_, ton_block::AccountStuff>, - custodians: &[UInt256], + account: Cow<'_, Account>, + custodians: &[HashBytes], ) -> Result> { - use nekoton_contracts::wallets::multisig2; + use crate::utils::wallets::multisig2; let function = match multisig_type { MultisigType::Multisig2 => multisig2::v2_0::get_update_requests(), @@ -622,9 +601,9 @@ pub fn get_pending_updates( _ => return Ok(Vec::new()), }; - run_local(clock, function, account_stuff.into_owned()).and_then(|tokens| { + run_local(clock, function, account.into_owned()).and_then(|tokens| { let array = match tokens.into_unpacker().unpack_next() { - Ok(ton_abi::TokenValue::Array(_, tokens)) => tokens, + Ok(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; @@ -638,15 +617,15 @@ pub fn get_pending_updates( } fn extend_pending_transaction( - tx: nekoton_contracts::wallets::multisig::MultisigTransaction, - custodians: &[UInt256], + tx: crate::utils::wallets::multisig::MultisigTransaction, + custodians: &[HashBytes], ) -> MultisigPendingTransaction { let confirmations = custodians .iter() .enumerate() .filter(|(i, _)| (0b1 << i) & tx.confirmation_mask != 0) .map(|(_, item)| *item) - .collect::>(); + .collect::>(); MultisigPendingTransaction { id: tx.id, @@ -664,15 +643,15 @@ fn extend_pending_transaction( } fn extend_pending_update( - tx: nekoton_contracts::wallets::multisig2::UpdateTransaction, - custodians: &[UInt256], + tx: crate::utils::wallets::multisig2::UpdateTransaction, + custodians: &[HashBytes], ) -> MultisigPendingUpdate { let confirmations = custodians .iter() .enumerate() .filter(|(i, _)| (0b1 << i) & tx.confirmations_mask != 0) .map(|(_, item)| *item) - .collect::>(); + .collect::>(); MultisigPendingUpdate { id: tx.id, @@ -688,26 +667,17 @@ fn extend_pending_update( } fn make_ext_message( - clock: &dyn Clock, public_key: &PublicKey, - address: MsgAddressInt, - expiration: Expiration, - function: &'static ton_abi::Function, - input: Vec, -) -> Result> { - let message = ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst: address, - ..Default::default() - }); - - make_labs_unsigned_message( - clock, - message, - expiration, - public_key, - Cow::Borrowed(function), - input, - ) + address: StdAddr, + expire_at: u32, + function: &'static Function, + input: Vec, +) -> Result { + let external_input = function.encode_external(&input); + let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; + let mut unsigned_message = unsigned_body.with_dst(address); + + Ok(unsigned_message) } const DEFAULT_LIFETIME: u32 = 3600; diff --git a/src/utils/wallets/ever_wallet.rs b/src/utils/wallets/ever_wallet.rs index 11190ba..7646853 100644 --- a/src/utils/wallets/ever_wallet.rs +++ b/src/utils/wallets/ever_wallet.rs @@ -1,58 +1,6 @@ use tycho_types::abi::{AbiType, Function}; -use crate::utils::wallets::ever_wallet::utils::declare_function; - -pub mod utils { - macro_rules! declare_function { - ( - $(abi: $abi:ident,)? - $(function_id: $id:literal,)? - $(header: [$($header:ident),+],)? - name: $name:literal, - inputs: $inputs:expr, - outputs: $outputs:expr$(,)? - ) => { - static ONCE: std::sync::OnceLock = std::sync::OnceLock::new(); - ONCE.get_or_init(|| { - let mut builder = tycho_types::abi::Function::builder($crate::utils::wallets::ever_wallet::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) - .with_headers($crate::utils::wallets::ever_wallet::utils::declare_function!(@header $($($header),+)?)) - .with_inputs($inputs) - .with_outputs($outputs); - - $crate::utils::wallets::ever_wallet::utils::declare_function!(@function_id builder $($id)?); - - builder - .build() - }) - }; - - (@function_id $builder:ident $id:literal) => { $builder.with_id($id) }; - (@function_id $builder:ident ) => {}; - - (@abi_version) => { tycho_types::abi::AbiVersion::V2_2 }; - (@abi_version v2_0) => { tycho_types::abi::AbiVersion::V2_0 }; - (@abi_version v2_1) => { tycho_types::abi::AbiVersion::V2_1 }; - (@abi_version v2_2) => { tycho_types::abi::AbiVersion::V2_2 }; - (@abi_version v2_3) => { tycho_types::abi::AbiVersion::V2_3 }; - (@abi_version v2_7) => { tycho_types::abi::AbiVersion::V2_7 }; - - (@header) => { Vec::new() }; - (@header $($header:ident),+) => { - vec![$($crate::utils::wallets::ever_wallet::utils::declare_function!(@header_item $header)),+] - }; - (@header_item pubkey) => { - tycho_types::abi::AbiHeaderType::Pubkey - }; - (@header_item time) => { - tycho_types::abi::AbiHeaderType::Time - }; - (@header_item expire) => { - tycho_types::abi::AbiHeaderType::Expire - }; - } - - pub(crate) use declare_function; -} +use crate::utils::declare_function; pub fn send_transaction() -> &'static Function { declare_function! { diff --git a/src/utils/wallets/multisig.rs b/src/utils/wallets/multisig.rs index e356240..cd6098a 100644 --- a/src/utils/wallets/multisig.rs +++ b/src/utils/wallets/multisig.rs @@ -1,117 +1,114 @@ -use nekoton_abi::*; -use ton_abi::{Param, ParamType}; +use std::sync::Arc; + +use tycho_types::{ + abi::{AbiType, Function}, + cell::{Cell, HashBytes}, + models::StdAddr, +}; use crate::utils::declare_function; -pub fn constructor() -> &'static ton_abi::Function { +pub fn constructor() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "constructor", inputs: vec![ - Param::new("owners", ParamType::Array(Box::new(ParamType::Uint(256)))), - Param::new("reqConfirms", ParamType::Uint(8)), + AbiType::Array(Arc::new(AbiType::Uint(256))).named("owners"), + AbiType::Uint(8).named("reqConfirms"), ], outputs: Vec::new(), } } -pub fn send_transaction() -> &'static ton_abi::Function { +pub fn send_transaction() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "sendTransaction", inputs: vec![ - Param::new("dest", ParamType::Address), - Param::new("value", ParamType::Uint(128)), - Param::new("bounce", ParamType::Bool), - Param::new("flags", ParamType::Uint(8)), - Param::new("payload", ParamType::Cell), + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Uint(8).named("flags"), + AbiType::Cell.named("payload"), ], outputs: Vec::new(), } } -pub fn submit_transaction() -> &'static ton_abi::Function { +pub fn submit_transaction() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "submitTransaction", inputs: vec![ - Param::new("dest", ParamType::Address), - Param::new("value", ParamType::Uint(128)), - Param::new("bounce", ParamType::Bool), - Param::new("allBalance", ParamType::Bool), - Param::new("payload", ParamType::Cell) + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Bool.named("allBalance"), + AbiType::Cell.named("payload"), ], - outputs: vec![Param::new("transId", ParamType::Uint(64))], + outputs: vec![ + AbiType::Uint(64).named("transId") + ], } } -pub fn confirm_transaction() -> &'static ton_abi::Function { +pub fn confirm_transaction() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "confirmTransaction", - inputs: vec![Param::new("transactionId", ParamType::Uint(64))], + inputs: vec![ + AbiType::Uint(64).named("transactionId"), + ], outputs: Vec::new(), } } -#[derive(Debug, UnpackAbi, KnownParamType)] +#[derive(Debug)] pub struct MultisigTransaction { - #[abi(uint64)] pub id: u64, - #[abi(uint32, name = "confirmationsMask")] pub confirmation_mask: u32, - #[abi(uint8, name = "signsRequired")] pub signs_required: u8, - #[abi(uint8, name = "signsReceived")] pub signs_received: u8, - #[abi(uint256)] - pub creator: ton_types::UInt256, - #[abi(uint8)] + pub creator: HashBytes, pub index: u8, - #[abi(address)] - pub dest: ton_block::MsgAddressInt, - #[abi(uint128)] + pub dest: StdAddr, pub value: u128, - #[abi(uint16, name = "sendFlags")] pub send_flags: u16, - #[abi(cell)] - pub payload: ton_types::Cell, - #[abi(bool)] + pub payload: Cell, pub bounce: bool, } -pub fn get_transactions() -> &'static ton_abi::Function { +pub fn get_transactions() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "getTransactions", inputs: Vec::new(), outputs: vec![ - Param::new("transactions", ParamType::Array(Box::new(MultisigTransaction::param_type()))) + AbiType::Array(Arc::new(MultisigTransaction::param_type())).named("transactions") ] } } -#[derive(Debug, UnpackAbi, KnownParamType, Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub struct MultisigCustodian { - #[abi(uint8)] pub index: u8, - #[abi(uint256)] - pub pubkey: ton_types::UInt256, + pub pubkey: HashBytes, } -pub fn get_custodians() -> &'static ton_abi::Function { +pub fn get_custodians() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "getCustodians", inputs: Vec::new(), outputs: vec![ - Param::new("custodians", ParamType::Array(Box::new(MultisigCustodian::param_type()))) + AbiType::Array(Arc::new(MultisigCustodian::param_type())).named("custodians") + ] } } @@ -119,21 +116,16 @@ pub fn get_custodians() -> &'static ton_abi::Function { pub mod safe_multisig { use super::*; - #[derive(Debug, Clone, Copy, UnpackAbiPlain, KnownParamTypePlain)] + #[derive(Debug, Clone, Copy)] pub struct SafeMultisigParams { - #[abi(uint8, name = "maxQueuedTransactions")] pub max_queued_transactions: u8, - #[abi(uint8, name = "maxCustodianCount")] pub max_custodian_count: u8, - #[abi(uint64, name = "expirationTime")] pub expiration_time: u64, - #[abi(uint128, name = "minValue")] pub min_value: u128, - #[abi(uint8, name = "requiredTxnConfirms")] pub required_txn_confirms: u8, } - pub fn get_parameters() -> &'static ton_abi::Function { + pub fn get_parameters() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], @@ -147,23 +139,17 @@ pub mod safe_multisig { pub mod set_code_multisig { use super::*; - #[derive(Debug, Clone, Copy, UnpackAbiPlain, KnownParamTypePlain)] + #[derive(Debug, Clone, Copy)] pub struct SetCodeMultisigParams { - #[abi(uint8, name = "maxQueuedTransactions")] pub max_queued_transactions: u8, - #[abi(uint8, name = "maxCustodianCount")] pub max_custodian_count: u8, - #[abi(uint64, name = "expirationTime")] pub expiration_time: u64, - #[abi(uint128, name = "minValue")] pub min_value: u128, - #[abi(uint8, name = "requiredTxnConfirms")] pub required_txn_confirms: u8, - #[abi(uint8, name = "requiredUpdConfirms")] pub required_upd_confirms: u8, } - pub fn get_parameters() -> &'static ton_abi::Function { + pub fn get_parameters() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs index 01d137c..6d2f407 100644 --- a/src/utils/wallets/multisig2.rs +++ b/src/utils/wallets/multisig2.rs @@ -1,144 +1,134 @@ -use nekoton_abi::*; -use ton_abi::{Param, ParamType}; +use std::sync::Arc; + +use tycho_types::{ + abi::{AbiType, Function}, + cell::{Cell, HashBytes}, + models::StdAddr, +}; use crate::utils::declare_function; -pub fn constructor() -> &'static ton_abi::Function { +pub fn constructor() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "constructor", inputs: vec![ - Param::new("owners", ParamType::Array(Box::new(ParamType::Uint(256)))), - Param::new("reqConfirms", ParamType::Uint(8)), - Param::new("lifetime", ParamType::Uint(32)), + AbiType::Array(Arc::new(AbiType::Uint(256))).named("owners"), + AbiType::Uint(8).named("reqConfirms"), + AbiType::Uint(32).named("lifetime"), ], outputs: Vec::new(), } } -pub fn send_transaction() -> &'static ton_abi::Function { +pub fn send_transaction() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "sendTransaction", inputs: vec![ - Param::new("dest", ParamType::Address), - Param::new("value", ParamType::Uint(128)), - Param::new("bounce", ParamType::Bool), - Param::new("flags", ParamType::Uint(8)), - Param::new("payload", ParamType::Cell), + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Uint(8).named("flags"), + AbiType::Cell.named("payload"), ], outputs: Vec::new(), } } -pub fn submit_transaction() -> &'static ton_abi::Function { +pub fn submit_transaction() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "submitTransaction", inputs: vec![ - Param::new("dest", ParamType::Address), - Param::new("value", ParamType::Uint(128)), - Param::new("bounce", ParamType::Bool), - Param::new("allBalance", ParamType::Bool), - Param::new("payload", ParamType::Cell), - Param::new("stateInit", ParamType::Optional(Box::new(ParamType::Cell))), + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Bool.named("allBalance"), + AbiType::Cell.named("payload"), + AbiType::Optional(Arc::new(AbiType::Cell)).named("stateInit"), + ], + outputs: vec![ + AbiType::Uint(64).named("transId") ], - outputs: vec![Param::new("transId", ParamType::Uint(64))], } } -pub fn confirm_transaction() -> &'static ton_abi::Function { +pub fn confirm_transaction() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "confirmTransaction", - inputs: vec![Param::new("transactionId", ParamType::Uint(64))], + inputs: vec![ + AbiType::Uint(64).named("transactionId"), + ], outputs: Vec::new(), } } -#[derive(Debug, UnpackAbi, KnownParamType)] +#[derive(Debug)] pub struct MultisigTransaction { - #[abi(uint64)] pub id: u64, - #[abi(uint32, name = "confirmationsMask")] pub confirmation_mask: u32, - #[abi(uint8, name = "signsRequired")] pub signs_required: u8, - #[abi(uint8, name = "signsReceived")] pub signs_received: u8, - #[abi(uint256)] - pub creator: ton_types::UInt256, - #[abi(uint8)] + pub creator: HashBytes, pub index: u8, - #[abi(address)] - pub dest: ton_block::MsgAddressInt, - #[abi(uint128)] + pub dest: StdAddr, pub value: u128, - #[abi(uint16, name = "sendFlags")] pub send_flags: u16, - #[abi(cell)] - pub payload: ton_types::Cell, - #[abi(bool)] + pub payload: Cell, pub bounce: bool, - #[abi] - pub state_init: Option, + pub state_init: Option, } -pub fn get_transactions() -> &'static ton_abi::Function { +pub fn get_transactions() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "getTransactions", inputs: Vec::new(), outputs: vec![ - Param::new("transactions", ParamType::Array(Box::new(MultisigTransaction::param_type()))) + AbiType::Array(Arc::new(MultisigTransaction::param_type())).named("transactions") ] } } -#[derive(Debug, Clone, Copy, UnpackAbi, KnownParamType)] +#[derive(Debug, Clone, Copy)] pub struct MultisigCustodian { - #[abi(uint8)] pub index: u8, - #[abi(uint256)] - pub pubkey: ton_types::UInt256, + pub pubkey: HashBytes, } -pub fn get_custodians() -> &'static ton_abi::Function { +pub fn get_custodians() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "getCustodians", inputs: Vec::new(), outputs: vec![ - Param::new("custodians", ParamType::Array(Box::new(MultisigCustodian::param_type()))) + AbiType::Array(Arc::new(MultisigCustodian::param_type())).named("custodians") ] } } -#[derive(Debug, Clone, UnpackAbiPlain, PackAbiPlain, KnownParamTypePlain)] +#[derive(Debug, Clone)] pub struct SubmitUpdateParams { - #[abi] - pub code_hash: Option, - #[abi] - pub owners: Option>, - #[abi] + pub code_hash: Option, + pub owners: Option>, pub req_confirms: Option, - #[abi] pub lifetime: Option, } -#[derive(Debug, Copy, Clone, UnpackAbiPlain, KnownParamTypePlain)] +#[derive(Debug, Copy, Clone)] pub struct SubmitUpdateOutput { - #[abi(uint64)] pub update_id: u64, } -pub fn submit_update() -> &'static ton_abi::Function { +pub fn submit_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], @@ -148,13 +138,12 @@ pub fn submit_update() -> &'static ton_abi::Function { } } -#[derive(Debug, Copy, Clone, UnpackAbiPlain, PackAbiPlain, KnownParamTypePlain)] +#[derive(Debug, Copy, Clone)] pub struct ConfirmUpdateParams { - #[abi(uint64)] pub update_id: u64, } -pub fn confirm_update() -> &'static ton_abi::Function { +pub fn confirm_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], @@ -164,15 +153,13 @@ pub fn confirm_update() -> &'static ton_abi::Function { } } -#[derive(Debug, Clone, UnpackAbiPlain, PackAbiPlain, KnownParamTypePlain)] +#[derive(Debug, Clone)] pub struct ExecuteUpdateParams { - #[abi(uint64)] pub update_id: u64, - #[abi] - pub code: Option, + pub code: Option, } -pub fn execute_update() -> &'static ton_abi::Function { +pub fn execute_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], @@ -182,23 +169,17 @@ pub fn execute_update() -> &'static ton_abi::Function { } } -#[derive(Debug, Clone, Copy, UnpackAbiPlain, KnownParamTypePlain)] +#[derive(Debug, Clone, Copy)] pub struct SetCodeMultisigParams { - #[abi(uint8, name = "maxQueuedTransactions")] pub max_queued_transactions: u8, - #[abi(uint8, name = "maxCustodianCount")] pub max_custodian_count: u8, - #[abi(uint64, name = "expirationTime")] pub expiration_time: u64, - #[abi(uint128, name = "minValue")] pub min_value: u128, - #[abi(uint8, name = "requiredTxnConfirms")] pub required_txn_confirms: u8, - #[abi(uint8, name = "requiredUpdConfirms")] pub required_upd_confirms: u8, } -pub fn get_parameters() -> &'static ton_abi::Function { +pub fn get_parameters() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], @@ -208,60 +189,54 @@ pub fn get_parameters() -> &'static ton_abi::Function { } } -#[derive(Debug, Clone, UnpackAbi, KnownParamType)] +#[derive(Debug, Clone)] pub struct UpdateTransaction { - #[abi(uint64)] pub id: u64, - #[abi(uint8)] pub index: u8, - #[abi(uint8)] pub signs: u8, - #[abi(uint32)] pub confirmations_mask: u32, - #[abi(uint256)] - pub creator: ton_types::UInt256, - #[abi] - pub new_code_hash: Option, - #[abi] - pub new_custodians: Option>, - #[abi] + pub creator: HashBytes, + pub new_code_hash: Option, + pub new_custodians: Option>, pub new_req_confirms: Option, - #[abi(with = "updated_lifetime")] + //#[abi(with = "updated_lifetime")] pub new_lifetime: Option, } -mod updated_lifetime { - use super::*; - use num_traits::cast::ToPrimitive; - - pub fn unpack(value: &ton_abi::TokenValue) -> UnpackerResult> { - let value = match value { - ton_abi::TokenValue::Optional(_, None) => return Ok(None), - ton_abi::TokenValue::Optional(_, Some(value)) => value, - _ => return Err(UnpackerError::InvalidAbi), - }; - - match value.as_ref() { - ton_abi::TokenValue::Uint(ton_abi::Uint { number, size: 32 }) => { - Ok(Some(number.to_u32().ok_or(UnpackerError::InvalidAbi)?)) - } - ton_abi::TokenValue::Uint(ton_abi::Uint { number, size: 64 }) => { - let lifetime = number.to_u64().ok_or(UnpackerError::InvalidAbi)?; - Ok(Some(lifetime as u32)) - } - _ => Err(UnpackerError::InvalidAbi), - } - } - - pub fn param_type() -> ParamType { - Option::::param_type() - } -} +//mod updated_lifetime { +// use super::*; +// use num_traits::cast::ToPrimitive; +// +// pub fn unpack(value: &TokenValue) -> UnpackerResult> { +// let value = match value { +// TokenValue::Optional(_, None) => return Ok(None), +// TokenValue::Optional(_, Some(value)) => value, +// _ => return Err(UnpackerError::InvalidAbi), +// }; +// +// match value.as_ref() { +// TokenValue::Uint(Uint { number, size: 32 }) => { +// Ok(Some(number.to_u32().ok_or(UnpackerError::InvalidAbi)?)) +// } +// TokenValue::Uint(Uint { number, size: 64 }) => { +// let lifetime = number.to_u64().ok_or(UnpackerError::InvalidAbi)?; +// Ok(Some(lifetime as u32)) +// } +// _ => Err(UnpackerError::InvalidAbi), +// } +// } +// +// pub fn param_type() -> ParamType { +// Option::::param_type() +// } +//} pub mod v2_0 { + use tycho_types::abi::NamedAbiType; + use super::*; - pub fn get_update_requests() -> &'static ton_abi::Function { + pub fn get_update_requests() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], @@ -269,18 +244,20 @@ pub mod v2_0 { inputs: Vec::new(), outputs: { let mut param_types = UpdateTransaction::param_type(); - if let ton_abi::ParamType::Tuple(params) = &mut param_types { - if let Some(ton_abi::Param { - kind: ton_abi::ParamType::Optional(param), + if let AbiType::Tuple(params) = &mut param_types { + if let Some(NamedAbiType { + ty: AbiType::Optional(param), .. }) = params.last_mut() { - if let ton_abi::ParamType::Uint(size) = param.as_mut() { + if let AbiType::Uint(size) = param.as_mut() { *size = 64; } } } - vec![Param::new("updates", ParamType::Array(Box::new(param_types)))] + vec![ + AbiType::Array(Arc::new(param_types)).named("updates") + ] }, } } @@ -289,14 +266,14 @@ pub mod v2_0 { pub mod v2_1 { use super::*; - pub fn get_update_requests() -> &'static ton_abi::Function { + pub fn get_update_requests() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "getUpdateRequests", inputs: Vec::new(), outputs: vec![ - Param::new("updates", ParamType::Array(Box::new(UpdateTransaction::param_type()))) + AbiType::Array(Arc::new(UpdateTransaction::param_type())).named("updates") ], } } From 4bd9131d5dfc62869dd1464dd039de448a24f395 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 26 Jan 2026 17:52:58 +0300 Subject: [PATCH 05/59] fix wallet v3 --- src/utils/ton_wallet/mod.rs | 17 +- src/utils/ton_wallet/multisig.rs | 36 ++-- src/utils/ton_wallet/wallet_v3.rs | 269 +++++++++++------------------- 3 files changed, 132 insertions(+), 190 deletions(-) diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs index f4d62a2..331baf3 100644 --- a/src/utils/ton_wallet/mod.rs +++ b/src/utils/ton_wallet/mod.rs @@ -9,6 +9,8 @@ use nekoton_utils::*; use tycho_types::cell::{Cell, HashBytes}; use tycho_types::models::{IntAddr, StateInit, StdAddr}; +use crate::utils::wallets; + pub use self::multisig::MultisigType; pub mod ever_wallet; @@ -109,7 +111,6 @@ impl WalletType { } pub fn code(&self) -> Cell { - use nekoton_contracts::wallets; match self { Self::Multisig(multisig_type) => multisig_type.code(), Self::WalletV3 => wallets::code::wallet_v3(), @@ -284,3 +285,17 @@ pub enum MessageFlagsError { #[error("Unknown message flags combination")] UnknownMessageFlags, } + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PendingTransaction { + /// External message hash + pub message_hash: HashBytes, + /// Incoming message source + pub src: Option, + /// Last known lt at the time the message was sent + pub latest_lt: u64, + /// Message broadcast timestamp (adjusted) + pub created_at: u32, + /// Expiration timestamp (adjusted) + pub expire_at: u32, +} diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index a14e5b8..03422fb 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -10,9 +10,6 @@ use tycho_types::{ models::{Account, StateInit, StdAddr}, }; -use nekoton_abi::*; -use nekoton_utils::*; - use crate::utils::ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}; use super::{Gift, TonWalletDetails}; @@ -102,7 +99,6 @@ pub fn prepare_confirm_transaction( } pub fn prepare_transfer( - clock: &dyn Clock, multisig_type: MultisigType, public_key: &PublicKey, has_multiple_owners: bool, @@ -234,19 +230,17 @@ pub fn prepare_execute_update( ) } -define_string_enum!( - #[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] - pub enum MultisigType { - SafeMultisigWallet, - SafeMultisigWallet24h, - SetcodeMultisigWallet, - SetcodeMultisigWallet24h, - BridgeMultisigWallet, - SurfWallet, - Multisig2, - Multisig2_1, - } -); +#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum MultisigType { + SafeMultisigWallet, + SafeMultisigWallet24h, + SetcodeMultisigWallet, + SetcodeMultisigWallet24h, + BridgeMultisigWallet, + SurfWallet, + Multisig2, + Multisig2_1, +} impl MultisigType { pub fn is_multisig2(self) -> bool { @@ -694,6 +688,14 @@ enum MultisigError { UnsupportedUpdate, } +pub type UnpackerResult = Result; + +#[derive(thiserror::Error, Debug, Clone, Copy)] +pub enum UnpackerError { + #[error("Invalid ABI")] + InvalidAbi, +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index 543b478..87bf145 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -2,53 +2,47 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::PublicKey; -use ton_block::{MsgAddrStd, MsgAddressInt, Serializable}; -use ton_types::{BuilderData, Cell, IBitstring, SliceData, UInt256}; - -use nekoton_utils::*; +use tycho_types::{ + abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, + cell::{Cell, CellBuilder, HashBytes}, + models::{Account, AccountState, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, +}; +use tycho_util::time::now_sec; -use super::{Gift, TonWalletDetails, TransferAction}; -use crate::core::models::{Expiration, ExpireAt, PendingTransaction}; -use crate::crypto::{SignedMessage, UnsignedMessage}; +use crate::utils::{ + ton_wallet::{Gift, PendingTransaction, TonWalletDetails}, + wallets::code::wallet_v3, +}; pub fn prepare_deploy( - clock: &dyn Clock, public_key: &PublicKey, workchain: i8, - expiration: Expiration, -) -> Result> { + expire_at: u32, +) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); - let dst = compute_contract_address(public_key, workchain); - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst, - ..Default::default() - }); - - message.set_state_init(init_data.make_state_init()?); - - let expire_at = ExpireAt::new(clock, expiration); - let (hash, payload) = init_data.make_transfer_payload(None, expire_at.timestamp)?; + let dst = compute_contract_address(public_key, workchain)?; - Ok(Box::new(UnsignedWalletV3Message { - init_data, - gifts: Vec::new(), + let (hash, payload) = init_data.make_transfer_payload(None, expire_at)?; + let unsigned_body = UnsignedBody { payload, - message, - expire_at, hash, - })) + abi_version: AbiVersion::V2_3, + expire_at, + }; + let mut unsigned_message = unsigned_body.with_dst(dst); + let state_init = init_data.make_state_init()?; + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) } -pub fn prepare_state_init(public_key: &PublicKey) -> Result { +pub fn prepare_state_init(public_key: &PublicKey) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); init_data.make_state_init() } /// Adjusts seqno if there are some recent pending transactions that have not expired pub fn estimate_seqno_offset( - clock: &dyn Clock, - current_state: &ton_block::AccountStuff, + current_state: &Account, pending_transactions: &[PendingTransaction], ) -> u32 { const SEQNO_ADJUST_INTERVAL: u32 = 30; // seconds @@ -66,8 +60,8 @@ pub fn estimate_seqno_offset( return 0; } - let now = clock.now_sec_u64() as u32; - let latest_lt = current_state.storage.last_trans_lt; + let now = now_sec(); + let latest_lt = current_state.last_trans_lt; let mut seqno_offset = 0; for pending in pending_transactions.iter().rev() { @@ -90,26 +84,23 @@ pub fn estimate_seqno_offset( } pub fn prepare_transfer( - clock: &dyn Clock, public_key: &PublicKey, - current_state: &ton_block::AccountStuff, + current_state: &Account, seqno_offset: u32, gifts: Vec, - expiration: Expiration, -) -> Result { + expire_at: u32, +) -> Result { if gifts.len() > MAX_MESSAGES { return Err(WalletV3Error::TooManyGifts.into()); } - let (mut init_data, with_state_init) = match ¤t_state.storage.state { - ton_block::AccountState::AccountActive { state_init, .. } => match &state_init.data { + let (init_data, with_state_init) = match ¤t_state.state { + AccountState::Active(state_init) => match &state_init.data { Some(data) => (InitData::try_from(data)?, false), None => return Err(WalletV3Error::InvalidInitData.into()), }, - ton_block::AccountState::AccountFrozen { .. } => { - return Err(WalletV3Error::AccountIsFrozen.into()) - } - ton_block::AccountState::AccountUninit => ( + AccountState::Frozen { .. } => return Err(WalletV3Error::AccountIsFrozen.into()), + AccountState::Uninit => ( InitData::from_key(public_key).with_wallet_id(WALLET_ID), true, ), @@ -117,91 +108,36 @@ pub fn prepare_transfer( init_data.seqno += seqno_offset; - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst: current_state.addr.clone(), - ..Default::default() - }); - - if with_state_init { - message.set_state_init(init_data.make_state_init()?); - } - - let expire_at = ExpireAt::new(clock, expiration); - let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at.timestamp)?; - - Ok(TransferAction::Sign(Box::new(UnsignedWalletV3Message { - init_data, - gifts, + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at)?; + let unsigned_body = UnsignedBody { payload, hash, + abi_version: AbiVersion::V2_3, expire_at, - message, - }))) + }; + let mut unsigned_message = unsigned_body.with_dst( + current_state + .address + .as_std() + .ok_or_else(|| WalletV3Error::InvalidAddress)? + .clone(), + ); + if with_state_init { + let state_init = init_data.make_state_init()?; + unsigned_message.set_state_init(Some(state_init)); + } + + Ok(unsigned_message) } #[derive(Clone)] struct UnsignedWalletV3Message { init_data: InitData, gifts: Vec, - payload: BuilderData, - hash: UInt256, - expire_at: ExpireAt, - message: ton_block::Message, -} - -impl UnsignedMessage for UnsignedWalletV3Message { - fn refresh_timeout(&mut self, clock: &dyn Clock) { - if !self.expire_at.refresh(clock) { - return; - } - - let (hash, payload) = self - .init_data - .make_transfer_payload(self.gifts.clone(), self.expire_at()) - .trust_me(); - self.hash = hash; - self.payload = payload; - } - - fn expire_at(&self) -> u32 { - self.expire_at.timestamp - } - - fn hash(&self) -> &[u8] { - self.hash.as_slice() - } - - fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { - let mut payload = self.payload.clone(); - payload.prepend_raw(signature, signature.len() * 8)?; - - let mut message = self.message.clone(); - message.set_body(SliceData::load_builder(payload)?); - - Ok(SignedMessage { - message, - expire_at: self.expire_at(), - }) - } - - fn sign_with_pruned_payload( - &self, - signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH], - prune_after_depth: u16, - ) -> Result { - let mut payload = self.payload.clone(); - payload.prepend_raw(signature, signature.len() * 8)?; - let body = payload.into_cell()?; - - let mut message = self.message.clone(); - message.set_body(prune_deep_cells(&body, prune_after_depth)?); - - Ok(SignedMessage { - message, - expire_at: self.expire_at(), - }) - } + payload: Cell, + hash: HashBytes, + expire_at: u32, + message: UnsignedExternalMessage, } pub static CODE_HASH: &[u8; 32] = &[ @@ -209,15 +145,16 @@ pub static CODE_HASH: &[u8; 32] = &[ 0xc0, 0xf7, 0x6d, 0xc4, 0x52, 0x40, 0x02, 0xa5, 0xd0, 0x91, 0x8b, 0x9a, 0x75, 0xd2, 0xd5, 0x99, ]; -pub fn is_wallet_v3(code_hash: &UInt256) -> bool { +pub fn is_wallet_v3(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> MsgAddressInt { - InitData::from_key(public_key) +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { + let hash = InitData::from_key(public_key) .with_wallet_id(WALLET_ID) - .compute_addr(workchain_id) - .trust_me() + .make_state_init() + .and_then(|state| state.hash())?; + Ok(StdAddr::new(workchain_id, hash.into())) } pub static DETAILS: TonWalletDetails = TonWalletDetails { @@ -239,12 +176,12 @@ const MAX_MESSAGES: usize = 4; pub struct InitData { pub seqno: u32, pub wallet_id: u32, - pub public_key: UInt256, + pub public_key: HashBytes, } impl InitData { pub fn public_key(&self) -> &[u8; 32] { - self.public_key.as_slice() + &self.public_key.0 } pub fn from_key(key: &PublicKey) -> Self { @@ -260,75 +197,63 @@ impl InitData { self } - pub fn compute_addr(&self, workchain_id: i8) -> Result { - let init_state = self.make_state_init()?.serialize()?; - let hash = init_state.repr_hash(); - Ok(MsgAddressInt::AddrStd(MsgAddrStd { - anycast: None, - workchain_id, - address: hash.into(), - })) + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let state_init = self.make_state_init()?; + let hash = CellBuilder::build_from(&state_init)?.repr_hash(); + Ok(StdAddr::new(workchain_id, hash.into())) } - pub fn make_state_init(&self) -> Result { - Ok(ton_block::StateInit { - code: Some(nekoton_contracts::wallets::code::wallet_v3()), + pub fn make_state_init(&self) -> Result { + Ok(StateInit { + code: Some(wallet_v3()), data: Some(self.serialize()?), ..Default::default() }) } pub fn serialize(&self) -> Result { - let mut data = BuilderData::new(); - data.append_u32(self.seqno)? - .append_u32(self.wallet_id)? - .append_raw(self.public_key.as_slice(), 256)?; - data.into_cell() + let mut builder = CellBuilder::new(); + builder.store_u32(self.seqno)?; + builder.store_u32(self.wallet_id)?; + builder.store_u256(self.public_key.as_bytes())?; + let data = builder.build()?; + Ok(data) } pub fn make_transfer_payload( &self, gifts: impl IntoIterator, expire_at: u32, - ) -> Result<(UInt256, BuilderData)> { - let mut payload = BuilderData::new(); - + ) -> Result<(HashBytes, Cell)> { // insert prefix - payload - .append_u32(self.wallet_id)? - .append_u32(expire_at)? - .append_u32(self.seqno)?; + let mut builder = CellBuilder::new(); + builder.store_u32(self.wallet_id)?; + builder.store_u32(expire_at)?; + builder.store_u32(self.seqno)?; // create internal message for gift in gifts { - let mut internal_message = - ton_block::Message::with_int_header(ton_block::InternalMessageHeader { + let internal_message = Message { + info: MsgInfo::Int(IntMsgInfo { ihr_disabled: true, bounce: gift.bounce, dst: gift.destination, - value: ton_block::CurrencyCollection::from_grams(ton_block::Grams::new( - gift.amount, - )?), + value: gift.amount.into(), ..Default::default() - }); - - if let Some(body) = gift.body { - internal_message.set_body(body); - } - - if let Some(state_init) = gift.state_init { - internal_message.set_state_init(state_init); - } - + }), + init: gift.state_init, + body: gift.body.unwrap_or(Default::default()).as_slice()?, + layout: None, + }; // append it to the body - payload - .append_u8(gift.flags)? - .checked_append_reference(internal_message.serialize()?)?; + builder.store_u8(self.flags)?; + builder.store_reference(CellBuilder::build_from(internal_message.borrow())?)?; } - let hash = payload.clone().into_cell()?.repr_hash(); + let payload = builder.build()?; + let hash = payload.repr_hash(); - Ok((hash, payload)) + Ok((*hash, payload)) } } @@ -336,11 +261,11 @@ impl TryFrom<&Cell> for InitData { type Error = anyhow::Error; fn try_from(data: &Cell) -> Result { - let mut cs = SliceData::load_cell_ref(data)?; + let mut slice = data.as_slice()?; Ok(Self { - seqno: cs.get_next_u32()?, - wallet_id: cs.get_next_u32()?, - public_key: UInt256::from_be_bytes(&cs.get_next_bytes(32)?), + seqno: slice.get_next_u32()?, + wallet_id: slice.get_next_u32()?, + public_key: HashBytes::from_be_bytes(&slice.get_next_bytes(32)?), }) } } From 0b19085106d38bfb5922f39b3095acc29f4be2d5 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 26 Jan 2026 17:52:58 +0300 Subject: [PATCH 06/59] fix wallet v3v4 --- src/utils/ton_wallet/wallet_v3.rs | 10 +- src/utils/ton_wallet/wallet_v3v4.rs | 300 +++++++++++----------------- 2 files changed, 119 insertions(+), 191 deletions(-) diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index 87bf145..fcf2ef1 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -94,7 +94,7 @@ pub fn prepare_transfer( return Err(WalletV3Error::TooManyGifts.into()); } - let (init_data, with_state_init) = match ¤t_state.state { + let (mut init_data, with_state_init) = match ¤t_state.state { AccountState::Active(state_init) => match &state_init.data { Some(data) => (InitData::try_from(data)?, false), None => return Err(WalletV3Error::InvalidInitData.into()), @@ -150,10 +150,10 @@ pub fn is_wallet_v3(code_hash: &HashBytes) -> bool { } pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { - let hash = InitData::from_key(public_key) + let state_init = InitData::from_key(public_key) .with_wallet_id(WALLET_ID) - .make_state_init() - .and_then(|state| state.hash())?; + .make_state_init()?; + let hash = CellBuilder::build_from(&state_init)?.repr_hash(); Ok(StdAddr::new(workchain_id, hash.into())) } @@ -280,4 +280,6 @@ enum WalletV3Error { AccountIsFrozen, #[error("Too many outgoing messages")] TooManyGifts, + #[error("Account address is not valid")] + InvalidAddress, } diff --git a/src/utils/ton_wallet/wallet_v3v4.rs b/src/utils/ton_wallet/wallet_v3v4.rs index bae849c..769900b 100644 --- a/src/utils/ton_wallet/wallet_v3v4.rs +++ b/src/utils/ton_wallet/wallet_v3v4.rs @@ -2,76 +2,63 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::PublicKey; -use ton_block::{MsgAddrStd, MsgAddressInt, Serializable}; -use ton_types::{BuilderData, Cell, IBitstring, SliceData, UInt256}; - -use nekoton_utils::*; +use tycho_types::{ + abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, + cell::{Cell, CellBuilder, HashBytes}, + models::{Account, AccountState, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, +}; -use super::{Gift, TonWalletDetails, TransferAction}; -use crate::core::models::{Expiration, ExpireAt}; -use crate::crypto::{SignedMessage, UnsignedMessage}; +use crate::utils::{ + ton_wallet::{Gift, TonWalletDetails}, + wallets::{self}, +}; pub fn prepare_deploy( - clock: &dyn Clock, public_key: &PublicKey, workchain: i8, - expiration: Expiration, + expire_at: u32, version: WalletVersion, -) -> Result> { +) -> Result { let init_data = InitData::from_key(public_key).with_subwallet_id(WALLET_ID); - let dst = compute_contract_address(public_key, workchain, version); - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst, - ..Default::default() - }); - - message.set_state_init(init_data.make_state_init(version)?); - - let expire_at = ExpireAt::new(clock, expiration); - let (hash, payload) = init_data.make_transfer_payload(None, expire_at.timestamp, version)?; + let dst = compute_contract_address(public_key, workchain, version)?; - Ok(Box::new(UnsignedWallet { - init_data, - gifts: Vec::new(), + let (hash, payload) = init_data.make_transfer_payload(None, expire_at, version)?; + let unsigned_body = UnsignedBody { payload, - message, - expire_at, hash, - version, - })) + abi_version: AbiVersion::V2_3, + expire_at, + }; + let mut unsigned_message = unsigned_body.with_dst(dst); + let state_init = init_data.make_state_init(version)?; + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) } -pub fn prepare_state_init( - public_key: &PublicKey, - version: WalletVersion, -) -> Result { +pub fn prepare_state_init(public_key: &PublicKey, version: WalletVersion) -> Result { let init_data = InitData::from_key(public_key).with_subwallet_id(WALLET_ID); init_data.make_state_init(version) } pub fn prepare_transfer( - clock: &dyn Clock, public_key: &PublicKey, - current_state: &ton_block::AccountStuff, + current_state: &Account, seqno_offset: u32, gifts: Vec, - expiration: Expiration, + expire_at: u32, version: WalletVersion, -) -> Result { +) -> Result { if gifts.len() > MAX_MESSAGES { return Err(WalletV4Error::TooManyGifts.into()); } - let (mut init_data, with_state_init) = match ¤t_state.storage.state { - ton_block::AccountState::AccountActive { state_init, .. } => match &state_init.data { + let (mut init_data, with_state_init) = match ¤t_state.state { + AccountState::Active(state_init) => match &state_init.data { Some(data) => (InitData::try_from(data)?, false), None => return Err(WalletV4Error::InvalidInitData.into()), }, - ton_block::AccountState::AccountFrozen { .. } => { - return Err(WalletV4Error::AccountIsFrozen.into()) - } - ton_block::AccountState::AccountUninit => ( + AccountState::Frozen { .. } => return Err(WalletV4Error::AccountIsFrozen.into()), + AccountState::Uninit => ( InitData::from_key(public_key).with_subwallet_id(WALLET_ID), true, ), @@ -79,96 +66,40 @@ pub fn prepare_transfer( init_data.seqno += seqno_offset; - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst: current_state.addr.clone(), - ..Default::default() - }); - - if with_state_init { - message.set_state_init(init_data.make_state_init(version)?); - } - - let expire_at = ExpireAt::new(clock, expiration); - let (hash, payload) = - init_data.make_transfer_payload(gifts.clone(), expire_at.timestamp, version)?; + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at, version)?; - Ok(TransferAction::Sign(Box::new(UnsignedWallet { - init_data, - gifts, + let unsigned_body = UnsignedBody { payload, hash, + abi_version: AbiVersion::V2_3, expire_at, - message, - version, - }))) + }; + let mut unsigned_message = unsigned_body.with_dst( + current_state + .address + .as_std() + .ok_or_else(|| WalletV4Error::InvalidAddress)? + .clone(), + ); + if with_state_init { + let state_init = init_data.make_state_init(version)?; + unsigned_message.set_state_init(Some(state_init)); + } + + Ok(unsigned_message) } #[derive(Clone)] struct UnsignedWallet { init_data: InitData, gifts: Vec, - payload: BuilderData, - hash: UInt256, - expire_at: ExpireAt, - message: ton_block::Message, + payload: Cell, + hash: HashBytes, + expire_at: u32, + message: UnsignedExternalMessage, version: WalletVersion, } -impl UnsignedMessage for UnsignedWallet { - fn refresh_timeout(&mut self, clock: &dyn Clock) { - if !self.expire_at.refresh(clock) { - return; - } - - let (hash, payload) = self - .init_data - .make_transfer_payload(self.gifts.clone(), self.expire_at(), self.version) - .trust_me(); - self.hash = hash; - self.payload = payload; - } - - fn expire_at(&self) -> u32 { - self.expire_at.timestamp - } - - fn hash(&self) -> &[u8] { - self.hash.as_slice() - } - - fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { - let mut payload = self.payload.clone(); - payload.prepend_raw(signature, signature.len() * 8)?; - - let mut message = self.message.clone(); - message.set_body(SliceData::load_builder(payload)?); - - Ok(SignedMessage { - message, - expire_at: self.expire_at(), - }) - } - - fn sign_with_pruned_payload( - &self, - signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH], - prune_after_depth: u16, - ) -> Result { - let mut payload = self.payload.clone(); - payload.append_raw(signature, signature.len() * 8)?; - let body = payload.into_cell()?; - - let mut message = self.message.clone(); - message.set_body(prune_deep_cells(&body, prune_after_depth)?); - - Ok(SignedMessage { - message, - expire_at: self.expire_at(), - }) - } -} - pub static CODE_HASH_V3_R1: &[u8; 32] = &[ 0xB6, 0x10, 0x41, 0xA5, 0x8A, 0x79, 0x80, 0xB9, 0x46, 0xE8, 0xFB, 0x9E, 0x19, 0x8E, 0x3C, 0x90, 0x4D, 0x24, 0x79, 0x9F, 0xFA, 0x36, 0x57, 0x4E, 0xA4, 0x25, 0x1C, 0x41, 0xA5, 0x66, 0xF5, 0x81, @@ -189,19 +120,19 @@ pub static CODE_HASH_V4_R2: &[u8; 32] = &[ 0x84, 0x67, 0x89, 0xFB, 0x4A, 0xE5, 0x80, 0xC8, 0x78, 0x86, 0x6D, 0x95, 0x9D, 0xAB, 0xD5, 0xC0, ]; -pub fn is_wallet_v3r1(code_hash: &UInt256) -> bool { +pub fn is_wallet_v3r1(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH_V3_R1 } -pub fn is_wallet_v3r2(code_hash: &UInt256) -> bool { +pub fn is_wallet_v3r2(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH_V3_R2 } -pub fn is_wallet_v4r1(code_hash: &UInt256) -> bool { +pub fn is_wallet_v4r1(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH_V4_R1 } -pub fn is_wallet_v4r2(code_hash: &UInt256) -> bool { +pub fn is_wallet_v4r2(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH_V4_R2 } @@ -209,7 +140,7 @@ pub fn compute_contract_address( public_key: &PublicKey, workchain_id: i8, version: WalletVersion, -) -> MsgAddressInt { +) -> StdAddr { InitData::from_key(public_key) .with_subwallet_id(WALLET_ID) .compute_addr(workchain_id, version) @@ -235,12 +166,12 @@ const MAX_MESSAGES: usize = 4; pub struct InitData { pub seqno: u32, pub wallet_id: i32, - pub public_key: UInt256, + pub public_key: HashBytes, } impl InitData { pub fn public_key(&self) -> &[u8; 32] { - self.public_key.as_slice() + &self.public_key.0 } pub fn from_key(key: &PublicKey) -> Self { @@ -256,25 +187,21 @@ impl InitData { self } - pub fn compute_addr(&self, workchain_id: i8, version: WalletVersion) -> Result { - let init_state = self.make_state_init(version)?.serialize()?; - let hash = init_state.repr_hash(); - Ok(MsgAddressInt::AddrStd(MsgAddrStd { - anycast: None, - workchain_id, - address: hash.into(), - })) + pub fn compute_addr(&self, workchain_id: i8, version: WalletVersion) -> Result { + let state_init = self.make_state_init(version)?; + let hash = CellBuilder::build_from(&state_init)?.repr_hash(); + Ok(StdAddr::new(workchain_id, hash.into())) } - pub fn make_state_init(&self, version: WalletVersion) -> Result { + pub fn make_state_init(&self, version: WalletVersion) -> Result { let code = match version { - WalletVersion::V3R1 => nekoton_contracts::wallets::code::wallet_v3r1(), - WalletVersion::V3R2 => nekoton_contracts::wallets::code::wallet_v3r2(), - WalletVersion::V4R1 => nekoton_contracts::wallets::code::wallet_v4r1(), - WalletVersion::V4R2 => nekoton_contracts::wallets::code::wallet_v4r2(), + WalletVersion::V3R1 => wallets::code::wallet_v3r1(), + WalletVersion::V3R2 => wallets::code::wallet_v3r2(), + WalletVersion::V4R1 => wallets::code::wallet_v4r1(), + WalletVersion::V4R2 => wallets::code::wallet_v4r2(), }; - Ok(ton_block::StateInit { + Ok(StateInit { code: Some(code), data: Some(self.serialize(version)?), ..Default::default() @@ -282,17 +209,18 @@ impl InitData { } pub fn serialize(&self, version: WalletVersion) -> Result { - let mut data = BuilderData::new(); - data.append_u32(self.seqno)? - .append_i32(self.wallet_id)? - .append_raw(self.public_key.as_slice(), 256)?; + let mut builder = CellBuilder::new(); + builder.store_u32(self.seqno)?; + builder.store_u32(self.wallet_id as _)?; + builder.store_u256(self.public_key.as_bytes())?; if matches!(version, WalletVersion::V4R1 | WalletVersion::V4R2) { // empty plugin dict - data.append_bit_zero()?; + builder.store_bit_zero()?; } - data.into_cell() + let data = builder.build()?; + Ok(data) } pub fn make_transfer_payload( @@ -300,49 +228,41 @@ impl InitData { gifts: impl IntoIterator, expire_at: u32, version: WalletVersion, - ) -> Result<(UInt256, BuilderData)> { - let mut payload = BuilderData::new(); - + ) -> Result<(HashBytes, Cell)> { // insert prefix - payload - .append_i32(self.wallet_id)? - .append_u32(expire_at)? - .append_u32(self.seqno)?; + let mut builder = CellBuilder::new(); + builder.store_u32(self.wallet_id as _)?; + builder.store_u32(expire_at)?; + builder.store_u32(self.seqno)?; // Opcode if matches!(version, WalletVersion::V4R1 | WalletVersion::V4R2) { - payload.append_u8(0)?; + builder.store_u8(0)?; } + // create internal message for gift in gifts { - let mut internal_message = - ton_block::Message::with_int_header(ton_block::InternalMessageHeader { + let internal_message = Message { + info: MsgInfo::Int(IntMsgInfo { ihr_disabled: true, bounce: gift.bounce, dst: gift.destination, - value: ton_block::CurrencyCollection::from_grams(ton_block::Grams::new( - gift.amount, - )?), + value: gift.amount.into(), ..Default::default() - }); - - if let Some(body) = gift.body { - internal_message.set_body(body); - } - - if let Some(state_init) = gift.state_init { - internal_message.set_state_init(state_init); - } - + }), + init: gift.state_init, + body: gift.body.unwrap_or(Default::default()).as_slice()?, + layout: None, + }; // append it to the body - payload - .append_u8(gift.flags)? - .checked_append_reference(internal_message.serialize()?)?; + builder.store_u8(self.flags)?; + builder.store_reference(CellBuilder::build_from(internal_message.borrow())?)?; } - let hash = payload.clone().into_cell()?.repr_hash(); + let payload = builder.build()?; + let hash = payload.repr_hash(); - Ok((hash, payload)) + Ok((*hash, payload)) } } @@ -350,11 +270,11 @@ impl TryFrom<&Cell> for InitData { type Error = anyhow::Error; fn try_from(data: &Cell) -> Result { - let mut cs = SliceData::load_cell_ref(data)?; + let mut slice = data.as_slice()?; Ok(Self { - seqno: cs.get_next_u32()?, - wallet_id: cs.get_next_i32()?, - public_key: UInt256::from_be_bytes(&cs.get_next_bytes(32)?), + seqno: slice.get_next_u32()?, + wallet_id: slice.get_next_u32()?, + public_key: HashBytes::from_be_bytes(&slice.get_next_bytes(32)?), }) } } @@ -377,19 +297,24 @@ enum WalletV4Error { AccountIsFrozen, #[error("Too many outgoing messages")] TooManyGifts, + #[error("Account address is not valid")] + InvalidAddress, } #[cfg(test)] mod tests { - use std::str::FromStr; - - use ton_block::Deserializable; - use ton_types::UInt256; - use nekoton_contracts::wallets; + use tycho_types::{ + boc::Boc, + cell::{HashBytes, Load}, + models::StateInit, + }; - use crate::core::ton_wallet::wallet_v3v4::{ - is_wallet_v4r1, is_wallet_v4r2, InitData, WalletVersion, WALLET_ID, + use crate::utils::{ + ton_wallet::wallet_v3v4::{ + is_wallet_v4r1, is_wallet_v4r2, InitData, WalletVersion, WALLET_ID, + }, + wallets, }; #[test] @@ -415,13 +340,14 @@ mod tests { #[test] fn state_init_v4r2() -> anyhow::Result<()> { let state_init_base64 = "te6ccgECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF2dW1vNw/It5bDWN3jVo5dxzZVk+Q11lVLs3LamPSWAVQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8SExQVAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNCAkCASAKCwB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAMDQBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDg8AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIBARABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVA=="; + let state_init = Boc::decode_base64(state_init_base64)?; - let state_init = ton_block::StateInit::construct_from_base64(state_init_base64)?; + let state_init = StateInit::load_from(&mut state_init.as_slice()?)?; let init_data_clone = InitData { seqno: 0, wallet_id: WALLET_ID, - public_key: UInt256::from_str( + public_key: HashBytes::from_str( "6756d6f370fc8b796c358dde3568e5dc7365593e435d6554bb372da98f496015", )?, }; From 579703656977991603305898b8fb68c91ecd3d5f Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Tue, 27 Jan 2026 11:23:22 +0300 Subject: [PATCH 07/59] fix wallet v5 --- src/utils/ton_wallet/highload_wallet_v2.rs | 19 +- src/utils/ton_wallet/wallet_v3.rs | 14 +- src/utils/ton_wallet/wallet_v3v4.rs | 13 +- src/utils/ton_wallet/wallet_v5r1.rs | 397 +++++++++------------ 4 files changed, 201 insertions(+), 242 deletions(-) diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 0e3bfaf..6e3f03c 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -244,16 +244,19 @@ impl TryFrom<&Cell> for InitData { fn try_from(data: &Cell) -> Result { let mut slice = data.as_slice()?; + let wallet_id = slice.load_u32()?; + let last_cleaned = slice.load_u64()?; + let mut buffer = [0u8; 32]; + slice.load_raw(&mut buffer, 32)?; + let public_key = HashBytes::from_slice(&buffer); + let mut data = Dict::::new(); + data.load_from(&mut slice)?; Ok(Self { - wallet_id: slice.get_next_u32()?, - last_cleaned: slice.get_next_u64()?, - public_key: HashBytes::from_be_bytes(&slice.get_next_bytes(32)?), - data: { - let mut map = Dict::::new(); - map.load_from(&mut slice)?; - map - }, + wallet_id, + last_cleaned, + public_key, + data, }) } } diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index fcf2ef1..3a37ab2 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -262,10 +262,18 @@ impl TryFrom<&Cell> for InitData { fn try_from(data: &Cell) -> Result { let mut slice = data.as_slice()?; + let is_signature_allowed = slice.load_bit()?; + let seqno = slice.load_u32()?; + let wallet_id = slice.load_u32()?; + let mut buffer = [0u8; 32]; + slice.load_raw(&mut buffer, 32)?; + let public_key = HashBytes::from_slice(&buffer); + let extensions = Option::::load_from(&mut slice)?; + Ok(Self { - seqno: slice.get_next_u32()?, - wallet_id: slice.get_next_u32()?, - public_key: HashBytes::from_be_bytes(&slice.get_next_bytes(32)?), + seqno, + wallet_id, + public_key, }) } } diff --git a/src/utils/ton_wallet/wallet_v3v4.rs b/src/utils/ton_wallet/wallet_v3v4.rs index 769900b..ed8318e 100644 --- a/src/utils/ton_wallet/wallet_v3v4.rs +++ b/src/utils/ton_wallet/wallet_v3v4.rs @@ -271,10 +271,17 @@ impl TryFrom<&Cell> for InitData { fn try_from(data: &Cell) -> Result { let mut slice = data.as_slice()?; + + let seqno = slice.load_u32()?; + let wallet_id = slice.load_u32()?.into(); + let mut buffer = [0u8; 32]; + slice.load_raw(&mut buffer, 32)?; + let public_key = HashBytes::from_slice(&buffer); + Ok(Self { - seqno: slice.get_next_u32()?, - wallet_id: slice.get_next_u32()?, - public_key: HashBytes::from_be_bytes(&slice.get_next_bytes(32)?), + seqno, + wallet_id, + public_key, }) } } diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index 0ea1444..eac7997 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -2,48 +2,44 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::PublicKey; -use ton_block::{MsgAddrStd, MsgAddressInt, Serializable}; -use ton_types::{BuilderData, Cell, IBitstring, SliceData, UInt256}; - -use nekoton_utils::*; +use tycho_types::{ + abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, + cell::{Cell, CellBuilder, HashBytes, Lazy}, + models::{ + Account, AccountState, OutAction, OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, + StateInit, StdAddr, + }, +}; -use super::{Gift, TonWalletDetails, TransferAction}; -use crate::core::models::{Expiration, ExpireAt}; -use crate::crypto::{SignedMessage, UnsignedMessage}; +use crate::utils::{ + ton_wallet::{Gift, TonWalletDetails}, + wallets::{self}, +}; const SIGNED_EXTERNAL_PREFIX: u32 = 0x7369676E; const SIGNED_INTERNAL_PREFIX: u32 = 0x73696E74; pub fn prepare_deploy( - clock: &dyn Clock, public_key: &PublicKey, workchain: i8, - expiration: Expiration, -) -> Result> { + expire_at: u32, +) -> Result { let init_data = make_init_data(public_key); let dst = compute_contract_address(public_key, workchain); - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst, - ..Default::default() - }); - - message.set_state_init(init_data.make_state_init()?); - - let expire_at = ExpireAt::new(clock, expiration); - let (hash, payload) = init_data.make_transfer_payload(None, expire_at.timestamp, false)?; - - Ok(Box::new(UnsignedWalletV5 { - init_data, - gifts: Vec::new(), + let (hash, payload) = init_data.make_transfer_payload(None, expire_at, false)?; + let unsigned_body = UnsignedBody { payload, - message, - expire_at, hash, - })) + abi_version: AbiVersion::V2_3, + expire_at, + }; + let mut unsigned_message = unsigned_body.with_dst(dst); + let state_init = init_data.make_state_init()?; + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) } -pub fn prepare_state_init(public_key: &PublicKey) -> Result { +pub fn prepare_state_init(public_key: &PublicKey) -> Result { let init_data = make_init_data(public_key); init_data.make_state_init() } @@ -54,21 +50,18 @@ pub fn make_init_data(public_key: &PublicKey) -> InitData { .with_is_signature_allowed(true) } -pub fn get_init_data( - current_state: &ton_block::AccountState, - public_key: &PublicKey, -) -> Result<(InitData, bool)> { - match current_state { - ton_block::AccountState::AccountActive { state_init, .. } => match &state_init.data { +pub fn get_init_data(current_state: &Account, public_key: &PublicKey) -> Result<(InitData, bool)> { + match current_state.state { + AccountState::Active(state_init) => match &state_init.data { Some(data) => Ok((InitData::try_from(data)?, false)), - None => Err(WalletV5Error::InvalidInitData.into()), + None => return Err(WalletV5Error::InvalidInitData.into()), }, - ton_block::AccountState::AccountFrozen { .. } => Err(WalletV5Error::AccountIsFrozen.into()), - ton_block::AccountState::AccountUninit => Ok((make_init_data(public_key), true)), + AccountState::Frozen { .. } => return Err(WalletV5Error::AccountIsFrozen.into()), + AccountState::Uninit => Ok((make_init_data(public_key), true)), } } -pub fn get_init_data_from_state_init(init: &ton_block::StateInit) -> Result { +pub fn get_init_data_from_state_init(init: &StateInit) -> Result { match &init.data { Some(data) => Ok(InitData::try_from(data)?), None => Err(WalletV5Error::InvalidInitData.into()), @@ -76,13 +69,12 @@ pub fn get_init_data_from_state_init(init: &ton_block::StateInit) -> Result, - expiration: Expiration, -) -> Result { + expire_at: u32, +) -> Result { if gifts.len() > MAX_MESSAGES { return Err(WalletV5Error::TooManyGifts.into()); } @@ -91,92 +83,37 @@ pub fn prepare_transfer( init_data.seqno += seqno_offset; - let mut message = - ton_block::Message::with_ext_in_header(ton_block::ExternalInboundMessageHeader { - dst: current_state.addr.clone(), - ..Default::default() - }); - - if with_state_init { - message.set_state_init(init_data.make_state_init()?); - } - - let expire_at = ExpireAt::new(clock, expiration); - let (hash, payload) = - init_data.make_transfer_payload(gifts.clone(), expire_at.timestamp, false)?; + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at, false)?; - Ok(TransferAction::Sign(Box::new(UnsignedWalletV5 { - init_data, - gifts, + let unsigned_body = UnsignedBody { payload, hash, + abi_version: AbiVersion::V2_3, expire_at, - message, - }))) + }; + let mut unsigned_message = unsigned_body.with_dst( + current_state + .address + .as_std() + .ok_or_else(|| WalletV5Error::InvalidAddress)? + .clone(), + ); + if with_state_init { + let state_init = init_data.make_state_init()?; + unsigned_message.set_state_init(Some(state_init)); + } + + Ok(unsigned_message) } #[derive(Clone)] struct UnsignedWalletV5 { init_data: InitData, gifts: Vec, - payload: BuilderData, - hash: UInt256, - expire_at: ExpireAt, - message: ton_block::Message, -} - -impl UnsignedMessage for UnsignedWalletV5 { - fn refresh_timeout(&mut self, clock: &dyn Clock) { - if !self.expire_at.refresh(clock) { - return; - } - - let (hash, payload) = self - .init_data - .make_transfer_payload(self.gifts.clone(), self.expire_at(), false) - .trust_me(); - self.hash = hash; - self.payload = payload; - } - - fn expire_at(&self) -> u32 { - self.expire_at.timestamp - } - - fn hash(&self) -> &[u8] { - self.hash.as_slice() - } - - fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { - let mut payload = self.payload.clone(); - payload.append_raw(signature, signature.len() * 8)?; - - let mut message = self.message.clone(); - message.set_body(SliceData::load_builder(payload)?); - - Ok(SignedMessage { - message, - expire_at: self.expire_at(), - }) - } - - fn sign_with_pruned_payload( - &self, - signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH], - prune_after_depth: u16, - ) -> Result { - let mut payload = self.payload.clone(); - payload.append_raw(signature, signature.len() * 8)?; - let body = payload.into_cell()?; - - let mut message = self.message.clone(); - message.set_body(prune_deep_cells(&body, prune_after_depth)?); - - Ok(SignedMessage { - message, - expire_at: self.expire_at(), - }) - } + payload: Cell, + hash: HashBytes, + expire_at: u32, + message: UnsignedExternalMessage, } pub static CODE_HASH: &[u8; 32] = &[ @@ -184,11 +121,11 @@ pub static CODE_HASH: &[u8; 32] = &[ 0xd1, 0xa3, 0x0f, 0x04, 0xf7, 0x37, 0xd4, 0xf6, 0x2a, 0x66, 0x8e, 0x95, 0x52, 0xd2, 0xb7, 0x2f, ]; -pub fn is_wallet_v5r1(code_hash: &UInt256) -> bool { +pub fn is_wallet_v5r1(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> MsgAddressInt { +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> StdAddr { make_init_data(public_key) .compute_addr(workchain_id) .trust_me() @@ -214,13 +151,13 @@ pub struct InitData { pub is_signature_allowed: bool, pub seqno: u32, pub wallet_id: u32, - pub public_key: UInt256, + pub public_key: HashBytes, pub extensions: Option, } impl InitData { pub fn public_key(&self) -> &[u8; 32] { - self.public_key.as_slice() + &self.public_key.0 } pub fn from_key(key: &PublicKey) -> Self { @@ -243,39 +180,36 @@ impl InitData { self } - pub fn compute_addr(&self, workchain_id: i8) -> Result { - let init_state = self.make_state_init()?.serialize()?; - let hash = init_state.repr_hash(); - Ok(MsgAddressInt::AddrStd(MsgAddrStd { - anycast: None, - workchain_id, - address: hash.into(), - })) + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let state_init = self.make_state_init()?; + let hash = CellBuilder::build_from(&state_init)?.repr_hash(); + Ok(StdAddr::new(workchain_id, hash.into())) } - pub fn make_state_init(&self) -> Result { - Ok(ton_block::StateInit { - code: Some(nekoton_contracts::wallets::code::wallet_v5r1()), + pub fn make_state_init(&self) -> Result { + Ok(StateInit { + code: Some(wallets::code::wallet_v5r1()), data: Some(self.serialize()?), ..Default::default() }) } pub fn serialize(&self) -> Result { - let mut data = BuilderData::new(); - data.append_bit_bool(self.is_signature_allowed)? - .append_u32(self.seqno)? - .append_u32(self.wallet_id)? - .append_raw(self.public_key.as_slice(), 256)?; + let mut builder = CellBuilder::new(); + builder.store_bit(self.is_signature_allowed)?; + builder.store_u32(self.seqno)?; + builder.store_u32(self.wallet_id)?; + builder.store_u256(self.public_key.as_bytes())?; if let Some(extensions) = &self.extensions { - data.append_bit_one()? - .checked_append_reference(extensions.clone())?; + builder.store_bit_one()?; + builder.store_reference(extensions.clone())?; } else { - data.append_bit_zero()?; + builder.store_bit_one()?; } - data.into_cell() + let data = builder.build()?; + Ok(data) } pub fn make_transfer_payload( @@ -283,7 +217,7 @@ impl InitData { gifts: impl IntoIterator, expire_at: u32, is_internal_flow: bool, - ) -> Result<(UInt256, BuilderData)> { + ) -> Result<(HashBytes, Cell)> { // Check if signatures are allowed if !self.is_signature_allowed { return if self.extensions.is_none() { @@ -293,59 +227,53 @@ impl InitData { }; } - let mut payload = BuilderData::new(); + let mut builder = CellBuilder::new(); // insert prefix if is_internal_flow { - payload.append_u32(SIGNED_INTERNAL_PREFIX)?; + builder.store_u32(SIGNED_INTERNAL_PREFIX)?; } else { - payload.append_u32(SIGNED_EXTERNAL_PREFIX)?; + builder.store_u32(SIGNED_EXTERNAL_PREFIX)?; }; - payload - .append_u32(self.wallet_id)? - .append_u32(expire_at)? - .append_u32(self.seqno)?; + builder.store_u32(self.wallet_id)?; + builder.store_u32(expire_at)?; + builder.store_u32(self.seqno)?; - let mut actions = ton_block::OutActions::new(); + let mut actions_builder = CellBuilder::new(); for gift in gifts { - let mut internal_message = - ton_block::Message::with_int_header(ton_block::InternalMessageHeader { + let internal_message = Lazy::new(&OwnedRelaxedMessage { + info: RelaxedMsgInfo::Int(RelaxedIntMsgInfo { ihr_disabled: true, bounce: gift.bounce, dst: gift.destination, - value: ton_block::CurrencyCollection::from_grams(ton_block::Grams::new( - gift.amount, - )?), + value: gift.amount.into(), ..Default::default() - }); - - if let Some(body) = gift.body { - internal_message.set_body(body); - } - - if let Some(state_init) = gift.state_init { - internal_message.set_state_init(state_init); - } - - let action = ton_block::OutAction::SendMsg { - mode: gift.flags, + }), + init: gift.state_init, + body: gift.body.unwrap_or(Default::default()).into(), + layout: None, + })?; + + let action = OutAction::SendMsg { + mode: gift.flags.into(), out_msg: internal_message, }; - actions.push_back(action); + actions_builder.store_reference(CellBuilder::build_from(action)?)?; } - payload.append_bit_one()?; - payload.checked_append_reference(actions.serialize()?)?; + builder.store_bit_one()?; + builder.store_reference(actions_builder.build()?)?; // has_other_actions - payload.append_bit_zero()?; + builder.store_bit_zero()?; - let hash = payload.clone().into_cell()?.repr_hash(); + let payload = builder.build()?; + let hash = payload.repr_hash(); - Ok((hash, payload)) + Ok((*hash, payload)) } } @@ -353,13 +281,21 @@ impl TryFrom<&Cell> for InitData { type Error = anyhow::Error; fn try_from(data: &Cell) -> Result { - let mut cs = SliceData::load_cell_ref(data)?; + let mut slice = data.as_slice()?; + let is_signature_allowed = slice.load_bit()?; + let seqno = slice.load_u32()?; + let wallet_id = slice.load_u32()?; + let mut buffer = [0u8; 32]; + slice.load_raw(&mut buffer, 32)?; + let public_key = HashBytes::from_slice(&buffer); + let extensions = Option::::load_from(&mut slice)?; + Ok(Self { - is_signature_allowed: cs.get_next_bit()?, - seqno: cs.get_next_u32()?, - wallet_id: cs.get_next_u32()?, - public_key: UInt256::from_be_bytes(&cs.get_next_bytes(32)?), - extensions: cs.get_next_dictionary()?, + is_signature_allowed, + seqno, + wallet_id, + public_key, + extensions, }) } } @@ -382,21 +318,26 @@ enum WalletV5Error { #[cfg(test)] mod tests { - use crate::core::ton_wallet::wallet_v5r1::{ - compute_contract_address, is_wallet_v5r1, InitData, WALLET_ID, + use ed25519_dalek::PublicKey; + use tycho_types::{ + boc::Boc, + cell::Load, + models::{Account, AccountState}, + }; + + use crate::utils::{ + ton_wallet::wallet_v5r1::{compute_contract_address, is_wallet_v5r1, InitData, WALLET_ID}, + wallets, }; - use crate::crypto::extend_with_signature_id; - use ed25519_dalek::{PublicKey, Signature, Verifier}; - use nekoton_contracts::wallets; - use ton_block::AccountState; - use ton_types::SliceData; #[test] fn state_init() -> anyhow::Result<()> { - let cell = ton_types::deserialize_tree_of_cells(&mut base64::decode("te6ccgECFgEAAucAAm6ADZRqTnEksRaYvpXRMbgzB92SzFv/19WbfQQgdDo7lYwEWQnKBnPzD1AAAXPmjwdAEj9i9OgmAgEAUYAAAAG///+IyIPTKTihvw1MFdzCAl7NQWIaeY9xhjENsss4FdrN+FAgART/APSkE/S88sgLAwIBIAYEAQLyBQEeINcLH4IQc2lnbrry4Ip/EQIBSBAHAgEgCQgAGb5fD2omhAgKDrkPoCwCASANCgIBSAwLABGyYvtRNDXCgCAAF7Ml+1E0HHXIdcLH4AIBbg8OABmvHfaiaEAQ65DrhY/AABmtznaiaEAg65Drhf/AAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hIRAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEgP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKFRQTABCTW9sx4ddM0AByMNcsCCSOLSHy4JLSAO1E0NIAURO68tCPVFAwkTGcAYEBQNch1woA8uCO4sjKAFjPFsntVJPywI3iAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQ=").unwrap().as_slice()).unwrap(); - let state = nekoton_utils::deserialize_account_stuff(cell)?; + let account_base64 = "te6ccgECFgEAAucAAm6ADZRqTnEksRaYvpXRMbgzB92SzFv/19WbfQQgdDo7lYwEWQnKBnPzD1AAAXPmjwdAEj9i9OgmAgEAUYAAAAG///+IyIPTKTihvw1MFdzCAl7NQWIaeY9xhjENsss4FdrN+FAgART/APSkE/S88sgLAwIBIAYEAQLyBQEeINcLH4IQc2lnbrry4Ip/EQIBSBAHAgEgCQgAGb5fD2omhAgKDrkPoCwCASANCgIBSAwLABGyYvtRNDXCgCAAF7Ml+1E0HHXIdcLH4AIBbg8OABmvHfaiaEAQ65DrhY/AABmtznaiaEAg65Drhf/AAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hIRAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEgP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKFRQTABCTW9sx4ddM0AByMNcsCCSOLSHy4JLSAO1E0NIAURO68tCPVFAwkTGcAYEBQNch1woA8uCO4sjKAFjPFsntVJPywI3iAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQ="; + let account = Boc::decode_base64(account_base64)?; + + let state = Account::load_from(&mut account.as_slice()?)?; - if let AccountState::AccountActive { state_init } = state.storage.state() { + if let AccountState::Active(state_init) = state_init.data { let init_data = InitData::try_from(state_init.data().unwrap())?; assert_eq!(init_data.is_signature_allowed, true); assert_eq!( @@ -427,42 +368,42 @@ mod tests { Ok(()) } - #[test] - fn check_signature_test() -> anyhow::Result<()> { - let public_key_bytes = - hex::decode("6c2f9514c1c0f2ec54cffe1ac2ba0e85268e76442c14205581ebc808fe7ee52c")?; - //let payload = base64::decode("te6ccgECCQEAAWMAASFzaW50f///EWjJNSIAAAABoAECCg7DyG0DBQIB80IAEiSxvuIkjLwTZ/69OCTi5io4ZpgjPKnD56XnecGH1Q0gcJ32yAAAAAAAAAAAAAAAAABz4iFDAAAAAAAAAAAAAAAAO5rKAIAfPq6ksCQX/kNfsY8xS5PTRd4WSjwjs5C/fod9ktFK+MAAAAAAAAAAAAAAAAD39JAwAwFDgBI2HlLkTtTC7ntWgsSS4jmXMUkhy2OTDHvAO1YAIIdyCAQBCAAAAAAIAgoOw8htAwgGAdNCABIksb7iJIy8E2f+vTgk4uYqOGaYIzypw+el53nBh9UNIC+vCAAAAAAAAAAAAAAAAAAARqnX7AAAAAAAAAAAAAAAAAIA9mKAH6YK7ZtGhTyJBnq9b54dnz07z830q8r/r5MBXJdSioIQBwFDgBhcpJ/VWhGKPK44GyznIrRqKDcoivK5/ZanRrMrFKCjiAgAAA==")?; - let payload = base64::decode("te6ccgECCQEAAaMAAaFzaW50f///EWjJNSIAAAABr9SYdbfeTOkhxaWVTsB40YIzxnswT6p7oxjydvTUZ0afi8fq5F2NvuyGho+YxBUC2NPkhtL3+tuMa5CfUwJMg2ABAgoOw8htAwUCAfNCABIksb7iJIy8E2f+vTgk4uYqOGaYIzypw+el53nBh9UNIHCd9sgAAAAAAAAAAAAAAAAAc+IhQwAAAAAAAAAAAAAAADuaygCAHz6upLAkF/5DX7GPMUuT00XeFko8I7OQv36HfZLRSvjAAAAAAAAAAAAAAAAA9/SQMAMBQ4ASNh5S5E7Uwu57VoLEkuI5lzFJIctjkwx7wDtWACCHcggEAQgAAAAACAIKDsPIbQMIBgHTQgASJLG+4iSMvBNn/r04JOLmKjhmmCM8qcPnped5wYfVDSAvrwgAAAAAAAAAAAAAAAAAAEap1+wAAAAAAAAAAAAAAAACAPZigB+mCu2bRoU8iQZ6vW+eHZ89O8/N9KvK/6+TAVyXUoqCEAcBQ4AYXKSf1VoRijyuOBss5yK0aig3KIryuf2Wp0azKxSgo4gIAAA=")?; - let in_msg_body = ton_types::deserialize_tree_of_cells(&mut payload.as_slice())?; - let in_msg_body_slice = SliceData::load_cell(in_msg_body)?; - - let public_key = PublicKey::from_bytes(public_key_bytes.as_slice())?; - - let result = check_signature(in_msg_body_slice, public_key, Some(2000))?; - assert!(result); - Ok(()) - } - - fn check_signature( - mut in_msg_body: SliceData, - public_key: PublicKey, - signature_id: Option, - ) -> anyhow::Result { - let signature_binding = in_msg_body - .get_slice(in_msg_body.remaining_bits() - 512, 512)? - .remaining_data(); - let sig = signature_binding.data(); - - let payload = in_msg_body - .shrink_data(in_msg_body.remaining_bits() - 512..) - .into_cell(); - - let hash = payload.repr_hash(); - - let data = extend_with_signature_id(hash.as_ref(), signature_id); - - Ok(public_key - .verify(&*data, &Signature::from_bytes(sig)?) - .is_ok()) - } + // #[test] + // fn check_signature_test() -> anyhow::Result<()> { + // let public_key_bytes = + // hex::decode("6c2f9514c1c0f2ec54cffe1ac2ba0e85268e76442c14205581ebc808fe7ee52c")?; + // //let payload = base64::decode("te6ccgECCQEAAWMAASFzaW50f///EWjJNSIAAAABoAECCg7DyG0DBQIB80IAEiSxvuIkjLwTZ/69OCTi5io4ZpgjPKnD56XnecGH1Q0gcJ32yAAAAAAAAAAAAAAAAABz4iFDAAAAAAAAAAAAAAAAO5rKAIAfPq6ksCQX/kNfsY8xS5PTRd4WSjwjs5C/fod9ktFK+MAAAAAAAAAAAAAAAAD39JAwAwFDgBI2HlLkTtTC7ntWgsSS4jmXMUkhy2OTDHvAO1YAIIdyCAQBCAAAAAAIAgoOw8htAwgGAdNCABIksb7iJIy8E2f+vTgk4uYqOGaYIzypw+el53nBh9UNIC+vCAAAAAAAAAAAAAAAAAAARqnX7AAAAAAAAAAAAAAAAAIA9mKAH6YK7ZtGhTyJBnq9b54dnz07z830q8r/r5MBXJdSioIQBwFDgBhcpJ/VWhGKPK44GyznIrRqKDcoivK5/ZanRrMrFKCjiAgAAA==")?; + // let payload = base64::decode("te6ccgECCQEAAaMAAaFzaW50f///EWjJNSIAAAABr9SYdbfeTOkhxaWVTsB40YIzxnswT6p7oxjydvTUZ0afi8fq5F2NvuyGho+YxBUC2NPkhtL3+tuMa5CfUwJMg2ABAgoOw8htAwUCAfNCABIksb7iJIy8E2f+vTgk4uYqOGaYIzypw+el53nBh9UNIHCd9sgAAAAAAAAAAAAAAAAAc+IhQwAAAAAAAAAAAAAAADuaygCAHz6upLAkF/5DX7GPMUuT00XeFko8I7OQv36HfZLRSvjAAAAAAAAAAAAAAAAA9/SQMAMBQ4ASNh5S5E7Uwu57VoLEkuI5lzFJIctjkwx7wDtWACCHcggEAQgAAAAACAIKDsPIbQMIBgHTQgASJLG+4iSMvBNn/r04JOLmKjhmmCM8qcPnped5wYfVDSAvrwgAAAAAAAAAAAAAAAAAAEap1+wAAAAAAAAAAAAAAAACAPZigB+mCu2bRoU8iQZ6vW+eHZ89O8/N9KvK/6+TAVyXUoqCEAcBQ4AYXKSf1VoRijyuOBss5yK0aig3KIryuf2Wp0azKxSgo4gIAAA=")?; + // let in_msg_body = ton_types::deserialize_tree_of_cells(&mut payload.as_slice())?; + // let in_msg_body_slice = SliceData::load_cell(in_msg_body)?; + // + // let public_key = PublicKey::from_bytes(public_key_bytes.as_slice())?; + // + // let result = check_signature(in_msg_body_slice, public_key, Some(2000))?; + // assert!(result); + // Ok(()) + // } + // + // fn check_signature( + // mut in_msg_body: SliceData, + // public_key: PublicKey, + // signature_id: Option, + // ) -> anyhow::Result { + // let signature_binding = in_msg_body + // .get_slice(in_msg_body.remaining_bits() - 512, 512)? + // .remaining_data(); + // let sig = signature_binding.data(); + // + // let payload = in_msg_body + // .shrink_data(in_msg_body.remaining_bits() - 512..) + // .into_cell(); + // + // let hash = payload.repr_hash(); + // + // let data = extend_with_signature_id(hash.as_ref(), signature_id); + // + // Ok(public_key + // .verify(&*data, &Signature::from_bytes(sig)?) + // .is_ok()) + // } } From d430d7e049e26c74e516015f77bcf6e1e9ba753d Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Tue, 27 Jan 2026 15:08:26 +0300 Subject: [PATCH 08/59] fix errors --- src/utils/mod.rs | 2 +- src/utils/ton_wallet/ever_wallet.rs | 29 ++++---- src/utils/ton_wallet/highload_wallet_v2.rs | 80 ++++++++++++---------- src/utils/ton_wallet/mod.rs | 4 +- src/utils/ton_wallet/multisig.rs | 22 +++--- src/utils/ton_wallet/wallet_v3.rs | 32 +++++---- src/utils/ton_wallet/wallet_v3v4.rs | 33 +++++---- src/utils/ton_wallet/wallet_v5r1.rs | 26 ++++--- src/utils/wallets/ever_wallet.rs | 2 +- src/utils/wallets/mod.rs | 1 - src/utils/wallets/notifications.rs | 41 ----------- 11 files changed, 126 insertions(+), 146 deletions(-) delete mode 100644 src/utils/wallets/notifications.rs diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2c9a291..bc16178 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -71,7 +71,7 @@ macro_rules! declare_function { vec![$($crate::utils::declare_function!(@header_item $header)),+] }; (@header_item pubkey) => { - tycho_types::abi::AbiHeaderType::Pubkey + tycho_types::abi::AbiHeaderType::PublicKey }; (@header_item time) => { tycho_types::abi::AbiHeaderType::Time diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs index 001fde1..2c14517 100644 --- a/src/utils/ton_wallet/ever_wallet.rs +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -3,7 +3,9 @@ use ed25519_dalek::PublicKey; use tycho_types::{ abi::{AbiHeaderType, AbiVersion, Function, NamedAbiValue, UnsignedExternalMessage}, cell::{CellBuilder, HashBytes}, - models::{Account, AccountState, IntAddr, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, + models::{ + Account, AccountState, CurrencyCollection, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr, + }, }; use crate::utils::ton_wallet::{Gift, TonWalletDetails}; @@ -15,14 +17,15 @@ pub fn prepare_deploy( expire_at: u32, ) -> Result { let state_init = prepare_state_init(public_key)?; - let hash = CellBuilder::build_from(&state_init)?.repr_hash(); + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); - let dst = StdAddr::new(workchain, hash.into()); + let dst = StdAddr::new(workchain, *hash); let headers = vec![ AbiHeaderType::Time, AbiHeaderType::Expire, - AbiHeaderType::Pubkey, + AbiHeaderType::PublicKey, ]; let function = Function::builder(AbiVersion::V2_3, "sendTransactionRaw") .with_headers(headers) @@ -58,7 +61,7 @@ pub fn prepare_transfer( (1, Some(gift)) if gift.state_init.is_none() => { let function = ever_wallet::send_transaction(); function.encode_external(&[ - NamedAbiValue::from(("destination", gift.destination)), + NamedAbiValue::from(("destination", gift.destination.into())), NamedAbiValue::from(("amount", gift.amount.into())), NamedAbiValue::from(("bounce", gift.bounce)), NamedAbiValue::from(("flags", gift.flags)), @@ -80,8 +83,8 @@ pub fn prepare_transfer( info: MsgInfo::Int(IntMsgInfo { ihr_disabled: true, bounce: gift.bounce, - dst: gift.destination, - value: gift.amount.into(), + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), ..Default::default() }), init: gift.state_init, @@ -122,16 +125,16 @@ pub fn is_ever_wallet(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> IntAddr { - let hash = prepare_state_init(public_key) - .and_then(|state| state.hash()) - .trust_me(); - IntAddr::Std(StdAddr::new(workchain_id, hash.into())) +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { + let state = prepare_state_init(public_key)?; + let binding = CellBuilder::build_from(state)?; + let hash = binding.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) } pub fn prepare_state_init(public_key: &PublicKey) -> Result { let mut builder = CellBuilder::new(); - builder.store_u256(public_key.as_bytes())?; + builder.store_u256(&public_key.as_bytes())?; builder.store_u64(0)?; let data = builder.build()?; diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 6e3f03c..14292ae 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -4,9 +4,11 @@ use anyhow::Result; use ed25519_dalek::PublicKey; use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, - cell::{Cell, CellBuilder, HashBytes}, + cell::{Cell, CellBuilder, CellFamily, HashBytes, Load, Store}, dict::Dict, - models::{Account, AccountState, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, + models::{ + Account, AccountState, CurrencyCollection, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr, + }, }; use crate::utils::wallets::code::highload_wallet_v2; @@ -19,7 +21,7 @@ pub fn prepare_deploy( expire_at: u32, ) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); - let dst = compute_contract_address(public_key, workchain); + let dst = compute_contract_address(public_key, workchain)?; let (hash, payload) = init_data.make_deploy_payload(expire_at)?; let unsigned_body = UnsignedBody { payload, @@ -60,9 +62,9 @@ pub fn prepare_transfer( ), }; - if init_data.data.len()? >= 500_usize { - return Err(HighloadWalletV2Error::InitDataTooLarge.into()); - } + // if init_data.data.len()? >= 500_usize { + // return Err(HighloadWalletV2Error::InitDataTooLarge.into()); + // } let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at)?; let unsigned_body = UnsignedBody { @@ -86,7 +88,6 @@ pub fn prepare_transfer( Ok(unsigned_message) } -#[derive(Clone)] struct UnsignedHighloadWalletV2Message { init_data: InitData, gifts: Vec, @@ -105,11 +106,10 @@ pub fn is_highload_wallet_v2(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> StdAddr { +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { InitData::from_key(public_key) .with_wallet_id(WALLET_ID) .compute_addr(workchain_id) - .trust_me() } pub static DETAILS: TonWalletDetails = TonWalletDetails { @@ -142,7 +142,7 @@ impl InitData { Self { wallet_id: 0, last_cleaned: 0, - public_key: key.as_bytes().into(), + public_key: HashBytes::from_slice(key.as_bytes()), data: Dict::default(), } } @@ -154,8 +154,9 @@ impl InitData { pub fn compute_addr(&self, workchain_id: i8) -> Result { let state_init = self.make_state_init()?; - let hash = CellBuilder::build_from(&state_init)?.repr_hash(); - Ok(StdAddr::new(workchain_id, hash.into())) + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) } pub fn make_state_init(&self) -> Result { @@ -170,9 +171,13 @@ impl InitData { let mut builder = CellBuilder::new(); builder.store_u32(self.wallet_id)?; builder.store_u64(self.last_cleaned)?; - builder.store_u256(self.public_key.as_bytes())?; + builder.store_u256(&self.public_key)?; + + let dict = CellBuilder::build_from(&self.data)?; + + builder.store_bit_one()?; + builder.store_cell_data(&dict)?; let data = builder.build()?; - self.data.add(0, data.clone())?; Ok(data) } @@ -186,7 +191,7 @@ impl InitData { let payload = builder.build()?; - let hash = payload.clone().repr_hash(); + let hash = payload.repr_hash(); Ok((*hash, payload)) } @@ -199,20 +204,21 @@ impl InitData { // Prepare messages array let mut messages = Dict::::new(); for (i, gift) in gifts.into_iter().enumerate() { + let body = gift.body.unwrap_or(Default::default()); let internal_message = Message { info: MsgInfo::Int(IntMsgInfo { ihr_disabled: true, bounce: gift.bounce, - dst: gift.destination, - value: gift.amount.into(), + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), ..Default::default() }), init: gift.state_init, - body: gift.body.unwrap_or(Default::default()).as_slice()?, + body: body.as_slice()?, layout: None, }; - let cell = CellBuilder::build_from(internal_message.borrow())?; + let cell = CellBuilder::build_from(internal_message)?; let mut item = CellBuilder::new(); item.store_u8(gift.flags)?; @@ -222,15 +228,18 @@ impl InitData { messages.set(key, item.build()?)?; } - let messages = CellBuilder::build_from(messages.borrow())?; - let messages_hash = messages.repr_hash(); + let mut message_builder = CellBuilder::new(); + messages.store_into(&mut message_builder, Cell::empty_context())?; + + let messages_cell = message_builder.clone().build()?; + let messages_hash = messages_cell.repr_hash(); // Build payload let mut payload = CellBuilder::new(); payload.store_u32(self.wallet_id)?; payload.store_u32(expire_at)?; payload.store_raw(&messages_hash.as_slice()[28..32], 32)?; - payload.store_builder(&messages.into())?; + payload.store_builder(&message_builder)?; let payload = payload.build()?; let hash = payload.repr_hash(); @@ -249,8 +258,7 @@ impl TryFrom<&Cell> for InitData { let mut buffer = [0u8; 32]; slice.load_raw(&mut buffer, 32)?; let public_key = HashBytes::from_slice(&buffer); - let mut data = Dict::::new(); - data.load_from(&mut slice)?; + let data = Dict::::load_from(&mut slice)?; Ok(Self { wallet_id, @@ -280,28 +288,30 @@ enum HighloadWalletV2Error { #[cfg(test)] pub mod tests { use anyhow::Result; - use ton_block::Deserializable; - use ton_types::HashmapType; + use tycho_types::{ + boc::Boc, + cell::Load, + models::{Account, AccountState}, + }; use crate::utils::ton_wallet::highload_wallet_v2::InitData; #[tokio::test] async fn check_state() -> Result<()> { - let data = "te6ccgICCBAAAQAAOOsAAAIBmggHAAEBWQAAAABij1ipvO9y33lafOQTY2Zjcpu/tM7FomMbSFDp4+8Ei8aUcpwDouTBwAACAgiLsUesAl4AAwIBYgA1AAQCAnAAFAAFAgFIABEABgIBIAAKAAcCAW4ACQAIAAm3P2/0YAAJty45OOACASAADAALAAm7gCmMCAIBIAAQAA0CASAADwAOAAm3SNaR4AAJt2IEcmAACbn59J8wAgEgABMAEgAJvH0s0sQACb0fFP0kAgEgACYAFQIBIAAfABYCASAAGgAXAgEgABkAGAAJulOvR9gACbr90p9IAgFYABwAGwAJuaPhPtACASAAHgAdAAm29FO6oAAJt4e9WeACASAAJQAgAgEgACQAIQIBIAAjACIACbg4HOPQAAm5N8GT0AAJumQHj3gACbxuQUbMAgEgACwAJwIBIAApACgACbxoeVqsAgEgACsAKgAJu3kh5AgACbuT7z2oAgEgADIALQIBIAAxAC4CAUgAMAAvAAm3D/yF4AAJttuTf6AACboR+FvYAgEgADQAMwAJu/xtoCgACbswljEoAgEgAVEANgIBIADGADcCASAAfQA4AgEgAFwAOQIBIABLADoCASAASgA7AgEgAD8APAIBIAA+AD0ACbv67XUoAAm728BYuAIBIABDAEACASAAQgBBAAm4XHbOEAAJuEX0C9ACASAASQBEAgEgAEgARQIBSABHAEYACLJFqpcACLIZDQgACbb/vM5gAAm5tBZx8AAJv6qZvYYCASAAVQBMAgEgAFIATQIBWABRAE4CAWYAUABPAAizva6wAAizJMbvAAm4yrfY0AIBIABUAFMACbv97cQIAAm7rFAKCAIBIABXAFYACbwdUD3MAgEgAFkAWAAJuqMTi4gCA5B3AFsAWgAHqXo6sAAHqTSr0AIBIABsAF0CASAAYwBeAgEgAGIAXwICcQBhAGAACbU816HAAAm0goAhwAAJvMcETxwCASAAawBkAgEgAGoAZQIBIABnAGYACbjQvmEQAgJxAGkAaAAHsBsUkQAHsGq37wAJu6lVp4gACb01zX/cAgEgAHYAbQIBIABzAG4CASAAcABvAAm6QVAyuAIBSAByAHEACbd6cIWgAAm2kdMmYAIBIAB1AHQACbotzlT4AAm66wR8CAIBIAB8AHcCASAAewB4AgEgAHoA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trax3CACASADAgMBAAm7WoeimAIBIAMEAwMACbm8yrWwAAm5HOVjEAIBSAMHAwYACbpktfloAAm7LsDquAIBIAMUAwkCAVgDCwMKAAm6hR6B6AIBIAMPAwwCASADDgMNAAm2f4hroAAJtllCn+ACASADEQMQAAm2Yx6y4AIBIAMTAxIACbSflolAAAm0QerswAIBIAMYAxUCASADFwMWAAm7tkoeSAAJuknHOugACb2TEThEAgEgAzkDGgIBIAMoAxsCASADIQMcAgEgAyADHQIBIAMfAx4ACbuaE9GIAAm76TwyuAAJvTF5SlwCASADJQMiAgFmAyQDIwAJt+MXWCAACbfdIXagAgEgAycDJgAJu+jFS4gACbqhfTLYAgEgAzIDKQIBIAMvAyoCAVgDLgMrAgFYAy0DLAAJtOPh2kAACbTQbPDAAAm5hunfsAIBIAMxAzAACbqF6XDoAAm7UeL9mAIBIAM2AzMCASADNQM0AAm7xZq5KAAJuxrvMUgCASADOAM3AAm7OY2quAAJumXrW7gCASADSwM6AgEgA0YDOwIBIANBAzwCASADPgM9AAm7wUzueAIBIANAAz8ACbmxiDdwAAm5KDyKEAIBIANDA0IACbupeOFIAgEgA0UDRAAJuNLLTRAACbir+oZQAgEgA0oDRwIBIANJA0gACbonQw4YAAm6iJr9KAAJvAwRrswCASADVQNMAgEgA1QDTQIBIANPA04ACbo2gb2IAgFqA1EDUAAJtAxQcEACAVgDUwNSAAex5r87AAex7i7vAAm8a0P6FAIBIANXA1YACb0WUg0sAgEgA1kDWAAJupoiEwgACbsZNqNoAgEgA94DWwIBIAObA1wCASADeANdAgEgA3EDXgIBIANmA18CASADYwNgAgEgA2IDYQAJu3UoScgACbtyxfUoAgEgA2UDZAAJu7/lGDgACbugeIXoAgEgA24DZwIBIANrA2gCASADagNpAAm5vLA10AAJubPahdACASADbQNsAAm5tK+QUAAJuEioGPACAUgDcANvAAm5CURc8AAJuCyslfACAUgDcwNyAAm8IYMwTAIBSAN3A3QCASADdgN1AAm3DbyIoAAJti1cfKAACbgyOlvwAgEgA4oDeQIBIAOBA3oCASADgAN7AgEgA30DfAAJu0UfkFgCASADfwN+AAm4pvk5sAAJua8OZHAA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"; + let account_base64 = "te6ccgICCBAAAQAAOOsAAAIBmggHAAEBWQAAAABij1ipvO9y33lafOQTY2Zjcpu/tM7FomMbSFDp4+8Ei8aUcpwDouTBwAACAgiLsUesAl4AAwIBYgA1AAQCAnAAFAAFAgFIABEABgIBIAAKAAcCAW4ACQAIAAm3P2/0YAAJty45OOACASAADAALAAm7gCmMCAIBIAAQAA0CASAADwAOAAm3SNaR4AAJt2IEcmAACbn59J8wAgEgABMAEgAJvH0s0sQACb0fFP0kAgEgACYAFQIBIAAfABYCASAAGgAXAgEgABkAGAAJulOvR9gACbr90p9IAgFYABwAGwAJuaPhPtACASAAHgAdAAm29FO6oAAJt4e9WeACASAAJQAgAgEgACQAIQIBIAAjACIACbg4HOPQAAm5N8GT0AAJumQHj3gACbxuQUbMAgEgACwAJwIBIAApACgACbxoeVqsAgEgACsAKgAJu3kh5AgACbuT7z2oAgEgADIALQIBIAAxAC4CAUgAMAAvAAm3D/yF4AAJttuTf6AACboR+FvYAgEgADQAMwAJu/xtoCgACbswljEoAgEgAVEANgIBIADGADcCASAAfQA4AgEgAFwAOQIBIABLADoCASAASgA7AgEgAD8APAIBIAA+AD0ACbv67XUoAAm728BYuAIBIABDAEACASAAQgBBAAm4XHbOEAAJuEX0C9ACASAASQBEAgEgAEgARQIBSABHAEYACLJFqpcACLIZDQgACbb/vM5gAAm5tBZx8AAJv6qZvYYCASAAVQBMAgEgAFIATQIBWABRAE4CAWYAUABPAAizva6wAAizJMbvAAm4yrfY0AIBIABUAFMACbv97cQIAAm7rFAKCAIBIABXAFYACbwdUD3MAgEgAFkAWAAJuqMTi4gCA5B3AFsAWgAHqXo6sAAHqTSr0AIBIABsAF0CASAAYwBeAgEgAGIAXwICcQBhAGAACbU816HAAAm0goAhwAAJvMcETxwCASAAawBkAgEgAGoAZQIBIABnAGYACbjQvmEQAgJxAGkAaAAHsBsUkQAHsGq37wAJu6lVp4gACb01zX/cAgEgAHYAbQIBIABzAG4CASAAcABvAAm6QVAyuAIBSAByAHEACbd6cIWgAAm2kdMmYAIBIAB1AHQACbotzlT4AAm66wR8CAIBIAB8AHcCASAAewB4AgEgAHoA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trax3CACASADAgMBAAm7WoeimAIBIAMEAwMACbm8yrWwAAm5HOVjEAIBSAMHAwYACbpktfloAAm7LsDquAIBIAMUAwkCAVgDCwMKAAm6hR6B6AIBIAMPAwwCASADDgMNAAm2f4hroAAJtllCn+ACASADEQMQAAm2Yx6y4AIBIAMTAxIACbSflolAAAm0QerswAIBIAMYAxUCASADFwMWAAm7tkoeSAAJuknHOugACb2TEThEAgEgAzkDGgIBIAMoAxsCASADIQMcAgEgAyADHQIBIAMfAx4ACbuaE9GIAAm76TwyuAAJvTF5SlwCASADJQMiAgFmAyQDIwAJt+MXWCAACbfdIXagAgEgAycDJgAJu+jFS4gACbqhfTLYAgEgAzIDKQIBIAMvAyoCAVgDLgMrAgFYAy0DLAAJtOPh2kAACbTQbPDAAAm5hunfsAIBIAMxAzAACbqF6XDoAAm7UeL9mAIBIAM2AzMCASADNQM0AAm7xZq5KAAJuxrvMUgCASADOAM3AAm7OY2quAAJumXrW7gCASADSwM6AgEgA0YDOwIBIANBAzwCASADPgM9AAm7wUzueAIBIANAAz8ACbmxiDdwAAm5KDyKEAIBIANDA0IACbupeOFIAgEgA0UDRAAJuNLLTRAACbir+oZQAgEgA0oDRwIBIANJA0gACbonQw4YAAm6iJr9KAAJvAwRrswCASADVQNMAgEgA1QDTQIBIANPA04ACbo2gb2IAgFqA1EDUAAJtAxQcEACAVgDUwNSAAex5r87AAex7i7vAAm8a0P6FAIBIANXA1YACb0WUg0sAgEgA1kDWAAJupoiEwgACbsZNqNoAgEgA94DWwIBIAObA1wCASADeANdAgEgA3EDXgIBIANmA18CASADYwNgAgEgA2IDYQAJu3UoScgACbtyxfUoAgEgA2UDZAAJu7/lGDgACbugeIXoAgEgA24DZwIBIANrA2gCASADagNpAAm5vLA10AAJubPahdACASADbQNsAAm5tK+QUAAJuEioGPACAUgDcANvAAm5CURc8AAJuCyslfACAUgDcwNyAAm8IYMwTAIBSAN3A3QCASADdgN1AAm3DbyIoAAJti1cfKAACbgyOlvwAgEgA4oDeQIBIAOBA3oCASADgAN7AgEgA30DfAAJu0UfkFgCASADfwN+AAm4pvk5sAAJua8OZHAA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"; + + let account = Boc::decode_base64(account_base64)?; - let bytes = base64::decode(data).expect("cant decode data"); - let cell = - ton_types::deserialize_tree_of_cells(&mut bytes.as_slice()).expect("deser failed"); - let account = ton_block::AccountState::construct_from_cell(cell)?; + let state = Account::load_from(&mut account.as_slice()?)?; - let data = match account { - ton_block::AccountState::AccountActive { state_init } => state_init.data.unwrap(), + let data = match state.state { + AccountState::Active(state_init) => state_init.data.unwrap(), _ => anyhow::bail!("ACCOUNT NOT ACTIVE"), }; - let init_data = InitData::try_from(&data).expect("init data failed"); + let _init_data = InitData::try_from(&data).expect("init data failed"); - println!("{:?}", init_data.data.len().unwrap()); + //println!("{:?}", init_data.data.len().unwrap()); Ok(()) } diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs index 331baf3..6b7c6c5 100644 --- a/src/utils/ton_wallet/mod.rs +++ b/src/utils/ton_wallet/mod.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use nekoton_utils::*; use tycho_types::cell::{Cell, HashBytes}; -use tycho_types::models::{IntAddr, StateInit, StdAddr}; +use tycho_types::models::{StateInit, StdAddr}; use crate::utils::wallets; @@ -54,7 +54,7 @@ pub struct TonWalletDetails { pub struct Gift { pub flags: u8, pub bounce: bool, - pub destination: IntAddr, + pub destination: StdAddr, pub amount: u128, pub body: Option, pub state_init: Option, diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index 03422fb..a72ef56 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -9,6 +9,7 @@ use tycho_types::{ dict::RawDict, models::{Account, StateInit, StdAddr}, }; +use tycho_util::time::Clock; use crate::utils::ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}; @@ -39,9 +40,10 @@ pub fn prepare_deploy( params: DeployParams<'_>, ) -> Result { let state_init = prepare_state_init(public_key, multisig_type); - let hash = CellBuilder::build_from(&state_init)?.repr_hash(); + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); - let dst = StdAddr::new(workchain, hash.into()); + let dst = StdAddr::new(workchain, *hash); let owners = params .owners @@ -345,8 +347,9 @@ pub fn compute_contract_address( workchain_id: i8, ) -> Result { let state_init = prepare_state_init(public_key, multisig_type); - let hash = CellBuilder::build_from(&state_init)?.repr_hash(); - Ok(StdAddr::new(workchain_id, hash.into())) + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) } pub fn ton_wallet_details(multisig_type: MultisigType) -> TonWalletDetails { @@ -407,11 +410,12 @@ fn run_local( function: &Function, account_stuff: Account, ) -> Result> { - let ExecutionOutput { - tokens, - result_code, - } = function.run_local(clock, account_stuff, &[], &[])?; - tokens.ok_or_else(|| MultisigError::NonZeroResultCode(result_code).into()) + unimplemented!() + //let ExecutionOutput { + // tokens, + // result_code, + //} = function.run_local(clock, account_stuff, &[], &[])?; + //tokens.ok_or_else(|| MultisigError::NonZeroResultCode(result_code).into()) } #[derive(Copy, Clone)] diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index 3a37ab2..5b3d27f 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -5,7 +5,9 @@ use ed25519_dalek::PublicKey; use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes}, - models::{Account, AccountState, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, + models::{ + Account, AccountState, CurrencyCollection, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr, + }, }; use tycho_util::time::now_sec; @@ -130,7 +132,6 @@ pub fn prepare_transfer( Ok(unsigned_message) } -#[derive(Clone)] struct UnsignedWalletV3Message { init_data: InitData, gifts: Vec, @@ -153,8 +154,9 @@ pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Res let state_init = InitData::from_key(public_key) .with_wallet_id(WALLET_ID) .make_state_init()?; - let hash = CellBuilder::build_from(&state_init)?.repr_hash(); - Ok(StdAddr::new(workchain_id, hash.into())) + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) } pub static DETAILS: TonWalletDetails = TonWalletDetails { @@ -188,7 +190,7 @@ impl InitData { Self { seqno: 0, wallet_id: 0, - public_key: key.as_bytes().into(), + public_key: HashBytes::from_slice(key.as_bytes()), } } @@ -199,8 +201,9 @@ impl InitData { pub fn compute_addr(&self, workchain_id: i8) -> Result { let state_init = self.make_state_init()?; - let hash = CellBuilder::build_from(&state_init)?.repr_hash(); - Ok(StdAddr::new(workchain_id, hash.into())) + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) } pub fn make_state_init(&self) -> Result { @@ -215,7 +218,7 @@ impl InitData { let mut builder = CellBuilder::new(); builder.store_u32(self.seqno)?; builder.store_u32(self.wallet_id)?; - builder.store_u256(self.public_key.as_bytes())?; + builder.store_u256(&self.public_key)?; let data = builder.build()?; Ok(data) } @@ -233,21 +236,22 @@ impl InitData { // create internal message for gift in gifts { + let body = gift.body.unwrap_or(Default::default()); let internal_message = Message { info: MsgInfo::Int(IntMsgInfo { ihr_disabled: true, bounce: gift.bounce, - dst: gift.destination, - value: gift.amount.into(), + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), ..Default::default() }), init: gift.state_init, - body: gift.body.unwrap_or(Default::default()).as_slice()?, + body: body.as_slice()?, layout: None, }; // append it to the body - builder.store_u8(self.flags)?; - builder.store_reference(CellBuilder::build_from(internal_message.borrow())?)?; + builder.store_u8(gift.flags)?; + builder.store_reference(CellBuilder::build_from(internal_message)?)?; } let payload = builder.build()?; @@ -262,13 +266,11 @@ impl TryFrom<&Cell> for InitData { fn try_from(data: &Cell) -> Result { let mut slice = data.as_slice()?; - let is_signature_allowed = slice.load_bit()?; let seqno = slice.load_u32()?; let wallet_id = slice.load_u32()?; let mut buffer = [0u8; 32]; slice.load_raw(&mut buffer, 32)?; let public_key = HashBytes::from_slice(&buffer); - let extensions = Option::::load_from(&mut slice)?; Ok(Self { seqno, diff --git a/src/utils/ton_wallet/wallet_v3v4.rs b/src/utils/ton_wallet/wallet_v3v4.rs index ed8318e..a5ebaab 100644 --- a/src/utils/ton_wallet/wallet_v3v4.rs +++ b/src/utils/ton_wallet/wallet_v3v4.rs @@ -5,7 +5,10 @@ use ed25519_dalek::PublicKey; use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes}, - models::{Account, AccountState, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr}, + models::{ + Account, AccountState, CurrencyCollection, IntAddr, IntMsgInfo, Message, MsgInfo, + StateInit, StdAddr, + }, }; use crate::utils::{ @@ -89,7 +92,6 @@ pub fn prepare_transfer( Ok(unsigned_message) } -#[derive(Clone)] struct UnsignedWallet { init_data: InitData, gifts: Vec, @@ -140,11 +142,10 @@ pub fn compute_contract_address( public_key: &PublicKey, workchain_id: i8, version: WalletVersion, -) -> StdAddr { +) -> Result { InitData::from_key(public_key) .with_subwallet_id(WALLET_ID) .compute_addr(workchain_id, version) - .trust_me() } pub static DETAILS: TonWalletDetails = TonWalletDetails { @@ -178,7 +179,7 @@ impl InitData { Self { seqno: 0, wallet_id: 0, - public_key: key.as_bytes().into(), + public_key: HashBytes::from_slice(key.as_bytes()), } } @@ -189,8 +190,9 @@ impl InitData { pub fn compute_addr(&self, workchain_id: i8, version: WalletVersion) -> Result { let state_init = self.make_state_init(version)?; - let hash = CellBuilder::build_from(&state_init)?.repr_hash(); - Ok(StdAddr::new(workchain_id, hash.into())) + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) } pub fn make_state_init(&self, version: WalletVersion) -> Result { @@ -212,7 +214,7 @@ impl InitData { let mut builder = CellBuilder::new(); builder.store_u32(self.seqno)?; builder.store_u32(self.wallet_id as _)?; - builder.store_u256(self.public_key.as_bytes())?; + builder.store_u256(&self.public_key)?; if matches!(version, WalletVersion::V4R1 | WalletVersion::V4R2) { // empty plugin dict @@ -242,21 +244,22 @@ impl InitData { // create internal message for gift in gifts { + let body = gift.body.unwrap_or(Default::default()); let internal_message = Message { info: MsgInfo::Int(IntMsgInfo { ihr_disabled: true, bounce: gift.bounce, - dst: gift.destination, - value: gift.amount.into(), + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), ..Default::default() }), init: gift.state_init, - body: gift.body.unwrap_or(Default::default()).as_slice()?, + body: body.as_slice()?, layout: None, }; // append it to the body - builder.store_u8(self.flags)?; - builder.store_reference(CellBuilder::build_from(internal_message.borrow())?)?; + builder.store_u8(gift.flags)?; + builder.store_reference(CellBuilder::build_from(internal_message)?)?; } let payload = builder.build()?; @@ -273,7 +276,7 @@ impl TryFrom<&Cell> for InitData { let mut slice = data.as_slice()?; let seqno = slice.load_u32()?; - let wallet_id = slice.load_u32()?.into(); + let wallet_id = slice.load_u32()? as i32; let mut buffer = [0u8; 32]; slice.load_raw(&mut buffer, 32)?; let public_key = HashBytes::from_slice(&buffer); @@ -311,6 +314,8 @@ enum WalletV4Error { #[cfg(test)] mod tests { + use std::str::FromStr; + use tycho_types::{ boc::Boc, cell::{HashBytes, Load}, diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index eac7997..bbcb85f 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -6,8 +6,8 @@ use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes, Lazy}, models::{ - Account, AccountState, OutAction, OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, - StateInit, StdAddr, + Account, AccountState, CurrencyCollection, IntAddr, OutAction, OwnedRelaxedMessage, + RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, StdAddr, }, }; @@ -25,7 +25,7 @@ pub fn prepare_deploy( expire_at: u32, ) -> Result { let init_data = make_init_data(public_key); - let dst = compute_contract_address(public_key, workchain); + let dst = compute_contract_address(public_key, workchain)?; let (hash, payload) = init_data.make_transfer_payload(None, expire_at, false)?; let unsigned_body = UnsignedBody { payload, @@ -106,7 +106,6 @@ pub fn prepare_transfer( Ok(unsigned_message) } -#[derive(Clone)] struct UnsignedWalletV5 { init_data: InitData, gifts: Vec, @@ -125,10 +124,8 @@ pub fn is_wallet_v5r1(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> StdAddr { - make_init_data(public_key) - .compute_addr(workchain_id) - .trust_me() +pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { + make_init_data(public_key).compute_addr(workchain_id) } pub static DETAILS: TonWalletDetails = TonWalletDetails { @@ -165,7 +162,7 @@ impl InitData { is_signature_allowed: false, seqno: 0, wallet_id: 0, - public_key: key.as_bytes().into(), + public_key: HashBytes::from_slice(key.as_bytes()), extensions: Default::default(), } } @@ -182,8 +179,9 @@ impl InitData { pub fn compute_addr(&self, workchain_id: i8) -> Result { let state_init = self.make_state_init()?; - let hash = CellBuilder::build_from(&state_init)?.repr_hash(); - Ok(StdAddr::new(workchain_id, hash.into())) + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) } pub fn make_state_init(&self) -> Result { @@ -199,7 +197,7 @@ impl InitData { builder.store_bit(self.is_signature_allowed)?; builder.store_u32(self.seqno)?; builder.store_u32(self.wallet_id)?; - builder.store_u256(self.public_key.as_bytes())?; + builder.store_u256(&self.public_key)?; if let Some(extensions) = &self.extensions { builder.store_bit_one()?; @@ -247,8 +245,8 @@ impl InitData { info: RelaxedMsgInfo::Int(RelaxedIntMsgInfo { ihr_disabled: true, bounce: gift.bounce, - dst: gift.destination, - value: gift.amount.into(), + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), ..Default::default() }), init: gift.state_init, diff --git a/src/utils/wallets/ever_wallet.rs b/src/utils/wallets/ever_wallet.rs index 7646853..4d10297 100644 --- a/src/utils/wallets/ever_wallet.rs +++ b/src/utils/wallets/ever_wallet.rs @@ -14,7 +14,7 @@ pub fn send_transaction() -> &'static Function { AbiType::Uint(8).named("flags"), AbiType::Cell.named("payload"), ], - outputs: Vec::new(), + outputs: vec![], } } diff --git a/src/utils/wallets/mod.rs b/src/utils/wallets/mod.rs index 10c4b1a..6f56b39 100644 --- a/src/utils/wallets/mod.rs +++ b/src/utils/wallets/mod.rs @@ -2,4 +2,3 @@ pub mod code; pub mod ever_wallet; pub mod multisig; pub mod multisig2; -pub mod notifications; diff --git a/src/utils/wallets/notifications.rs b/src/utils/wallets/notifications.rs deleted file mode 100644 index 9f7dc55..0000000 --- a/src/utils/wallets/notifications.rs +++ /dev/null @@ -1,41 +0,0 @@ -use ton_abi::{Param, ParamType}; - -use crate::utils::declare_function; - -pub fn notify_wallet_deployed() -> &'static ton_abi::Function { - declare_function! { - abi: v2_0, - name: "notifyWalletDeployed", - inputs: vec![Param::new("root", ParamType::Address)], - outputs: Vec::new(), - } -} - -pub fn depool_on_round_complete() -> &'static ton_abi::Function { - declare_function! { - abi: v2_0, - name: "onRoundComplete", - inputs: vec![ - Param::new("roundId", ParamType::Uint(64)), - Param::new("reward", ParamType::Uint(64)), - Param::new("ordinaryStake", ParamType::Uint(64)), - Param::new("vestingStake", ParamType::Uint(64)), - Param::new("lockStake", ParamType::Uint(64)), - Param::new("reinvest", ParamType::Bool), - Param::new("reason", ParamType::Uint(8)), - ], - outputs: Vec::new(), - } -} - -pub fn depool_receive_answer() -> &'static ton_abi::Function { - declare_function! { - abi: v2_0, - name: "receiveAnswer", - inputs: vec![ - Param::new("errcode", ParamType::Uint(32)), - Param::new("comment", ParamType::Uint(64)), - ], - outputs: Vec::new(), - } -} From 2f585bc0c74e89b1aa1d62ba345ed133f8cf7fde Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 28 Jan 2026 12:00:23 +0300 Subject: [PATCH 09/59] fix errors --- src/utils/ton_wallet/ever_wallet.rs | 44 +++-- src/utils/ton_wallet/highload_wallet_v2.rs | 3 +- src/utils/ton_wallet/mod.rs | 2 +- src/utils/ton_wallet/multisig.rs | 129 ++++++++----- src/utils/ton_wallet/wallet_v3.rs | 3 +- src/utils/ton_wallet/wallet_v5r1.rs | 21 ++- src/utils/wallets/multisig.rs | 76 ++++++-- src/utils/wallets/multisig2.rs | 202 +++++++++++++++------ 8 files changed, 334 insertions(+), 146 deletions(-) diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs index 2c14517..145c217 100644 --- a/src/utils/ton_wallet/ever_wallet.rs +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -1,10 +1,11 @@ use anyhow::Result; use ed25519_dalek::PublicKey; use tycho_types::{ - abi::{AbiHeaderType, AbiVersion, Function, NamedAbiValue, UnsignedExternalMessage}, + abi::{AbiHeaderType, AbiValue, AbiVersion, Function, NamedAbiType, UnsignedExternalMessage}, cell::{CellBuilder, HashBytes}, models::{ - Account, AccountState, CurrencyCollection, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr, + Account, AccountState, CurrencyCollection, IntAddr, IntMsgInfo, Message, MsgInfo, + StateInit, StdAddr, }, }; @@ -29,8 +30,8 @@ pub fn prepare_deploy( ]; let function = Function::builder(AbiVersion::V2_3, "sendTransactionRaw") .with_headers(headers) - .with_inputs(vec![]) - .with_outputs(vec![]) + .with_inputs(vec![] as Vec) + .with_outputs(vec![] as Vec) .with_id(0x169e3e11) .build(); @@ -57,16 +58,18 @@ pub fn prepare_transfer( } let mut gifts = gifts.into_iter(); - let external_input = match (gifts.len(), gifts.next()) { + let (function, tokens) = match (gifts.len(), gifts.next()) { (1, Some(gift)) if gift.state_init.is_none() => { let function = ever_wallet::send_transaction(); - function.encode_external(&[ - NamedAbiValue::from(("destination", gift.destination.into())), - NamedAbiValue::from(("amount", gift.amount.into())), - NamedAbiValue::from(("bounce", gift.bounce)), - NamedAbiValue::from(("flags", gift.flags)), - NamedAbiValue::from(("body", gift.body.unwrap_or_default().into_cell())), - ]) + let tokens = [ + AbiValue::address(gift.destination).named("destination"), + AbiValue::uint(128, gift.amount).named("value"), + AbiValue::Bool(gift.bounce).named("bounce"), + AbiValue::uint(8, gift.flags).named("flags"), + AbiValue::Cell(gift.body.unwrap_or_default()).named("body"), + ] + .to_vec(); + (function, tokens) } (len, gift) => { let function = match len { @@ -79,6 +82,7 @@ pub fn prepare_transfer( let mut tokens = Vec::with_capacity(len * 2); for gift in gift.into_iter().chain(gifts) { + let body = gift.body.unwrap_or(Default::default()); let internal_message = Message { info: MsgInfo::Int(IntMsgInfo { ihr_disabled: true, @@ -88,20 +92,20 @@ pub fn prepare_transfer( ..Default::default() }), init: gift.state_init, - body: gift.body.unwrap_or(Default::default()).as_slice()?, + body: body.as_slice()?, layout: None, }; - tokens.push(NamedAbiValue::from(("flags", gift.flags.token_value()))); - tokens.push(NamedAbiValue::from(( - "message", - CellBuilder::build_from(internal_message.borrow())?, - ))); + tokens.push(AbiValue::uint(8, gift.flags).named("flags")); + tokens.push( + AbiValue::Cell(CellBuilder::build_from(internal_message)?).named("message"), + ); } - function.encode_external(&tokens) + (function, tokens) } }; + let external_input = function.encode_external(&tokens); let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; let mut unsigned_message = unsigned_body.with_dst(address); @@ -134,7 +138,7 @@ pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Res pub fn prepare_state_init(public_key: &PublicKey) -> Result { let mut builder = CellBuilder::new(); - builder.store_u256(&public_key.as_bytes())?; + builder.store_u256(&HashBytes::from(public_key.to_bytes()))?; builder.store_u64(0)?; let data = builder.build()?; diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 14292ae..f19b006 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -7,7 +7,8 @@ use tycho_types::{ cell::{Cell, CellBuilder, CellFamily, HashBytes, Load, Store}, dict::Dict, models::{ - Account, AccountState, CurrencyCollection, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr, + Account, AccountState, CurrencyCollection, IntAddr, IntMsgInfo, Message, MsgInfo, + StateInit, StdAddr, }, }; diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs index 6b7c6c5..55fbed8 100644 --- a/src/utils/ton_wallet/mod.rs +++ b/src/utils/ton_wallet/mod.rs @@ -112,7 +112,7 @@ impl WalletType { pub fn code(&self) -> Cell { match self { - Self::Multisig(multisig_type) => multisig_type.code(), + Self::Multisig(multisig_type) => multisig_type.code().unwrap(), Self::WalletV3 => wallets::code::wallet_v3(), Self::WalletV3R1 => wallets::code::wallet_v3r1(), Self::WalletV3R2 => wallets::code::wallet_v3r2(), diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index a72ef56..863d5e1 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -4,8 +4,8 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::PublicKey; use tycho_types::{ - abi::{AbiValue, Function, NamedAbiValue, UnsignedExternalMessage}, - cell::{Cell, CellBuilder, CellDataBuilder, HashBytes}, + abi::{AbiValue, Function, IntoAbi, NamedAbiValue, UnsignedExternalMessage}, + cell::{Cell, CellBuilder, CellDataBuilder, CellFamily, HashBytes, Load}, dict::RawDict, models::{Account, StateInit, StdAddr}, }; @@ -39,7 +39,7 @@ pub fn prepare_deploy( expire_at: u32, params: DeployParams<'_>, ) -> Result { - let state_init = prepare_state_init(public_key, multisig_type); + let state_init = prepare_state_init(public_key, multisig_type)?; let cell_builder = CellBuilder::build_from(&state_init)?; let hash = cell_builder.repr_hash(); @@ -48,7 +48,7 @@ pub fn prepare_deploy( let owners = params .owners .iter() - .map(|public_key| HashBytes::from(public_key.as_bytes())) + .map(|public_key| HashBytes(public_key.to_bytes())) .collect::>(); let is_new_multisig = multisig_type.is_multisig2(); @@ -60,17 +60,15 @@ pub fn prepare_deploy( return Err(MultisigError::CustomExpirationTimeNotSupported.into()); }; - let external_input = { - let mut abi_values = vec![ - NamedAbiValue::from(("owners", owners)), - NamedAbiValue::from(("reqConfirms", params.req_confirms)), - ]; - if is_new_multisig { - abi_values.push(NamedAbiValue::from(("lifetime", DEFAULT_LIFETIME))); - } + let mut abi_values = vec![ + owners.as_abi().named("owners"), + params.req_confirms.as_abi().named("reqConfirms"), + ]; + if is_new_multisig { + abi_values.push(params.expiration_time.unwrap_or(DEFAULT_LIFETIME).as_abi().named("lifetime")); + } - function.encode_external(&abi_values) - }; + let external_input = function.encode_external(&abi_values); let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; let mut unsigned_message = unsigned_body.with_dst(dst); @@ -96,7 +94,8 @@ pub fn prepare_confirm_transaction( address, expire_at, function, - vec![NamedAbiValue::from(("transactionId", transaction_id))], + vec![ + transaction_id.as_abi().named("transactionId")], ) } @@ -124,20 +123,19 @@ pub fn prepare_transfer( }; let mut named_abi_values = vec![ - NamedAbiValue::from(("destination", gift.destination)), - NamedAbiValue::from(("amount", gift.amount.into())), - NamedAbiValue::from(("bounce", gift.bounce)), - NamedAbiValue::from(("flags", all_balance)), - NamedAbiValue::from(("body", gift.body.unwrap_or_default().into_cell())), + AbiValue::address(gift.destination).named("destination"), + AbiValue::uint(128, gift.amount).named("amount"), + AbiValue::Bool(gift.bounce).named("bounce"), + AbiValue::uint(8, all_balance).named("flags"), + AbiValue::Cell(gift.body.unwrap_or_default()).named("body"), ]; if is_new_multisig { - named_abi_values.push(NamedAbiValue::from(( - "stateInit", + named_abi_values.push( gift.state_init .map(|state_init| CellBuilder::build_from(&state_init)) - .transpose()?, - ))); + .transpose()?.as_abi().named("stateInit"), +); } (function, named_abi_values) } else { @@ -146,12 +144,12 @@ pub fn prepare_transfer( } else { crate::utils::wallets::multisig::send_transaction() }; - let mut named_abi_values = vec![ - NamedAbiValue::from(("destination", gift.destination)), - NamedAbiValue::from(("amount", gift.amount.into())), - NamedAbiValue::from(("bounce", gift.bounce)), - NamedAbiValue::from(("flags", gift.flags)), - NamedAbiValue::from(("body", gift.body.unwrap_or_default().into_cell())), + let named_abi_values = vec![ + AbiValue::address(gift.destination).named("destination"), + AbiValue::uint(128, gift.amount).named("amount"), + AbiValue::Bool(gift.bounce).named("bounce"), + AbiValue::uint(8, gift.flags).named("flags"), + AbiValue::Cell(gift.body.unwrap_or_default()).named("body"), ]; (function, named_abi_values) }; @@ -183,7 +181,7 @@ pub fn prepare_code_update( req_confirms: None, lifetime: None, } - .pack(), + .abi_values(), ) } @@ -205,7 +203,7 @@ pub fn prepare_confirm_update( address, expire_at, multisig2::confirm_update(), - multisig2::ConfirmUpdateParams { update_id }.pack(), + multisig2::ConfirmUpdateParams { update_id }.abi_values(), ) } @@ -228,7 +226,7 @@ pub fn prepare_execute_update( address, expire_at, multisig2::execute_update(), - multisig2::ExecuteUpdateParams { update_id, code }.pack(), + multisig2::ExecuteUpdateParams { update_id, code }.abi_values(), ) } @@ -244,6 +242,47 @@ pub enum MultisigType { Multisig2_1, } +impl MultisigType { + #[inline(always)] + pub fn as_str(&self) -> &'static str { + match self { + Self::SafeMultisigWallet => "SafeMultisigWallet", + Self::SafeMultisigWallet24h => "SafeMultisigWallet24h", + Self::SetcodeMultisigWallet => "SetcodeMultisigWallet", + Self::SetcodeMultisigWallet24h => "SetcodeMultisigWallet24h", + Self::BridgeMultisigWallet => "BridgeMultisigWallet", + Self::SurfWallet => "SurfWallet", + Self::Multisig2 => "Multisig2", + Self::Multisig2_1 => "Multisig2_1", + } + } +} + +impl std::str::FromStr for MultisigType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "SafeMultisigWallet" => Self::SafeMultisigWallet, + "SafeMultisigWallet24h" => Self::SafeMultisigWallet24h, + "SetcodeMultisigWallet" => Self::SetcodeMultisigWallet, + "SetcodeMultisigWallet24h" => Self::SetcodeMultisigWallet24h, + "BridgeMultisigWallet" => Self::BridgeMultisigWallet, + "SurfWallet" => Self::SurfWallet, + "Multisig2" => Self::Multisig2, + "Multisig2_1" => Self::Multisig2_1, + _ => return Err(anyhow::anyhow!("Invalid multisig type")), + }) + } +} + +impl std::fmt::Display for MultisigType { + fn fmt(&self, f: &'_ mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + + impl MultisigType { pub fn is_multisig2(self) -> bool { matches!(self, Self::Multisig2 | Self::Multisig2_1) @@ -260,7 +299,7 @@ impl MultisigType { ) } - pub fn state_init(&self) -> StateInit { + pub fn state_init(&self) -> Result { use crate::utils::wallets; let state_init = match self { @@ -273,7 +312,7 @@ impl MultisigType { MultisigType::Multisig2 => wallets::code::multisig2(), MultisigType::Multisig2_1 => wallets::code::multisig2_1(), }; - StateInit::load_from(&mut state_init.as_slice()?).trust_me() + StateInit::load_from(&mut state_init.as_slice()?).map_err(Into::into) } pub fn code_hash(&self) -> &[u8; 32] { @@ -289,8 +328,8 @@ impl MultisigType { } } - pub fn code(&self) -> Cell { - self.state_init().code.trust_me() + pub fn code(&self) -> Result { + self.state_init()?.code.ok_or(UnpackerError::InvalidAbi.into()) } } @@ -346,7 +385,7 @@ pub fn compute_contract_address( multisig_type: MultisigType, workchain_id: i8, ) -> Result { - let state_init = prepare_state_init(public_key, multisig_type); + let state_init = prepare_state_init(public_key, multisig_type)?; let cell_builder = CellBuilder::build_from(&state_init)?; let hash = cell_builder.repr_hash(); Ok(StdAddr::new(workchain_id, *hash)) @@ -379,13 +418,13 @@ pub fn ton_wallet_details(multisig_type: MultisigType) -> TonWalletDetails { } } -pub fn prepare_state_init(public_key: &PublicKey, multisig_type: MultisigType) -> StateInit { - let mut state_init = multisig_type.state_init(); +pub fn prepare_state_init(public_key: &PublicKey, multisig_type: MultisigType) -> Result { + let mut state_init = multisig_type.state_init()?; let mut result = if state_init.data.is_none() { RawDict::new() } else { - state_init.data.parse::>()? + RawDict::<64>::from(state_init.data) }; let context = Cell::empty_context(); @@ -402,7 +441,7 @@ pub fn prepare_state_init(public_key: &PublicKey, multisig_type: MultisigType) - let cell = CellBuilder::build_from_ext(result, context)?; state_init.data = Some(cell); - state_init + Ok(state_init) } fn run_local( @@ -466,7 +505,7 @@ pub fn get_custodians( run_local(clock, function, account.into_owned()).and_then(parse_multisig_contract_custodians) } -fn parse_multisig_contract_custodians(tokens: Vec) -> Result> { +fn parse_multisig_contract_custodians(tokens: Vec) -> Result> { let array = match tokens.into_unpacker().unpack_next() { Ok(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), @@ -673,7 +712,7 @@ fn make_ext_message( ) -> Result { let external_input = function.encode_external(&input); let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; - let mut unsigned_message = unsigned_body.with_dst(address); + let unsigned_message = unsigned_body.with_dst(address); Ok(unsigned_message) } @@ -713,7 +752,7 @@ mod tests { .unwrap(); assert_eq!( - compute_contract_address(&key, MultisigType::SetcodeMultisigWallet24h, 0).to_string(), + compute_contract_address(&key, MultisigType::SetcodeMultisigWallet24h, 0).unwrap().to_string(), "0:3de70f9212154344a3158768b3fed731fc865ca15948b0d6d0d34daf4c6a7a0a" ); } diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index 5b3d27f..b454a44 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -6,7 +6,8 @@ use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes}, models::{ - Account, AccountState, CurrencyCollection, IntMsgInfo, Message, MsgInfo, StateInit, StdAddr, + Account, AccountState, CurrencyCollection, IntAddr, IntMsgInfo, Message, MsgInfo, + StateInit, StdAddr, }, }; use tycho_util::time::now_sec; diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index bbcb85f..e9e97aa 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -4,10 +4,10 @@ use anyhow::Result; use ed25519_dalek::PublicKey; use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, - cell::{Cell, CellBuilder, HashBytes, Lazy}, + cell::{Cell, CellBuilder, HashBytes, Lazy, Load}, models::{ Account, AccountState, CurrencyCollection, IntAddr, OutAction, OwnedRelaxedMessage, - RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, StdAddr, + RelaxedIntMsgInfo, RelaxedMsgInfo, SendMsgFlags, StateInit, StdAddr, }, }; @@ -52,7 +52,7 @@ pub fn make_init_data(public_key: &PublicKey) -> InitData { pub fn get_init_data(current_state: &Account, public_key: &PublicKey) -> Result<(InitData, bool)> { match current_state.state { - AccountState::Active(state_init) => match &state_init.data { + AccountState::Active(ref state_init) => match &state_init.data { Some(data) => Ok((InitData::try_from(data)?, false)), None => return Err(WalletV5Error::InvalidInitData.into()), }, @@ -78,8 +78,7 @@ pub fn prepare_transfer( if gifts.len() > MAX_MESSAGES { return Err(WalletV5Error::TooManyGifts.into()); } - let (mut init_data, with_state_init) = - get_init_data(current_state.storage.state(), public_key)?; + let (mut init_data, with_state_init) = get_init_data(current_state, public_key)?; init_data.seqno += seqno_offset; @@ -255,7 +254,7 @@ impl InitData { })?; let action = OutAction::SendMsg { - mode: gift.flags.into(), + mode: SendMsgFlags::from_bits_retain(gift.flags), out_msg: internal_message, }; @@ -312,6 +311,8 @@ enum WalletV5Error { SignaturesDisabled, #[error("Wallet locked")] WalletLocked, + #[error("Account address is not valid")] + InvalidAddress, } #[cfg(test)] @@ -335,18 +336,18 @@ mod tests { let state = Account::load_from(&mut account.as_slice()?)?; - if let AccountState::Active(state_init) = state_init.data { - let init_data = InitData::try_from(state_init.data().unwrap())?; + if let AccountState::Active(state_init) = state.state { + let init_data = InitData::try_from(&state_init.data.unwrap())?; assert_eq!(init_data.is_signature_allowed, true); assert_eq!( - init_data.public_key.to_hex_string(), + init_data.public_key.to_string(), "9107a65271437e1a982bb98404bd9a82c434f31ee30c621b6596702bb59bf0a0" ); assert_eq!(init_data.wallet_id, WALLET_ID); assert_eq!(init_data.extensions, None); let public_key = PublicKey::from_bytes(init_data.public_key.as_slice())?; - let address = compute_contract_address(&public_key, 0); + let address = compute_contract_address(&public_key, 0)?; assert_eq!( address.to_string(), "0:6ca35273892588b4c5f4ae898dc1983eec9662dffebeacdbe82103a1d1dcac60" diff --git a/src/utils/wallets/multisig.rs b/src/utils/wallets/multisig.rs index cd6098a..258c47a 100644 --- a/src/utils/wallets/multisig.rs +++ b/src/utils/wallets/multisig.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use tycho_types::{ - abi::{AbiType, Function}, + abi::{AbiType, Function, NamedAbiType, WithAbiType}, cell::{Cell, HashBytes}, models::StdAddr, }; @@ -17,7 +17,7 @@ pub fn constructor() -> &'static Function { AbiType::Array(Arc::new(AbiType::Uint(256))).named("owners"), AbiType::Uint(8).named("reqConfirms"), ], - outputs: Vec::new(), + outputs: vec![] as Vec, } } @@ -33,7 +33,7 @@ pub fn send_transaction() -> &'static Function { AbiType::Uint(8).named("flags"), AbiType::Cell.named("payload"), ], - outputs: Vec::new(), + outputs: vec![] as Vec, } } @@ -63,7 +63,7 @@ pub fn confirm_transaction() -> &'static Function { inputs: vec![ AbiType::Uint(64).named("transactionId"), ], - outputs: Vec::new(), + outputs: vec![] as Vec, } } @@ -82,14 +82,32 @@ pub struct MultisigTransaction { pub bounce: bool, } +impl WithAbiType for MultisigTransaction { + fn abi_type() -> AbiType { + AbiType::Tuple(Arc::new([ + AbiType::Uint(64).named("id"), + AbiType::Uint(32).named("confirmationMask"), + AbiType::Uint(8).named("signsRequired"), + AbiType::Uint(8).named("signsReceived"), + AbiType::Uint(256).named("creator"), + AbiType::Uint(8).named("index"), + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Uint(16).named("sendFlags"), + AbiType::Cell.named("payload"), + AbiType::Bool.named("bounce"), + ])) + } +} + pub fn get_transactions() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "getTransactions", - inputs: Vec::new(), + inputs: vec![] as Vec, outputs: vec![ - AbiType::Array(Arc::new(MultisigTransaction::param_type())).named("transactions") + AbiType::Array(Arc::new(MultisigTransaction::abi_type())).named("transactions") ] } } @@ -100,14 +118,23 @@ pub struct MultisigCustodian { pub pubkey: HashBytes, } +impl WithAbiType for MultisigCustodian { + fn abi_type() -> AbiType { + AbiType::Tuple(Arc::new([ + AbiType::Uint(8).named("index"), + AbiType::Uint(256).named("pubkey"), + ])) + } +} + pub fn get_custodians() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "getCustodians", - inputs: Vec::new(), + inputs: vec![] as Vec, outputs: vec![ - AbiType::Array(Arc::new(MultisigCustodian::param_type())).named("custodians") + AbiType::Array(Arc::new(MultisigCustodian::abi_type())).named("custodians") ] } @@ -125,13 +152,25 @@ pub mod safe_multisig { pub required_txn_confirms: u8, } + impl SafeMultisigParams { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(8).named("maxQueuedTransactions"), + AbiType::Uint(8).named("maxCustodianCount"), + AbiType::Uint(64).named("expirationTime"), + AbiType::Uint(128).named("minValue"), + AbiType::Uint(8).named("requiredTxnConfirms"), + ] + } + } + pub fn get_parameters() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "getParameters", - inputs: Vec::new(), - outputs: SafeMultisigParams::param_type(), + inputs: vec![] as Vec, + outputs: SafeMultisigParams::abi_type(), } } } @@ -149,13 +188,26 @@ pub mod set_code_multisig { pub required_upd_confirms: u8, } + impl SetCodeMultisigParams { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(8).named("maxQueuedTransactions"), + AbiType::Uint(8).named("maxCustodianCount"), + AbiType::Uint(64).named("expirationTime"), + AbiType::Uint(128).named("minValue"), + AbiType::Uint(8).named("requiredTxnConfirms"), + AbiType::Uint(8).named("requiredUpdConfirms"), + ] + } + } + pub fn get_parameters() -> &'static Function { declare_function! { abi: v2_0, header: [pubkey, time, expire], name: "getParameters", - inputs: Vec::new(), - outputs: SetCodeMultisigParams::param_type(), + inputs: vec![] as Vec, + outputs: SetCodeMultisigParams::abi_type(), } } } diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs index 6d2f407..ee2696c 100644 --- a/src/utils/wallets/multisig2.rs +++ b/src/utils/wallets/multisig2.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use tycho_types::{ - abi::{AbiType, Function}, + abi::{AbiType, Function, IntoAbi, NamedAbiType, NamedAbiValue, WithAbiType}, cell::{Cell, HashBytes}, models::StdAddr, }; @@ -18,7 +18,7 @@ pub fn constructor() -> &'static Function { AbiType::Uint(8).named("reqConfirms"), AbiType::Uint(32).named("lifetime"), ], - outputs: Vec::new(), + outputs: vec![] as Vec, } } @@ -34,7 +34,7 @@ pub fn send_transaction() -> &'static Function { AbiType::Uint(8).named("flags"), AbiType::Cell.named("payload"), ], - outputs: Vec::new(), + outputs: vec![] as Vec, } } @@ -65,7 +65,7 @@ pub fn confirm_transaction() -> &'static Function { inputs: vec![ AbiType::Uint(64).named("transactionId"), ], - outputs: Vec::new(), + outputs: vec![] as Vec, } } @@ -85,14 +85,34 @@ pub struct MultisigTransaction { pub state_init: Option, } + +impl WithAbiType for MultisigTransaction { + fn abi_type() -> AbiType { + AbiType::Tuple(Arc::new([ + AbiType::Uint(64).named("id"), + AbiType::Uint(32).named("confirmationMask"), + AbiType::Uint(8).named("signsRequired"), + AbiType::Uint(8).named("signsReceived"), + AbiType::Uint(256).named("creator"), + AbiType::Uint(8).named("index"), + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Uint(16).named("sendFlags"), + AbiType::Cell.named("payload"), + AbiType::Bool.named("bounce"), + AbiType::Optional(Arc::new(AbiType::Cell)).named("stateInit"), + ])) + } +} + pub fn get_transactions() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "getTransactions", - inputs: Vec::new(), + inputs: vec![] as Vec, outputs: vec![ - AbiType::Array(Arc::new(MultisigTransaction::param_type())).named("transactions") + AbiType::Array(Arc::new(MultisigTransaction::abi_type())).named("transactions") ] } } @@ -103,14 +123,23 @@ pub struct MultisigCustodian { pub pubkey: HashBytes, } +impl WithAbiType for MultisigCustodian { + fn abi_type() -> AbiType { + AbiType::Tuple(Arc::new([ + AbiType::Uint(8).named("index"), + AbiType::Uint(256).named("pubkey"), + ])) + } +} + pub fn get_custodians() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "getCustodians", - inputs: Vec::new(), + inputs: vec![] as Vec, outputs: vec![ - AbiType::Array(Arc::new(MultisigCustodian::param_type())).named("custodians") + AbiType::Array(Arc::new(MultisigCustodian::abi_type())).named("custodians") ] } } @@ -123,18 +152,46 @@ pub struct SubmitUpdateParams { pub lifetime: Option, } +impl SubmitUpdateParams { + fn abi_type() -> Vec { + vec![ + AbiType::Optional(Arc::new(AbiType::Uint(256))).named("codeHash"), + AbiType::Optional(Arc::new(AbiType::Array(Arc::new(AbiType::Uint(256))))).named("owners"), + AbiType::Optional(Arc::new(AbiType::Uint(8))).named("reqConfirms"), + AbiType::Optional(Arc::new(AbiType::Uint(64))).named("lifetime"), + ] + } + + pub fn abi_values(&self) -> Vec { + vec![ + self.code_hash.as_abi().named("codeHash"), + self.owners.as_abi().named("owners"), + self.req_confirms.as_abi().named("reqConfirms"), + self.lifetime.as_abi().named("lifetime"), + ] + } +} + #[derive(Debug, Copy, Clone)] pub struct SubmitUpdateOutput { pub update_id: u64, } +impl SubmitUpdateOutput { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(64).named("updateId"), + ] + } +} + pub fn submit_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "submitUpdate", - inputs: SubmitUpdateParams::param_type(), - outputs: SubmitUpdateOutput::param_type(), + inputs: SubmitUpdateParams::abi_type(), + outputs: SubmitUpdateOutput::abi_type(), } } @@ -143,13 +200,27 @@ pub struct ConfirmUpdateParams { pub update_id: u64, } +impl ConfirmUpdateParams { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(64).named("updateId"), + ] + } + + pub fn abi_values(&self) -> Vec { + vec![ + self.update_id.as_abi().named("updateId"), + ] + } +} + pub fn confirm_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "confirmUpdate", - inputs: ConfirmUpdateParams::param_type(), - outputs: Vec::new(), + inputs: ConfirmUpdateParams::abi_type(), + outputs: vec![] as Vec, } } @@ -159,13 +230,30 @@ pub struct ExecuteUpdateParams { pub code: Option, } +impl ExecuteUpdateParams { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(64).named("updateId"), + AbiType::Optional(Arc::new(AbiType::Cell)).named("code"), + ] + } + + pub fn abi_values(&self) -> Vec { + vec![ + self.update_id.as_abi().named("updateId"), + self.code.as_abi().named("code"), + ] + } + +} + pub fn execute_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "executeUpdate", - inputs: ExecuteUpdateParams::param_type(), - outputs: Vec::new(), + inputs: ExecuteUpdateParams::abi_type(), + outputs: vec![] as Vec, } } @@ -179,13 +267,25 @@ pub struct SetCodeMultisigParams { pub required_upd_confirms: u8, } +impl SetCodeMultisigParams { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(8).named("maxQueuedTransactions"), + AbiType::Uint(8).named("maxCustodianCount"), + AbiType::Uint(64).named("expirationTime"), + AbiType::Uint(128).named("minValue"), + AbiType::Uint(8).named("requiredTxnConfirms"), + AbiType::Uint(8).named("requiredUpdConfirms"), + ] + } +} pub fn get_parameters() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "getParameters", - inputs: Vec::new(), - outputs: SetCodeMultisigParams::param_type(), + inputs: vec![] as Vec, + outputs: SetCodeMultisigParams::abi_type(), } } @@ -199,37 +299,24 @@ pub struct UpdateTransaction { pub new_code_hash: Option, pub new_custodians: Option>, pub new_req_confirms: Option, - //#[abi(with = "updated_lifetime")] pub new_lifetime: Option, } -//mod updated_lifetime { -// use super::*; -// use num_traits::cast::ToPrimitive; -// -// pub fn unpack(value: &TokenValue) -> UnpackerResult> { -// let value = match value { -// TokenValue::Optional(_, None) => return Ok(None), -// TokenValue::Optional(_, Some(value)) => value, -// _ => return Err(UnpackerError::InvalidAbi), -// }; -// -// match value.as_ref() { -// TokenValue::Uint(Uint { number, size: 32 }) => { -// Ok(Some(number.to_u32().ok_or(UnpackerError::InvalidAbi)?)) -// } -// TokenValue::Uint(Uint { number, size: 64 }) => { -// let lifetime = number.to_u64().ok_or(UnpackerError::InvalidAbi)?; -// Ok(Some(lifetime as u32)) -// } -// _ => Err(UnpackerError::InvalidAbi), -// } -// } -// -// pub fn param_type() -> ParamType { -// Option::::param_type() -// } -//} +impl WithAbiType for UpdateTransaction { + fn abi_type() -> AbiType { + AbiType::Tuple(Arc::new([ + AbiType::Uint(64).named("id"), + AbiType::Uint(8).named("index"), + AbiType::Uint(8).named("signs"), + AbiType::Uint(32).named("confirmationsMask"), + AbiType::Uint(256).named("creator"), + AbiType::Optional(Arc::new(AbiType::Uint(256))).named("newCodeHash"), + AbiType::Optional(Arc::new(AbiType::Array(Arc::new(AbiType::Uint(256))))).named("newCustodians"), + AbiType::Optional(Arc::new(AbiType::Uint(8))).named("newReqConfirms"), + AbiType::Optional(Arc::new(AbiType::Uint(32))).named("newLifetime"), + ])) + } +} pub mod v2_0 { use tycho_types::abi::NamedAbiType; @@ -241,19 +328,22 @@ pub mod v2_0 { abi: v2_3, header: [pubkey, time, expire], name: "getUpdateRequests", - inputs: Vec::new(), + inputs: vec![] as Vec, outputs: { - let mut param_types = UpdateTransaction::param_type(); - if let AbiType::Tuple(params) = &mut param_types { - if let Some(NamedAbiType { - ty: AbiType::Optional(param), - .. - }) = params.last_mut() { - if let AbiType::Uint(size) = param.as_mut() { - *size = 64; + let param_types = match UpdateTransaction::abi_type() { + AbiType::Tuple(params) => { + let mut vec = params.iter().cloned().collect::>(); + if let Some(last) = vec.last_mut() { + if let NamedAbiType { ty: AbiType::Optional(param), name } = last { + if let AbiType::Uint(_) = param.as_ref() { + *last = AbiType::Optional(Arc::new(AbiType::Uint(64))).named(&*name.clone()); + } + } } + AbiType::Tuple(Arc::<[NamedAbiType]>::from(vec)) } - } + other => other, + }; vec![ AbiType::Array(Arc::new(param_types)).named("updates") @@ -271,9 +361,9 @@ pub mod v2_1 { abi: v2_3, header: [pubkey, time, expire], name: "getUpdateRequests", - inputs: Vec::new(), + inputs: vec![] as Vec, outputs: vec![ - AbiType::Array(Arc::new(UpdateTransaction::param_type())).named("updates") + AbiType::Array(Arc::new(UpdateTransaction::abi_type())).named("updates") ], } } From 87b7772ab27a03f98571eb909586a48f570d30cd Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 28 Jan 2026 16:46:18 +0300 Subject: [PATCH 10/59] fix errors --- src/utils/mod.rs | 6 +- src/utils/ton_wallet/mod.rs | 5 +- src/utils/ton_wallet/multisig.rs | 149 ++++++++++++++++++++++++------- src/utils/wallets/multisig.rs | 37 ++------ src/utils/wallets/multisig2.rs | 42 +++------ 5 files changed, 141 insertions(+), 98 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index bc16178..0488e2f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -46,8 +46,8 @@ macro_rules! declare_function { ONCE.get_or_init(|| { let mut builder = tycho_types::abi::Function::builder($crate::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) .with_headers($crate::utils::declare_function!(@header $($($header),+)?)) - .with_inputs($inputs) - .with_outputs($outputs); + .with_inputs($inputs as Vec) + .with_outputs($outputs as Vec); $crate::utils::declare_function!(@function_id builder $($id)?); @@ -56,7 +56,7 @@ macro_rules! declare_function { }) }; - (@function_id $builder:ident $id:literal) => { $builder.with_id($id) }; + (@function_id $builder:ident $id:literal) => { $builder = $builder.with_id($id) }; (@function_id $builder:ident ) => {}; (@abi_version) => { tycho_types::abi::AbiVersion::V2_2 }; diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs index 55fbed8..84fd640 100644 --- a/src/utils/ton_wallet/mod.rs +++ b/src/utils/ton_wallet/mod.rs @@ -6,6 +6,7 @@ use ed25519_dalek::PublicKey; use serde::{Deserialize, Serialize}; use nekoton_utils::*; +use tycho_types::abi::FromAbi; use tycho_types::cell::{Cell, HashBytes}; use tycho_types::models::{StateInit, StdAddr}; @@ -221,7 +222,7 @@ pub fn compute_address( } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, FromAbi)] pub struct MultisigPendingTransaction { pub id: u64, pub confirmations: Vec, @@ -236,7 +237,7 @@ pub struct MultisigPendingTransaction { pub bounce: bool, } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, FromAbi)] pub struct MultisigPendingUpdate { pub id: u64, pub confirmations: Vec, diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index 863d5e1..51c4383 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -4,7 +4,7 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::PublicKey; use tycho_types::{ - abi::{AbiValue, Function, IntoAbi, NamedAbiValue, UnsignedExternalMessage}, + abi::{AbiValue, FromAbi, Function, IntoAbi, NamedAbiValue, UnsignedExternalMessage}, cell::{Cell, CellBuilder, CellDataBuilder, CellFamily, HashBytes, Load}, dict::RawDict, models::{Account, StateInit, StdAddr}, @@ -61,14 +61,20 @@ pub fn prepare_deploy( }; let mut abi_values = vec![ - owners.as_abi().named("owners"), + owners.as_abi().named("owners"), params.req_confirms.as_abi().named("reqConfirms"), ]; if is_new_multisig { - abi_values.push(params.expiration_time.unwrap_or(DEFAULT_LIFETIME).as_abi().named("lifetime")); + abi_values.push( + params + .expiration_time + .unwrap_or(DEFAULT_LIFETIME) + .as_abi() + .named("lifetime"), + ); } - let external_input = function.encode_external(&abi_values); + let external_input = function.encode_external(&abi_values); let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; let mut unsigned_message = unsigned_body.with_dst(dst); @@ -94,8 +100,7 @@ pub fn prepare_confirm_transaction( address, expire_at, function, - vec![ - transaction_id.as_abi().named("transactionId")], + vec![transaction_id.as_abi().named("transactionId")], ) } @@ -134,8 +139,10 @@ pub fn prepare_transfer( named_abi_values.push( gift.state_init .map(|state_init| CellBuilder::build_from(&state_init)) - .transpose()?.as_abi().named("stateInit"), -); + .transpose()? + .as_abi() + .named("stateInit"), + ); } (function, named_abi_values) } else { @@ -282,7 +289,6 @@ impl std::fmt::Display for MultisigType { } } - impl MultisigType { pub fn is_multisig2(self) -> bool { matches!(self, Self::Multisig2 | Self::Multisig2_1) @@ -329,7 +335,9 @@ impl MultisigType { } pub fn code(&self) -> Result { - self.state_init()?.code.ok_or(UnpackerError::InvalidAbi.into()) + self.state_init()? + .code + .ok_or(UnpackerError::InvalidAbi.into()) } } @@ -418,7 +426,10 @@ pub fn ton_wallet_details(multisig_type: MultisigType) -> TonWalletDetails { } } -pub fn prepare_state_init(public_key: &PublicKey, multisig_type: MultisigType) -> Result { +pub fn prepare_state_init( + public_key: &PublicKey, + multisig_type: MultisigType, +) -> Result { let mut state_init = multisig_type.state_init()?; let mut result = if state_init.data.is_none() { @@ -466,6 +477,44 @@ pub struct MultisigParamsPrefix { pub required_confirms: u8, } +impl TryFrom> for MultisigParamsPrefix { + fn try_from(params: Vec) -> std::result::Result { + let mut params_iter = params.into_iter(); + + let Some(AbiValue::Uint(8, max_queued_transactions)) = params_iter.next().map(|v| v.value) + else { + return Err(MultisigError::InvalidParams.into()); + }; + + let Some(AbiValue::Uint(8, max_custodian_count)) = params_iter.next().map(|v| v.value) + else { + return Err(MultisigError::InvalidParams.into()); + }; + + let Some(AbiValue::Uint(64, expiration_time)) = params_iter.next().map(|v| v.value) else { + return Err(MultisigError::InvalidParams.into()); + }; + + let Some(AbiValue::Uint(128, min_value)) = params_iter.next().map(|v| v.value) else { + return Err(MultisigError::InvalidParams.into()); + }; + + let Some(AbiValue::Uint(8, required_confirms)) = params_iter.next().map(|v| v.value) else { + return Err(MultisigError::InvalidParams.into()); + }; + + Ok(MultisigParamsPrefix { + max_queued_transactions: u8::try_from(max_queued_transactions)?, + max_custodian_count: u8::try_from(max_custodian_count)?, + expiration_time: u64::try_from(expiration_time)?, + min_value: u128::try_from(min_value)?, + required_confirms: u8::try_from(required_confirms)?, + }) + } + + type Error = anyhow::Error; +} + pub fn get_params( clock: &dyn Clock, multisig_type: MultisigType, @@ -487,9 +536,8 @@ pub fn get_params( } }; - let output: MultisigParamsPrefix = - run_local(clock, function, account.into_owned())?.unpack()?; - Ok(output) + let output = run_local(clock, function, account.into_owned())?; + MultisigParamsPrefix::try_from(output) } pub fn get_custodians( @@ -506,14 +554,14 @@ pub fn get_custodians( } fn parse_multisig_contract_custodians(tokens: Vec) -> Result> { - let array = match tokens.into_unpacker().unpack_next() { - Ok(AbiValue::Array(_, tokens)) => tokens, + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; let mut custodians = array .into_iter() - .map(|item| item.unpack()) + .map(crate::utils::wallets::multisig::MultisigCustodian::from_abi) .collect::, _>>()?; custodians.sort_by(|a, b| a.index.cmp(&b.index)); @@ -532,6 +580,26 @@ pub fn find_pending_transaction( pub id: u64, } + impl TryFrom for MultisigTransactionId { + fn try_from(params: AbiValue) -> std::result::Result { + let AbiValue::Tuple(params) = params else { + return Err(anyhow::anyhow!("Invalid params")); + }; + + let mut params_iter = params.into_iter(); + + let Some(AbiValue::Uint(64, index)) = params_iter.next().map(|v| v.value) else { + return Err(anyhow::anyhow!("Invalid params")); + }; + + Ok(MultisigTransactionId { + id: u64::try_from(index)?, + }) + } + + type Error = anyhow::Error; + } + let function = if multisig_type.is_multisig2() { crate::utils::wallets::multisig2::get_transactions() } else { @@ -540,14 +608,14 @@ pub fn find_pending_transaction( let tokens = run_local(clock, function, account.into_owned())?; - let array = match tokens.into_unpacker().unpack_next() { - Ok(AbiValue::Array(_, tokens)) => tokens, + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; for item in array { - let MultisigTransactionId { id } = item.unpack()?; - if pending_transaction_id == id { + let m = MultisigTransactionId::try_from(item)?; + if pending_transaction_id == m.id { return Ok(true); } } @@ -570,13 +638,13 @@ pub fn find_pending_update( let tokens = run_local(clock, function, account.into_owned())?; - let array = match tokens.into_unpacker().unpack_next() { - Ok(AbiValue::Array(_, tokens)) => tokens, + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; for item in array { - let update: multisig2::UpdateTransaction = item.unpack()?; + let update = multisig2::UpdateTransaction::from_abi(item)?; if update_id == update.id { return Ok(Some(UpdatedParams { new_code_hash: update.new_code_hash, @@ -598,6 +666,7 @@ pub struct UpdatedParams { pub new_lifetime: Option, } + pub fn get_pending_transactions( clock: &dyn Clock, multisig_type: MultisigType, @@ -610,15 +679,20 @@ pub fn get_pending_transactions( crate::utils::wallets::multisig::get_transactions() }; run_local(clock, function, account.into_owned()).and_then(|tokens| { - let array = match tokens.into_unpacker().unpack_next() { - Ok(AbiValue::Array(_, tokens)) => tokens, + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; let transactions = array .into_iter() - .map(|item| Ok(extend_pending_transaction(item.unpack()?, custodians))) - .collect::>>()?; + .map(|item| { + Ok(extend_pending_transaction( + crate::utils::wallets::multisig::MultisigTransaction::from_abi(item)?, + custodians, + )) + }) + .collect::>>()?; Ok(transactions) }) @@ -639,15 +713,20 @@ pub fn get_pending_updates( }; run_local(clock, function, account.into_owned()).and_then(|tokens| { - let array = match tokens.into_unpacker().unpack_next() { - Ok(AbiValue::Array(_, tokens)) => tokens, + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), }; let updates = array .into_iter() - .map(|item| Ok(extend_pending_update(item.unpack()?, custodians))) - .collect::>>()?; + .map(|item| { + Ok(extend_pending_update( + crate::utils::wallets::multisig2::UpdateTransaction::from_abi(item)?, + custodians, + )) + }) + .collect::>>()?; Ok(updates) }) @@ -729,6 +808,8 @@ enum MultisigError { CustomExpirationTimeNotSupported, #[error("Update is not supported or not implemented for this contract type")] UnsupportedUpdate, + #[error("Invalid params")] + InvalidParams, } pub type UnpackerResult = Result; @@ -752,7 +833,9 @@ mod tests { .unwrap(); assert_eq!( - compute_contract_address(&key, MultisigType::SetcodeMultisigWallet24h, 0).unwrap().to_string(), + compute_contract_address(&key, MultisigType::SetcodeMultisigWallet24h, 0) + .unwrap() + .to_string(), "0:3de70f9212154344a3158768b3fed731fc865ca15948b0d6d0d34daf4c6a7a0a" ); } diff --git a/src/utils/wallets/multisig.rs b/src/utils/wallets/multisig.rs index 258c47a..0920834 100644 --- a/src/utils/wallets/multisig.rs +++ b/src/utils/wallets/multisig.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use tycho_types::{ - abi::{AbiType, Function, NamedAbiType, WithAbiType}, + abi::{AbiType, FromAbi, Function, IntoAbi, NamedAbiType, WithAbiType}, cell::{Cell, HashBytes}, models::StdAddr, }; @@ -67,39 +67,25 @@ pub fn confirm_transaction() -> &'static Function { } } -#[derive(Debug)] +#[derive(IntoAbi, FromAbi, WithAbiType, Eq, PartialEq, Debug, Clone)] pub struct MultisigTransaction { pub id: u64, + #[abi(name = "confirmationsMask")] pub confirmation_mask: u32, + #[abi(name = "signsRequired")] pub signs_required: u8, + #[abi(name = "signsReceived")] pub signs_received: u8, pub creator: HashBytes, pub index: u8, pub dest: StdAddr, pub value: u128, + #[abi(name = "sendFlags")] pub send_flags: u16, pub payload: Cell, pub bounce: bool, } -impl WithAbiType for MultisigTransaction { - fn abi_type() -> AbiType { - AbiType::Tuple(Arc::new([ - AbiType::Uint(64).named("id"), - AbiType::Uint(32).named("confirmationMask"), - AbiType::Uint(8).named("signsRequired"), - AbiType::Uint(8).named("signsReceived"), - AbiType::Uint(256).named("creator"), - AbiType::Uint(8).named("index"), - AbiType::Address.named("dest"), - AbiType::Uint(128).named("value"), - AbiType::Uint(16).named("sendFlags"), - AbiType::Cell.named("payload"), - AbiType::Bool.named("bounce"), - ])) - } -} - pub fn get_transactions() -> &'static Function { declare_function! { abi: v2_0, @@ -112,21 +98,12 @@ pub fn get_transactions() -> &'static Function { } } -#[derive(Debug, Copy, Clone)] +#[derive(IntoAbi, FromAbi, WithAbiType, Eq, PartialEq, Debug, Clone)] pub struct MultisigCustodian { pub index: u8, pub pubkey: HashBytes, } -impl WithAbiType for MultisigCustodian { - fn abi_type() -> AbiType { - AbiType::Tuple(Arc::new([ - AbiType::Uint(8).named("index"), - AbiType::Uint(256).named("pubkey"), - ])) - } -} - pub fn get_custodians() -> &'static Function { declare_function! { abi: v2_0, diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs index ee2696c..d27f31c 100644 --- a/src/utils/wallets/multisig2.rs +++ b/src/utils/wallets/multisig2.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use tycho_types::{ - abi::{AbiType, Function, IntoAbi, NamedAbiType, NamedAbiValue, WithAbiType}, + abi::{AbiType, FromAbi, Function, IntoAbi, NamedAbiType, NamedAbiValue, WithAbiType}, cell::{Cell, HashBytes}, models::StdAddr, }; @@ -85,7 +85,6 @@ pub struct MultisigTransaction { pub state_init: Option, } - impl WithAbiType for MultisigTransaction { fn abi_type() -> AbiType { AbiType::Tuple(Arc::new([ @@ -156,7 +155,8 @@ impl SubmitUpdateParams { fn abi_type() -> Vec { vec![ AbiType::Optional(Arc::new(AbiType::Uint(256))).named("codeHash"), - AbiType::Optional(Arc::new(AbiType::Array(Arc::new(AbiType::Uint(256))))).named("owners"), + AbiType::Optional(Arc::new(AbiType::Array(Arc::new(AbiType::Uint(256))))) + .named("owners"), AbiType::Optional(Arc::new(AbiType::Uint(8))).named("reqConfirms"), AbiType::Optional(Arc::new(AbiType::Uint(64))).named("lifetime"), ] @@ -179,9 +179,7 @@ pub struct SubmitUpdateOutput { impl SubmitUpdateOutput { fn abi_type() -> Vec { - vec![ - AbiType::Uint(64).named("updateId"), - ] + vec![AbiType::Uint(64).named("updateId")] } } @@ -202,15 +200,11 @@ pub struct ConfirmUpdateParams { impl ConfirmUpdateParams { fn abi_type() -> Vec { - vec![ - AbiType::Uint(64).named("updateId"), - ] + vec![AbiType::Uint(64).named("updateId")] } pub fn abi_values(&self) -> Vec { - vec![ - self.update_id.as_abi().named("updateId"), - ] + vec![self.update_id.as_abi().named("updateId")] } } @@ -244,7 +238,6 @@ impl ExecuteUpdateParams { self.code.as_abi().named("code"), ] } - } pub fn execute_update() -> &'static Function { @@ -289,35 +282,24 @@ pub fn get_parameters() -> &'static Function { } } -#[derive(Debug, Clone)] +#[derive(IntoAbi, FromAbi, WithAbiType, Eq, PartialEq, Debug, Clone)] pub struct UpdateTransaction { pub id: u64, pub index: u8, pub signs: u8, + #[abi(name = "confirmationsMask")] pub confirmations_mask: u32, pub creator: HashBytes, + #[abi(name = "newCodeHash")] pub new_code_hash: Option, + #[abi(name = "newCustodians")] pub new_custodians: Option>, + #[abi(name = "newReqConfirms")] pub new_req_confirms: Option, + #[abi(name = "newLifetime")] pub new_lifetime: Option, } -impl WithAbiType for UpdateTransaction { - fn abi_type() -> AbiType { - AbiType::Tuple(Arc::new([ - AbiType::Uint(64).named("id"), - AbiType::Uint(8).named("index"), - AbiType::Uint(8).named("signs"), - AbiType::Uint(32).named("confirmationsMask"), - AbiType::Uint(256).named("creator"), - AbiType::Optional(Arc::new(AbiType::Uint(256))).named("newCodeHash"), - AbiType::Optional(Arc::new(AbiType::Array(Arc::new(AbiType::Uint(256))))).named("newCustodians"), - AbiType::Optional(Arc::new(AbiType::Uint(8))).named("newReqConfirms"), - AbiType::Optional(Arc::new(AbiType::Uint(32))).named("newLifetime"), - ])) - } -} - pub mod v2_0 { use tycho_types::abi::NamedAbiType; From df308a3ac70c0cad4072920e2f1df379d3061630 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 29 Jan 2026 10:53:50 +0300 Subject: [PATCH 11/59] fix multisig test --- src/utils/ton_wallet/multisig.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index 51c4383..a623614 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -5,7 +5,7 @@ use anyhow::Result; use ed25519_dalek::PublicKey; use tycho_types::{ abi::{AbiValue, FromAbi, Function, IntoAbi, NamedAbiValue, UnsignedExternalMessage}, - cell::{Cell, CellBuilder, CellDataBuilder, CellFamily, HashBytes, Load}, + cell::{Cell, CellBuilder, CellDataBuilder, HashBytes, Load}, dict::RawDict, models::{Account, StateInit, StdAddr}, }; @@ -432,24 +432,22 @@ pub fn prepare_state_init( ) -> Result { let mut state_init = multisig_type.state_init()?; - let mut result = if state_init.data.is_none() { - RawDict::new() - } else { - RawDict::<64>::from(state_init.data) - }; + let state_init_data = state_init.data.unwrap_or_default(); - let context = Cell::empty_context(); - let mut key_builder = CellDataBuilder::new(); + let state_init_data_slice = state_init_data.as_slice()?; + + let cell = state_init_data_slice.get_reference_cloned(0).ok(); + let mut result = RawDict::<64>::from(cell); + + let mut key_builder = CellDataBuilder::new(); key_builder.store_u64(0)?; - result.set_ext( - key_builder.as_data_slice(), - &CellBuilder::from_raw_data(public_key.as_bytes(), 256)?.as_data_slice(), - context, - )?; + let cell_builder = CellBuilder::from_raw_data(public_key.as_bytes(), 256)?; + let data_slice = cell_builder.as_data_slice(); + result.set(key_builder.as_data_slice(), &data_slice)?; // Encode init data as mapping - let cell = CellBuilder::build_from_ext(result, context)?; + let cell = CellBuilder::build_from(result)?; state_init.data = Some(cell); Ok(state_init) @@ -666,7 +664,6 @@ pub struct UpdatedParams { pub new_lifetime: Option, } - pub fn get_pending_transactions( clock: &dyn Clock, multisig_type: MultisigType, From c33f3ba9534eb906913c620a830984cd93d79a17 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 29 Jan 2026 11:13:41 +0300 Subject: [PATCH 12/59] fix highload wallet test --- src/utils/ton_wallet/highload_wallet_v2.rs | 24 +++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index f19b006..15e37d6 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -63,9 +63,9 @@ pub fn prepare_transfer( ), }; - // if init_data.data.len()? >= 500_usize { - // return Err(HighloadWalletV2Error::InitDataTooLarge.into()); - // } + if init_data.data.values().count() >= 500_usize { + return Err(HighloadWalletV2Error::InitDataTooLarge.into()); + } let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at)?; let unsigned_body = UnsignedBody { @@ -289,30 +289,26 @@ enum HighloadWalletV2Error { #[cfg(test)] pub mod tests { use anyhow::Result; - use tycho_types::{ - boc::Boc, - cell::Load, - models::{Account, AccountState}, - }; + use tycho_types::{boc::Boc, cell::Load, models::AccountState}; use crate::utils::ton_wallet::highload_wallet_v2::InitData; - #[tokio::test] - async fn check_state() -> Result<()> { + #[test] + fn check_state() -> Result<()> { let account_base64 = "te6ccgICCBAAAQAAOOsAAAIBmggHAAEBWQAAAABij1ipvO9y33lafOQTY2Zjcpu/tM7FomMbSFDp4+8Ei8aUcpwDouTBwAACAgiLsUesAl4AAwIBYgA1AAQCAnAAFAAFAgFIABEABgIBIAAKAAcCAW4ACQAIAAm3P2/0YAAJty45OOACASAADAALAAm7gCmMCAIBIAAQAA0CASAADwAOAAm3SNaR4AAJt2IEcmAACbn59J8wAgEgABMAEgAJvH0s0sQACb0fFP0kAgEgACYAFQIBIAAfABYCASAAGgAXAgEgABkAGAAJulOvR9gACbr90p9IAgFYABwAGwAJuaPhPtACASAAHgAdAAm29FO6oAAJt4e9WeACASAAJQAgAgEgACQAIQIBIAAjACIACbg4HOPQAAm5N8GT0AAJumQHj3gACbxuQUbMAgEgACwAJwIBIAApACgACbxoeVqsAgEgACsAKgAJu3kh5AgACbuT7z2oAgEgADIALQIBIAAxAC4CAUgAMAAvAAm3D/yF4AAJttuTf6AACboR+FvYAgEgADQAMwAJu/xtoCgACbswljEoAgEgAVEANgIBIADGADcCASAAfQA4AgEgAFwAOQIBIABLADoCASAASgA7AgEgAD8APAIBIAA+AD0ACbv67XUoAAm728BYuAIBIABDAEACASAAQgBBAAm4XHbOEAAJuEX0C9ACASAASQBEAgEgAEgARQIBSABHAEYACLJFqpcACLIZDQgACbb/vM5gAAm5tBZx8AAJv6qZvYYCASAAVQBMAgEgAFIATQIBWABRAE4CAWYAUABPAAizva6wAAizJMbvAAm4yrfY0AIBIABUAFMACbv97cQIAAm7rFAKCAIBIABXAFYACbwdUD3MAgEgAFkAWAAJuqMTi4gCA5B3AFsAWgAHqXo6sAAHqTSr0AIBIABsAF0CASAAYwBeAgEgAGIAXwICcQBhAGAACbU816HAAAm0goAhwAAJvMcETxwCASAAawBkAgEgAGoAZQIBIABnAGYACbjQvmEQAgJxAGkAaAAHsBsUkQAHsGq37wAJu6lVp4gACb01zX/cAgEgAHYAbQIBIABzAG4CASAAcABvAAm6QVAyuAIBSAByAHEACbd6cIWgAAm2kdMmYAIBIAB1AHQACbotzlT4AAm66wR8CAIBIAB8AHcCASAAewB4AgEgAHoA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trax3CACASADAgMBAAm7WoeimAIBIAMEAwMACbm8yrWwAAm5HOVjEAIBSAMHAwYACbpktfloAAm7LsDquAIBIAMUAwkCAVgDCwMKAAm6hR6B6AIBIAMPAwwCASADDgMNAAm2f4hroAAJtllCn+ACASADEQMQAAm2Yx6y4AIBIAMTAxIACbSflolAAAm0QerswAIBIAMYAxUCASADFwMWAAm7tkoeSAAJuknHOugACb2TEThEAgEgAzkDGgIBIAMoAxsCASADIQMcAgEgAyADHQIBIAMfAx4ACbuaE9GIAAm76TwyuAAJvTF5SlwCASADJQMiAgFmAyQDIwAJt+MXWCAACbfdIXagAgEgAycDJgAJu+jFS4gACbqhfTLYAgEgAzIDKQIBIAMvAyoCAVgDLgMrAgFYAy0DLAAJtOPh2kAACbTQbPDAAAm5hunfsAIBIAMxAzAACbqF6XDoAAm7UeL9mAIBIAM2AzMCASADNQM0AAm7xZq5KAAJuxrvMUgCASADOAM3AAm7OY2quAAJumXrW7gCASADSwM6AgEgA0YDOwIBIANBAzwCASADPgM9AAm7wUzueAIBIANAAz8ACbmxiDdwAAm5KDyKEAIBIANDA0IACbupeOFIAgEgA0UDRAAJuNLLTRAACbir+oZQAgEgA0oDRwIBIANJA0gACbonQw4YAAm6iJr9KAAJvAwRrswCASADVQNMAgEgA1QDTQIBIANPA04ACbo2gb2IAgFqA1EDUAAJtAxQcEACAVgDUwNSAAex5r87AAex7i7vAAm8a0P6FAIBIANXA1YACb0WUg0sAgEgA1kDWAAJupoiEwgACbsZNqNoAgEgA94DWwIBIAObA1wCASADeANdAgEgA3EDXgIBIANmA18CASADYwNgAgEgA2IDYQAJu3UoScgACbtyxfUoAgEgA2UDZAAJu7/lGDgACbugeIXoAgEgA24DZwIBIANrA2gCASADagNpAAm5vLA10AAJubPahdACASADbQNsAAm5tK+QUAAJuEioGPACAUgDcANvAAm5CURc8AAJuCyslfACAUgDcwNyAAm8IYMwTAIBSAN3A3QCASADdgN1AAm3DbyIoAAJti1cfKAACbgyOlvwAgEgA4oDeQIBIAOBA3oCASADgAN7AgEgA30DfAAJu0UfkFgCASADfwN+AAm4pvk5sAAJua8OZHAA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"; let account = Boc::decode_base64(account_base64)?; - let state = Account::load_from(&mut account.as_slice()?)?; + let state = AccountState::load_from(&mut account.as_slice()?)?; - let data = match state.state { + let data = match state { AccountState::Active(state_init) => state_init.data.unwrap(), _ => anyhow::bail!("ACCOUNT NOT ACTIVE"), }; - let _init_data = InitData::try_from(&data).expect("init data failed"); + let init_data = InitData::try_from(&data).expect("init data failed"); - //println!("{:?}", init_data.data.len().unwrap()); + println!("{:?}", init_data.data.values().count()); Ok(()) } From cc5a04e22fb6420345cbaa3ef602c59e16dfeff6 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 29 Jan 2026 11:23:09 +0300 Subject: [PATCH 13/59] fix wallet v5 test --- src/utils/ton_wallet/wallet_v5r1.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index e9e97aa..feb0295 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -202,7 +202,7 @@ impl InitData { builder.store_bit_one()?; builder.store_reference(extensions.clone())?; } else { - builder.store_bit_one()?; + builder.store_bit_zero()?; } let data = builder.build()?; @@ -283,7 +283,7 @@ impl TryFrom<&Cell> for InitData { let seqno = slice.load_u32()?; let wallet_id = slice.load_u32()?; let mut buffer = [0u8; 32]; - slice.load_raw(&mut buffer, 32)?; + slice.load_raw(&mut buffer, 256)?; let public_key = HashBytes::from_slice(&buffer); let extensions = Option::::load_from(&mut slice)?; From 53f91dfe0fa14c5e11f5610b1352110160b85215 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 2 Feb 2026 10:11:36 +0300 Subject: [PATCH 14/59] fix macros --- src/utils/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 0488e2f..9c4ed45 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -44,7 +44,7 @@ macro_rules! declare_function { ) => { static ONCE: std::sync::OnceLock = std::sync::OnceLock::new(); ONCE.get_or_init(|| { - let mut builder = tycho_types::abi::Function::builder($crate::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) + let builder = tycho_types::abi::Function::builder($crate::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) .with_headers($crate::utils::declare_function!(@header $($($header),+)?)) .with_inputs($inputs as Vec) .with_outputs($outputs as Vec); @@ -56,7 +56,7 @@ macro_rules! declare_function { }) }; - (@function_id $builder:ident $id:literal) => { $builder = $builder.with_id($id) }; + (@function_id $builder:ident $id:literal) => { let $builder = $builder.with_id($id); }; (@function_id $builder:ident ) => {}; (@abi_version) => { tycho_types::abi::AbiVersion::V2_2 }; From 15b0fdf8a5400ce2bdfb752ae583a5c04b3043d5 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 2 Feb 2026 14:43:56 +0300 Subject: [PATCH 15/59] start wallet impl --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/client/ton/mod.rs | 243 +++++++++++++--------------- src/models/address.rs | 8 +- src/models/existing_contract.rs | 8 + src/models/mod.rs | 2 + src/models/states_cache.rs | 69 -------- src/services/ton.rs | 2 +- src/ton_core/mod.rs | 14 +- src/ton_core/ton_subscriber/mod.rs | 36 ++--- src/utils/mnemonic.rs | 239 +++++++++++++++++++++++++++ src/utils/mod.rs | 3 +- src/utils/shard_utils.rs | 3 +- src/utils/token_wallet.rs | 3 +- src/utils/ton_wallet/ever_wallet.rs | 1 - src/utils/tx_context.rs | 3 +- 16 files changed, 392 insertions(+), 246 deletions(-) create mode 100644 src/models/existing_contract.rs delete mode 100644 src/models/states_cache.rs create mode 100644 src/utils/mnemonic.rs diff --git a/Cargo.lock b/Cargo.lock index b12972c..9aabe14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5148,7 +5148,7 @@ dependencies = [ "clap", "dashmap 5.5.3", "derive_more", - "ed25519-dalek 1.0.1", + "ed25519-dalek 2.2.0", "futures", "futures-util", "hex", diff --git a/Cargo.toml b/Cargo.toml index a4c8ea2..47ada30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ tycho-types = { version = "0.3.1", features = ["tycho", "stats", "serde", "abi"] uuid = { version = "1.1", features = ["v4", "serde"] } -ed25519-dalek = { git = "https://github.com/broxus/ed25519-dalek.git" } +ed25519-dalek = { version = "2.1.1" } tikv-jemallocator = { version = "0.6.0", features = [ "unprefixed_malloc_on_supported_platforms", diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 47f66b7..bdc498a 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -4,10 +4,8 @@ use std::sync::Arc; use anyhow::anyhow; use axum::http::StatusCode; use bigdecimal::{BigDecimal, ToPrimitive}; -use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signer}; +use ed25519_dalek::{SecretKey, Signer, VerifyingKey}; use nekoton::core::models::Expiration; -use nekoton::core::ton_wallet::multisig::DeployParams; -use nekoton::core::ton_wallet::{MultisigType, TransferAction}; use nekoton::core::InternalMessage; use nekoton::crypto::{SignedMessage, UnsignedMessage}; use nekoton_abi::MessageBuilder; @@ -17,8 +15,10 @@ use num_traits::FromPrimitive; use tokio::sync::oneshot; use ton_block::{GetRepresentationHash, MsgAddressInt}; use ton_types::{deserialize_tree_of_cells, SliceData, UInt256}; -use tycho_types::cell::HashBytes; -use tycho_types::models::StdAddr; +use tycho_types::boc::Boc; +use tycho_types::cell::{CellBuilder, HashBytes}; +use tycho_types::models::{OwnedMessage, StdAddr}; +use tycho_util::time::now_sec; use uuid::Uuid; use crate::api::*; @@ -27,6 +27,8 @@ use crate::prelude::*; use crate::services::*; use crate::sqlx_client::*; use crate::ton_core::*; +use crate::utils::ton_wallet::multisig::DeployParams; +use crate::utils::ton_wallet::MultisigType; use crate::utils::*; mod utils; @@ -82,29 +84,23 @@ impl TonClient { let address = match account_type { AccountType::HighloadWallet => { - nekoton::core::ton_wallet::highload_wallet_v2::compute_contract_address( + ton_wallet::highload_wallet_v2::compute_contract_address( &public, workchain_id as i8, ) } - AccountType::Wallet => nekoton::core::ton_wallet::wallet_v3::compute_contract_address( + AccountType::Wallet => { + ton_wallet::wallet_v3::compute_contract_address(&public, workchain_id as i8) + } + AccountType::SafeMultisig => ton_wallet::multisig::compute_contract_address( &public, + MultisigType::SafeMultisigWallet, workchain_id as i8, ), - AccountType::SafeMultisig => { - nekoton::core::ton_wallet::multisig::compute_contract_address( - &public, - MultisigType::SafeMultisigWallet, - workchain_id as i8, - ) - } AccountType::EverWallet => { - nekoton::core::ton_wallet::ever_wallet::compute_contract_address( - &public, - workchain_id as i8, - ) + ton_wallet::ever_wallet::compute_contract_address(&public, workchain_id as i8) } - }; + }?; let (custodians, confirmations) = match account_type { AccountType::SafeMultisig => ( @@ -132,14 +128,15 @@ impl TonClient { let mut custodians = Vec::with_capacity(public_keys.len()); for key in public_keys { - custodians.push( - PublicKey::from_bytes(&hex::decode(key).map_err(|_| { - TonServiceError::WrongInput("Invalid custodian".to_string()) - })?) - .map_err(|_| { - TonServiceError::WrongInput("Invalid custodian".to_string()) - })?, - ); + let mut key = [0u8; 32]; + let decoded_key = hex::decode(key).map_err(|_| { + TonServiceError::WrongInput("Invalid custodian".to_string()) + })?; + key.copy_from_slice(&decoded_key); + + custodians.push(VerifyingKey::from_bytes(&key).map_err(|_| { + TonServiceError::WrongInput("Invalid custodian".to_string()) + })?); } custodians.push(public); @@ -154,14 +151,13 @@ impl TonClient { }; // Subscribe to accounts - let account = HashBytes::from_str(&address.address().to_hex_string()) - .map_err(|_| anyhow!("Couldn't parse address"))?; + let account = address.address; self.ton_core.add_ton_account_subscription([account]); Ok(CreatedAddress { - workchain_id: address.workchain_id(), - hex: address.address().to_hex_string(), - base64url: nekoton_utils::pack_std_smc_addr(true, &address, true)?, + workchain_id: address.workchain as i32, + hex: address.address.to_string(), + base64url: address.display_base64_url(true).to_string(), public_key: public.to_bytes().to_vec(), private_key: secret.to_bytes().to_vec(), account_type, @@ -173,29 +169,26 @@ impl TonClient { pub async fn get_address_info( &self, - owner: &MsgAddressInt, + owner: &StdAddr, ) -> Result { - let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); + let account = owner.address; let contract = match self.ton_core.get_contract_state(&account) { Ok(contract) => contract, Err(_) => return Ok(NetworkAddressData::uninit(owner)), }; let network_balance = - BigDecimal::from_u128(contract.account.storage.balance.grams.as_u128()) + BigDecimal::from_u128(contract.account.balance.tokens.into_inner()) .ok_or(TonClientError::ParseBigDecimal)?; - let (last_transaction_hash, last_transaction_lt) = - utils::parse_last_transaction(&contract.last_transaction_id); - Ok(NetworkAddressData { - workchain_id: contract.account.addr.workchain_id(), - hex: contract.account.addr.address().to_hex_string(), - account_status: contract.account.storage.state.into(), + workchain_id: contract.account.address.workchain(), + hex: contract.account.address.to_string(), + account_status: contract.account.state.into(), network_balance, - last_transaction_hash, - last_transaction_lt, - sync_u_time: contract.timings.current_utime(&SimpleClock) as i64, + last_transaction_hash: Some(contract.last_transaction_hash.to_string()), + last_transaction_lt: Some(contract.account.last_trans_lt.to_string()), + sync_u_time: 0i64, // TODO fix }) } @@ -204,8 +197,11 @@ impl TonClient { address: &AddressDb, public_key: &[u8], private_key: &[u8], - ) -> Result, Error> { - let public_key = PublicKey::from_bytes(public_key)?; + ) -> Result, Error> { + let mut key = [0u8; 32]; + key.copy_from_slice(&public_key); + + let public_key = VerifyingKey::from_bytes(&key)?; let unsigned_message = match address.account_type { AccountType::SafeMultisig => { @@ -215,15 +211,18 @@ impl TonClient { let owners = custodians .into_iter() - .map(|item| PublicKey::from_bytes(&hex::decode(item).trust_me()).trust_me()) - .collect::>(); - - nekoton::core::ton_wallet::multisig::prepare_deploy( - &SimpleClock, + .map(|item| { + let mut key = [0u8; 32]; + key.copy_from_slice(&hex::decode(item).trust_me()); + VerifyingKey::from_bytes(&key).trust_me() + }) + .collect::>(); + + ton_wallet::multisig::prepare_deploy( &public_key, MultisigType::SafeMultisigWallet, address.workchain_id as i8, - Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT), + now_sec() + DEFAULT_EXPIRATION_TIMEOUT, DeployParams { owners: &owners, req_confirms: address.confirmations.trust_me() as u8, @@ -231,32 +230,29 @@ impl TonClient { }, )? } - AccountType::EverWallet => nekoton::core::ton_wallet::ever_wallet::prepare_deploy( - &SimpleClock, + AccountType::EverWallet => ton_wallet::ever_wallet::prepare_deploy( &public_key, address.workchain_id as i8, - Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT), + now_sec() + DEFAULT_EXPIRATION_TIMEOUT, )?, AccountType::HighloadWallet | AccountType::Wallet => { return Ok(None); } }; - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, - }; + let mut key = [0u8; 32]; + key.copy_from_slice(&private_key); - let data_to_sign = ton_abi::extend_signature_with_id( - unsigned_message.hash(), - self.ton_core.signature_id(), - ); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let signed_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; + + let cell_builder = CellBuilder::build_from(&signed_message).map_err(From::from)?; + let hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { id: Uuid::new_v4(), - message_hash: signed_message.message.hash()?.to_hex_string(), + message_hash: hash.to_string(), account_workchain_id: address.workchain_id, account_hex: address.hex.clone(), original_value: None, @@ -280,41 +276,38 @@ impl TonClient { let original_outputs = serde_json::to_value(transaction.outputs.clone())?; let bounce = transaction.bounce.unwrap_or_default(); - let public_key = PublicKey::from_bytes(public_key)?; + let mut key = [0u8; 32]; + key.copy_from_slice(&public_key); + + let public_key = VerifyingKey::from_bytes(&key)?; + let address = nekoton_utils::repack_address(&transaction.from_address.0)?; - let expiration = Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT); + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; // parse input payload - let payload_cell = match &transaction.payload { - None => None, - Some(s) => { - let bytes = base64::decode(s).map_err(anyhow::Error::from)?; - let mut slice = &bytes[..]; - let tree_of_cells = deserialize_tree_of_cells(&mut slice)?; - Some(tree_of_cells) - } - }; + let body = transaction + .payload + .map(|s| Boc::decode_base64(s)) + .transpose() + .map_err(From::from)?; let transfer_action = match account_type { AccountType::HighloadWallet => { let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); let current_state = self.ton_core.get_contract_state(&account)?.account; - let mut gifts: Vec = vec![]; + let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); - let destination = nekoton_utils::repack_address(&item.recipient_address.0)?; + let destination = + StdAddr::from_str(&item.recipient_address.0).map_err(From::from)?; let amount = item .value .to_u128() .ok_or(TonClientError::ParseBigDecimal)?; - let body = payload_cell - .as_ref() - .map(|c| SliceData::load_cell(c.clone())) - .transpose()?; - gifts.push(nekoton::core::ton_wallet::Gift { + gifts.push(ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -323,12 +316,11 @@ impl TonClient { state_init: None, }); } - nekoton::core::ton_wallet::highload_wallet_v2::prepare_transfer( - &SimpleClock, + ton_wallet::highload_wallet_v2::prepare_transfer( &public_key, ¤t_state, gifts, - expiration, + expire_at, )? } AccountType::Wallet => { @@ -347,7 +339,7 @@ impl TonClient { let flags = recipient.output_type.clone().unwrap_or_default(); let body = payload_cell.map(SliceData::load_cell).transpose()?; - let gifts = vec![nekoton::core::ton_wallet::Gift { + let gifts = vec![ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -355,12 +347,9 @@ impl TonClient { body, state_init: None, }]; - let seqno_offset = nekoton::core::ton_wallet::wallet_v3::estimate_seqno_offset( - &SimpleClock, - ¤t_state, - &[], - ); - nekoton::core::ton_wallet::wallet_v3::prepare_transfer( + let seqno_offset = + ton_wallet::wallet_v3::estimate_seqno_offset(&SimpleClock, ¤t_state, &[]); + ton_wallet::wallet_v3::prepare_transfer( &SimpleClock, &public_key, ¤t_state, @@ -387,7 +376,7 @@ impl TonClient { let body = payload_cell.map(SliceData::load_cell).transpose()?; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -395,7 +384,7 @@ impl TonClient { body, state_init: None, }; - nekoton::core::ton_wallet::multisig::prepare_transfer( + ton_wallet::multisig::prepare_transfer( &SimpleClock, MultisigType::SafeMultisigWallet, &public_key, @@ -409,7 +398,7 @@ impl TonClient { let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); let current_state = self.ton_core.get_contract_state(&account)?.account; - let mut gifts: Vec = vec![]; + let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); let destination = nekoton_utils::repack_address(&item.recipient_address.0)?; @@ -421,7 +410,7 @@ impl TonClient { .as_ref() .map(|c| SliceData::load_cell(c.clone())) .transpose()?; - gifts.push(nekoton::core::ton_wallet::Gift { + gifts.push(ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -430,7 +419,7 @@ impl TonClient { state_init: None, }); } - nekoton::core::ton_wallet::ever_wallet::prepare_transfer( + ton_wallet::ever_wallet::prepare_transfer( &SimpleClock, &public_key, ¤t_state, @@ -475,13 +464,13 @@ impl TonClient { public_key: &[u8], private_key: &[u8], ) -> Result<(SentTransaction, SignedMessage), Error> { - let public_key = PublicKey::from_bytes(public_key)?; + let public_key = VerifyingKey::from_bytes(public_key)?; let address = nekoton_utils::repack_address(&transaction.address.0)?; let account_workchain_id = address.workchain_id(); let account_hex = address.address().to_hex_string(); - let unsigned_message = nekoton::core::ton_wallet::multisig::prepare_confirm_transaction( + let unsigned_message = ton_wallet::multisig::prepare_confirm_transaction( &SimpleClock, MultisigType::SafeMultisigWallet, &public_key, @@ -853,7 +842,7 @@ impl TonClient { ) .await?; - let public_key = PublicKey::from_bytes(public_key).unwrap_or_default(); + let public_key = VerifyingKey::from_bytes(public_key).unwrap_or_default(); let key_pair = Keypair { secret: SecretKey::from_bytes(private_key)?, @@ -884,7 +873,7 @@ impl TonClient { params: Option>, ) -> Result, Error> { let address = nekoton_utils::repack_address(sender_addr)?; - let public_key = PublicKey::from_bytes(public_key)?; + let public_key = VerifyingKey::from_bytes(public_key)?; let expiration = Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT); @@ -902,7 +891,7 @@ impl TonClient { let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); let current_state = self.ton_core.get_contract_state(&account)?.account; - let gifts = vec![nekoton::core::ton_wallet::Gift { + let gifts = vec![ton_wallet::Gift { flags: execution_flag, bounce, destination, @@ -911,13 +900,10 @@ impl TonClient { state_init: None, }]; - let seqno_offset = nekoton::core::ton_wallet::wallet_v3::estimate_seqno_offset( - &SimpleClock, - ¤t_state, - &[], - ); + let seqno_offset = + ton_wallet::wallet_v3::estimate_seqno_offset(&SimpleClock, ¤t_state, &[]); - nekoton::core::ton_wallet::wallet_v3::prepare_transfer( + ton_wallet::wallet_v3::prepare_transfer( &SimpleClock, &public_key, ¤t_state, @@ -932,7 +918,7 @@ impl TonClient { None => return Err(TonClientError::CustodiansNotFound.into()), }; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: execution_flag, bounce, destination, @@ -941,7 +927,7 @@ impl TonClient { state_init: None, }; - nekoton::core::ton_wallet::multisig::prepare_transfer( + ton_wallet::multisig::prepare_transfer( &SimpleClock, MultisigType::SafeMultisigWallet, &public_key, @@ -955,7 +941,7 @@ impl TonClient { let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); let current_state = self.ton_core.get_contract_state(&account)?.account; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: execution_flag, bounce, destination, @@ -964,7 +950,7 @@ impl TonClient { state_init: None, }; - nekoton::core::ton_wallet::highload_wallet_v2::prepare_transfer( + ton_wallet::highload_wallet_v2::prepare_transfer( &SimpleClock, &public_key, ¤t_state, @@ -976,7 +962,7 @@ impl TonClient { let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); let current_state = self.ton_core.get_contract_state(&account)?.account; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: execution_flag, bounce, destination, @@ -985,7 +971,7 @@ impl TonClient { state_init: None, }; - nekoton::core::ton_wallet::ever_wallet::prepare_transfer( + ton_wallet::ever_wallet::prepare_transfer( &SimpleClock, &public_key, ¤t_state, @@ -1064,14 +1050,14 @@ fn build_token_transaction( let expiration = Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT); - let public_key = PublicKey::from_bytes(public_key).unwrap_or_default(); + let public_key = VerifyingKey::from_bytes(public_key).unwrap_or_default(); let transfer_action = match account_type { AccountType::HighloadWallet => { let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); let current_state = ton_core.get_contract_state(&account)?.account; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -1080,7 +1066,7 @@ fn build_token_transaction( state_init: None, }; - nekoton::core::ton_wallet::highload_wallet_v2::prepare_transfer( + ton_wallet::highload_wallet_v2::prepare_transfer( &SimpleClock, &public_key, ¤t_state, @@ -1092,7 +1078,7 @@ fn build_token_transaction( let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); let current_state = ton_core.get_contract_state(&account)?.account; - let gifts = vec![nekoton::core::ton_wallet::Gift { + let gifts = vec![ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -1101,13 +1087,10 @@ fn build_token_transaction( state_init: None, }]; - let seqno_offset = nekoton::core::ton_wallet::wallet_v3::estimate_seqno_offset( - &SimpleClock, - ¤t_state, - &[], - ); + let seqno_offset = + ton_wallet::wallet_v3::estimate_seqno_offset(&SimpleClock, ¤t_state, &[]); - nekoton::core::ton_wallet::wallet_v3::prepare_transfer( + ton_wallet::wallet_v3::prepare_transfer( &SimpleClock, &public_key, ¤t_state, @@ -1122,7 +1105,7 @@ fn build_token_transaction( None => return Err(TonClientError::CustodiansNotFound.into()), }; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -1131,7 +1114,7 @@ fn build_token_transaction( state_init: None, }; - nekoton::core::ton_wallet::multisig::prepare_transfer( + ton_wallet::multisig::prepare_transfer( &SimpleClock, MultisigType::SafeMultisigWallet, &public_key, @@ -1145,7 +1128,7 @@ fn build_token_transaction( let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); let current_state = ton_core.get_contract_state(&account)?.account; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -1154,7 +1137,7 @@ fn build_token_transaction( state_init: None, }; - nekoton::core::ton_wallet::ever_wallet::prepare_transfer( + ton_wallet::ever_wallet::prepare_transfer( &SimpleClock, &public_key, ¤t_state, diff --git a/src/models/address.rs b/src/models/address.rs index 9ad59fb..b2c4465 100644 --- a/src/models/address.rs +++ b/src/models/address.rs @@ -1,6 +1,6 @@ use bigdecimal::BigDecimal; use schemars::JsonSchema; -use ton_block::MsgAddressInt; +use tycho_types::models::StdAddr; use crate::models::*; @@ -98,10 +98,10 @@ pub struct NetworkAddressData { } impl NetworkAddressData { - pub fn uninit(owner: &MsgAddressInt) -> NetworkAddressData { + pub fn uninit(owner: &StdAddr) -> NetworkAddressData { NetworkAddressData { - workchain_id: owner.workchain_id(), - hex: owner.address().to_hex_string(), + workchain_id: owner.workchain as i32, + hex: owner.address.to_string(), account_status: AccountStatus::UnInit, network_balance: Default::default(), last_transaction_hash: None, diff --git a/src/models/existing_contract.rs b/src/models/existing_contract.rs new file mode 100644 index 0000000..d58284e --- /dev/null +++ b/src/models/existing_contract.rs @@ -0,0 +1,8 @@ +use tycho_types::cell::HashBytes; + + +#[derive(Clone, Debug)] +pub struct ExistingContract { + pub account: tycho_types::models::Account, + pub last_transaction_hash: HashBytes, +} diff --git a/src/models/mod.rs b/src/models/mod.rs index d4810f8..232ea27 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -13,6 +13,7 @@ pub use self::token_transaction_events::*; pub use self::token_transactions::*; pub use self::transaction_events::*; pub use self::transactions::*; +pub use self::existing_contract::*; mod account_enums; mod account_transaction_event; @@ -29,3 +30,4 @@ mod token_transaction_events; mod token_transactions; mod transaction_events; mod transactions; +mod existing_contract; diff --git a/src/models/states_cache.rs b/src/models/states_cache.rs deleted file mode 100644 index 9e00718..0000000 --- a/src/models/states_cache.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::sync::Arc; - -use lru::LruCache; -use nekoton::transport::models::ExistingContract; -use nekoton_utils::TrustMe; -use parking_lot::Mutex; -use ton_block::StdAddr; - -use crate::sqlx_client::*; - -#[derive(Clone)] -pub struct StatesCache { - cache: Arc>>, - db: SqlxClient, -} - -impl StatesCache { - pub async fn new(sqlx_client: SqlxClient) -> Result { - let states = sqlx_client.get_token_whitelist().await?; - let mut res = LruCache::new(100); - - states.into_iter().for_each(|x| { - if let Some(state) = x.state { - res.put(nekoton_utils::repack_address(&x.address).trust_me(), { - let state: ExistingContract = serde_json::from_value(state).trust_me(); - state - }); - } - }); - Ok(Self { - cache: Arc::new(Mutex::new(res)), - db: sqlx_client, - }) - } - - pub async fn get(&self, address: &StdAddr) -> Option { - let state = { - let mut lock = self.cache.lock(); - lock.get(address).cloned() - }; - match state { - Some(a) => Some(a), - None => { - let got = self.db.get_root_token(&address.to_string()).await.ok()?; - match got.state { - None => None, - Some(state) => { - let state: Option = serde_json::from_value(state).ok(); - state - } - } - } - } - } - - pub async fn insert(&self, key: StdAddr, value: ExistingContract) { - { - self.cache.lock().put(key.clone(), value.clone()); - } - - if let Err(e) = self - .db - .update_root_token_state(&key.to_string(), serde_json::json!(value)) - .await - { - tracing::error!("Failed inserting root token state: {}", e) - } - } -} diff --git a/src/services/ton.rs b/src/services/ton.rs index 782aa7e..239fc80 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -1179,7 +1179,7 @@ impl TonService { message_hash: String, account_hex: String, account_workchain_id: i32, - signed_message: SignedMessage, + signed_message: OwnerMessage, non_blocking: bool, with_db_update: bool, ) -> Result<(), Error> { diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index 5685bac..e6e77b9 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use std::sync::Arc; use anyhow::{Context, Result}; -use nekoton::transport::models::*; use nekoton_abi::*; use nekoton_utils::Clock; use nekoton_utils::SimpleClock; @@ -78,7 +77,7 @@ impl TonCore { .add_account_subscription(accounts); } - pub fn get_contract_state(&self, account: &UInt256) -> Result { + pub fn get_contract_state(&self, account: &HashBytes) -> Result { self.context.get_contract_state(account) } @@ -178,7 +177,7 @@ impl TonCoreContext { Ok(()) } - fn get_contract_state(&self, account: &UInt256) -> Result { + fn get_contract_state(&self, account: &HashBytes) -> Result { let account = HashBytes::from_slice(account.as_slice()); match self .ton_subscriber @@ -237,15 +236,10 @@ impl TonCoreContext { let account = tycho_types::models::OptionalAccount::load_from(&mut account_state.data.as_slice()?)?; - let LastTransactionId::Exact(last_transaction_id) = account_state.last_transaction_id - else { - return Ok(None); - }; - let shard_account = tycho_types::models::ShardAccount { account: Lazy::new(&account).unwrap(), - last_trans_hash: HashBytes::from_slice(last_transaction_id.hash.as_slice()), - last_trans_lt: last_transaction_id.lt, + last_trans_hash: account_state.last_transaction_hash, + last_trans_lt: account.last_trans_lt(), }; let config_cell = Boc::decode_base64("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").unwrap(); diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index b39f472..77a56d1 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -6,7 +6,6 @@ use anyhow::Result; use futures::stream::FuturesUnordered; use futures::StreamExt; use nekoton::core::models::TokenWalletVersion; -use nekoton::transport::models::ExistingContract; use nekoton_utils::TrustMe; use parking_lot::{Mutex, RwLock, RwLockReadGuard}; use rustc_hash::FxHashMap; @@ -20,6 +19,7 @@ use tycho_block_util::state::{RefMcStateHandle, ShardStateStuff}; use tycho_vm::StackValue; use crate::ton_core::*; +use crate::models::ExistingContract; pub struct TonSubscriber { // tip block timestamp @@ -588,7 +588,7 @@ where pub struct ShardAccount { pub(crate) data: Cell, - pub(crate) last_transaction_id: LastTransactionId, + pub(crate) last_transaction_hash: HashBytes, _state_handle: RefMcStateHandle, } @@ -599,16 +599,10 @@ pub fn make_existing_contract(state: Option) -> Result Ok(Some(ShardAccount { data: account.account.as_cell().unwrap().clone(), - last_transaction_id: LastTransactionId::Exact(TransactionId { - lt: account.last_trans_lt, - hash: UInt256::with_array(account.last_trans_hash.0), - }), + last_transaction_hash: account.last_trans_hash, _state_handle: self.state_handle.clone(), })), None => Ok(None), @@ -653,17 +644,12 @@ impl ShardAccountsMapExt for FxHashMap { match item { Some((_, shard)) => { if let Some((_, account)) = shard.accounts.get(account)? { - let last_transaction_id = LastTransactionId::Exact(TransactionId { - lt: account.last_trans_lt, - hash: UInt256::with_array(account.last_trans_hash.0), - }); - + let last_transaction_hash = account.last_trans_hash; let account = account.account.load()?; - if let Some(stuff) = convert_to_old_account(account)? { + if let Some(account) = account.0 { return Ok(Some(ExistingContract { - account: stuff, - timings: GenTimings::Unknown, - last_transaction_id, + account, + last_transaction_hash, })); } } diff --git a/src/utils/mnemonic.rs b/src/utils/mnemonic.rs new file mode 100644 index 0000000..d6cf220 --- /dev/null +++ b/src/utils/mnemonic.rs @@ -0,0 +1,239 @@ +use anyhow::Error; +use rand::Rng; +use serde::de::{MapAccess, Unexpected, Visitor}; +use serde::{de, Deserialize, Deserializer, Serialize}; +use sha2::Digest; +use slip10_ed25519::derive_ed25519_private_key; +use tiny_hderive::bip32::ExtendedPrivKey; + +const LANGUAGE: bip39::Language = bip39::Language::English; + +#[derive(Serialize, Copy, Clone, Debug, Eq, PartialEq)] +pub enum MnemonicType { + /// Phrase with 24 words, used in Crystal Wallet + Legacy, + /// Phrase with 12 or 24 words, used everywhere else. The additional parameter is used in + /// derivation path to create multiple keys from one mnemonic + Bip39(Bip39MnemonicData), +} + +impl MnemonicType { + pub fn account_id(self) -> u16 { + match self { + Self::Legacy => 0, + Self::Bip39(item) => item.account_id, + } + } +} + +impl<'de> Deserialize<'de> for MnemonicType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct MnemonicTypeVisitor; + + impl<'de> Visitor<'de> for MnemonicTypeVisitor { + type Value = MnemonicType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("string 'Legacy' or object with 'Labs' or 'Bip39' key") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "Legacy" => Ok(MnemonicType::Legacy), + _ => Err(de::Error::invalid_value(Unexpected::Str(value), &"Legacy")), + } + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + match map.next_key::()? { + Some(key) => match key.as_str() { + "Labs" => { + let account_id = map.next_value()?; + Ok(MnemonicType::Bip39(Bip39MnemonicData { + account_id, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + })) + } + "Bip39" => { + let data: Bip39MnemonicData = map.next_value()?; + Ok(MnemonicType::Bip39(data)) + } + _ => Err(de::Error::unknown_field(&key, &["Labs", "Bip39"])), + }, + None => Err(de::Error::missing_field("type")), + } + } + } + + deserializer.deserialize_any(MnemonicTypeVisitor) + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] +pub struct Bip39MnemonicData { + pub account_id: u16, + pub path: Bip39Path, + pub entropy: Bip39Entropy, +} + +impl Bip39MnemonicData { + pub fn labs_old(account_id: u16) -> Self { + Self { + account_id, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + } + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum Bip39Path { + Ever, + Ton, +} + +impl Bip39Path { + pub fn derive(&self, seed_bytes: &[u8], account_id: u16) -> anyhow::Result<[u8; 32]> { + let derived = match self { + Self::Ever => { + let derived = ExtendedPrivKey::derive( + seed_bytes, + format!("m/44'/396'/0'/0/{account_id}").as_str(), + ) + .map_err(|_| anyhow::anyhow!("Invalid derivation path"))?; + + derived.secret() + } + Self::Ton => derive_ed25519_private_key(seed_bytes, &[44, 607, account_id as u32]), + }; + + Ok(derived) + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum Bip39Entropy { + Bits128, + Bits256, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GeneratedKey { + pub words: Vec<&'static str>, + pub account_type: MnemonicType, +} + +pub fn derive_from_phrase(phrase: &str, mnemonic_type: MnemonicType) -> Result { + match mnemonic_type { + MnemonicType::Legacy => legacy::derive_from_phrase(phrase), + MnemonicType::Bip39(data) => labs::derive_from_phrase(phrase, data), + } +} + +/// Generates mnemonic and keypair. +pub fn generate_key(account_type: MnemonicType) -> GeneratedKey { + use bip39::util::{Bits11, IterExt}; + + let rng = &mut rand::thread_rng(); + + pub fn generate_words(entropy: &[u8]) -> Vec<&'static str> { + let wordlist = LANGUAGE.wordlist(); + + let checksum_byte = sha2::Sha256::digest(entropy)[0]; + + entropy + .iter() + .chain(Some(&checksum_byte)) + .bits() + .map(|bits: Bits11| wordlist.get_word(bits)) + .collect() + } + + let entropy_size = match account_type { + MnemonicType::Legacy => 32, + MnemonicType::Bip39(data) => match data.entropy { + Bip39Entropy::Bits128 => 16, + Bip39Entropy::Bits256 => 32, + }, + }; + + let entropy = (0..entropy_size) + .map(|_| rng.gen::()) + .collect::>(); + + GeneratedKey { + account_type, + words: generate_words(&entropy), + } +} + +#[cfg(test)] +mod tests { + use serde::Deserialize; + + use crate::crypto::mnemonic::LANGUAGE; + use crate::crypto::{Bip39Entropy, Bip39MnemonicData, Bip39Path, MnemonicType}; + + #[test] + fn mnemonic_type_deserialize() { + #[derive(Deserialize)] + struct Test { + mnemonic_type: MnemonicType, + } + + let legacy = r#"{"mnemonic_type":"Legacy"}"#; + let legacy_mnemonic: Test = serde_json::from_str(legacy).unwrap(); + assert_eq!(legacy_mnemonic.mnemonic_type, MnemonicType::Legacy); + + let labs = r#"{"mnemonic_type":{"Labs":2}}"#; + let labs_mnemonic: Test = serde_json::from_str(labs).unwrap(); + assert_eq!( + labs_mnemonic.mnemonic_type, + MnemonicType::Bip39(Bip39MnemonicData::labs_old(2)) + ); + + let bip39 = + r#"{"mnemonic_type":{"Bip39":{"account_id":0,"path":"ever","entropy":"bits128"}}}"#; + let bip39_mnemonic: Test = serde_json::from_str(bip39).unwrap(); + assert_eq!( + bip39_mnemonic.mnemonic_type, + MnemonicType::Bip39(Bip39MnemonicData { + account_id: 0, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + }) + ); + } + + const TEST_MNEMONIC: &str = "moral ill excuse avoid only father maid cash coin fat replace roast damage egg garment slot begin wreck elephant sea left marble afford drip"; + + #[test] + fn ton_derive() { + let mnemonic = bip39::Mnemonic::from_phrase(TEST_MNEMONIC, LANGUAGE).unwrap(); + let hd = bip39::Seed::new(&mnemonic, ""); + let seed_bytes = hd.as_bytes(); + + let derived = Bip39Path::Ton.derive(seed_bytes, 0).unwrap(); + + let secret = ed25519_dalek::SecretKey::from_bytes(&derived).unwrap(); + let public = ed25519_dalek::PublicKey::from(&secret); + + assert_eq!( + hex::encode(public.to_bytes()), + "09304d7bd820598794b1be73c95d7bcdd749ef583c8a6a243487dcf4156212a5" + ) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9c4ed45..0f46f19 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -16,10 +16,11 @@ pub use self::tx_context::*; mod encoding; mod existing_contract; +pub mod mnemonic; mod pending_messages_queue; mod shard_utils; mod token_wallet; -mod ton_wallet; +pub mod ton_wallet; mod tx_context; mod wallets; diff --git a/src/utils/shard_utils.rs b/src/utils/shard_utils.rs index 10741e9..5236332 100644 --- a/src/utils/shard_utils.rs +++ b/src/utils/shard_utils.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; use anyhow::Result; -use nekoton::transport::models::ExistingContract; use tycho_types::{cell::HashBytes, models::ShardIdent}; +use crate::models::ExistingContract; + pub type ShardsMap = HashMap; #[derive(Debug, Clone)] diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index 8df2a1d..1d07760 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -4,7 +4,6 @@ use nekoton::core::models::{ RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion, TransferRecipient, }; use nekoton::core::InternalMessage; -use nekoton::transport::models::ExistingContract; use nekoton_abi::{BigUint128, BigUint256, ExecutionContext, MessageBuilder}; use nekoton_contracts::tip3_any::{RootTokenContractState, TokenWalletContractState}; use nekoton_contracts::{old_tip3, tip3_1}; @@ -13,6 +12,8 @@ use num_bigint::BigUint; use ton_block::MsgAddressInt; use ton_types::{SliceData, UInt256}; +use crate::models::ExistingContract; + const INITIAL_BALANCE: u64 = 100_000_000; // 0.1 TON pub fn prepare_token_transfer( diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs index 145c217..1531e7d 100644 --- a/src/utils/ton_wallet/ever_wallet.rs +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use ed25519_dalek::PublicKey; use tycho_types::{ abi::{AbiHeaderType, AbiValue, AbiVersion, Function, NamedAbiType, UnsignedExternalMessage}, cell::{CellBuilder, HashBytes}, diff --git a/src/utils/tx_context.rs b/src/utils/tx_context.rs index f186efa..6c994a3 100644 --- a/src/utils/tx_context.rs +++ b/src/utils/tx_context.rs @@ -1,8 +1,9 @@ -use nekoton::transport::models::ExistingContract; use tokio::sync::oneshot; use ton_types::UInt256; use tycho_types::models::BlockId; +use crate::models::ExistingContract; + pub trait ReadFromTransaction: Sized { fn read_from_transaction(ctx: &TxContext<'_>, state: HandleTransactionStatusTx) -> Option; From adf0f5e20384f3cb6539e999b273e541527a8472 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Tue, 3 Feb 2026 10:51:02 +0300 Subject: [PATCH 16/59] fix ton client --- src/client/ton/mod.rs | 424 ++++++++++++++--------------- src/client/ton/utils.rs | 10 + src/models/existing_contract.rs | 1 - src/models/mod.rs | 4 +- src/models/token_balance.rs | 10 +- src/services/ton.rs | 32 ++- src/ton_core/mod.rs | 43 ++- src/ton_core/ton_subscriber/mod.rs | 2 +- src/utils/token_wallet.rs | 120 ++++---- 9 files changed, 314 insertions(+), 332 deletions(-) diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index bdc498a..b72bd29 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -4,17 +4,11 @@ use std::sync::Arc; use anyhow::anyhow; use axum::http::StatusCode; use bigdecimal::{BigDecimal, ToPrimitive}; -use ed25519_dalek::{SecretKey, Signer, VerifyingKey}; -use nekoton::core::models::Expiration; -use nekoton::core::InternalMessage; -use nekoton::crypto::{SignedMessage, UnsignedMessage}; -use nekoton_abi::MessageBuilder; -use nekoton_utils::{SimpleClock, TrustMe}; +use ed25519_dalek::VerifyingKey; use num_bigint::BigUint; use num_traits::FromPrimitive; use tokio::sync::oneshot; -use ton_block::{GetRepresentationHash, MsgAddressInt}; -use ton_types::{deserialize_tree_of_cells, SliceData, UInt256}; +use tycho_types::abi::{Function, NamedAbiValue, UnsignedExternalMessage}; use tycho_types::boc::Boc; use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{OwnedMessage, StdAddr}; @@ -22,6 +16,7 @@ use tycho_util::time::now_sec; use uuid::Uuid; use crate::api::*; +use crate::client::ton::utils::PrepareResult; use crate::models::*; use crate::prelude::*; use crate::services::*; @@ -167,19 +162,15 @@ impl TonClient { }) } - pub async fn get_address_info( - &self, - owner: &StdAddr, - ) -> Result { + pub async fn get_address_info(&self, owner: &StdAddr) -> Result { let account = owner.address; let contract = match self.ton_core.get_contract_state(&account) { Ok(contract) => contract, Err(_) => return Ok(NetworkAddressData::uninit(owner)), }; - let network_balance = - BigDecimal::from_u128(contract.account.balance.tokens.into_inner()) - .ok_or(TonClientError::ParseBigDecimal)?; + let network_balance = BigDecimal::from_u128(contract.account.balance.tokens.into_inner()) + .ok_or(TonClientError::ParseBigDecimal)?; Ok(NetworkAddressData { workchain_id: contract.account.address.workchain(), @@ -197,11 +188,12 @@ impl TonClient { address: &AddressDb, public_key: &[u8], private_key: &[u8], - ) -> Result, Error> { + ) -> Result, Error> { let mut key = [0u8; 32]; key.copy_from_slice(&public_key); let public_key = VerifyingKey::from_bytes(&key)?; + let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; let unsigned_message = match address.account_type { AccountType::SafeMultisig => { @@ -222,7 +214,7 @@ impl TonClient { &public_key, MultisigType::SafeMultisigWallet, address.workchain_id as i8, - now_sec() + DEFAULT_EXPIRATION_TIMEOUT, + expired_at, DeployParams { owners: &owners, req_confirms: address.confirmations.trust_me() as u8, @@ -233,7 +225,7 @@ impl TonClient { AccountType::EverWallet => ton_wallet::ever_wallet::prepare_deploy( &public_key, address.workchain_id as i8, - now_sec() + DEFAULT_EXPIRATION_TIMEOUT, + expired_at, )?, AccountType::HighloadWallet | AccountType::Wallet => { return Ok(None); @@ -245,9 +237,9 @@ impl TonClient { let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); - let signed_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; + let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; - let cell_builder = CellBuilder::build_from(&signed_message).map_err(From::from)?; + let cell_builder = CellBuilder::build_from(&owned_message).map_err(From::from)?; let hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { @@ -261,7 +253,11 @@ impl TonClient { bounce: false, }; - Ok(Some((sent_transaction, signed_message))) + Ok(Some(PrepareResult { + sent_transaction, + owned_message, + expired_at, + })) } pub async fn prepare_transaction( @@ -271,7 +267,7 @@ impl TonClient { private_key: &[u8], account_type: &AccountType, custodians: &Option, - ) -> Result<(SentTransaction, SignedMessage), Error> { + ) -> Result { let original_value = transaction.outputs.iter().map(|o| o.value.clone()).sum(); let original_outputs = serde_json::to_value(transaction.outputs.clone())?; let bounce = transaction.bounce.unwrap_or_default(); @@ -281,9 +277,9 @@ impl TonClient { let public_key = VerifyingKey::from_bytes(&key)?; - let address = nekoton_utils::repack_address(&transaction.from_address.0)?; + let address = StdAddr::from_str(&transaction.from_address.0).map_err(From::from)?; - let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; // parse input payload let body = transaction @@ -292,9 +288,9 @@ impl TonClient { .transpose() .map_err(From::from)?; - let transfer_action = match account_type { + let unsigned_message = match account_type { AccountType::HighloadWallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; let mut gifts: Vec = vec![]; @@ -320,24 +316,24 @@ impl TonClient { &public_key, ¤t_state, gifts, - expire_at, + expired_at, )? } AccountType::Wallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; let recipient = transaction .outputs .first() .ok_or(TonClientError::RecipientNotFound)?; - let destination = nekoton_utils::repack_address(&recipient.recipient_address.0)?; + let destination = + StdAddr::from_str(&recipient.recipient_address.0).map_err(From::from)?; let amount = recipient .value .to_u128() .ok_or(TonClientError::ParseBigDecimal)?; let flags = recipient.output_type.clone().unwrap_or_default(); - let body = payload_cell.map(SliceData::load_cell).transpose()?; let gifts = vec![ton_wallet::Gift { flags: flags.into(), @@ -348,14 +344,13 @@ impl TonClient { state_init: None, }]; let seqno_offset = - ton_wallet::wallet_v3::estimate_seqno_offset(&SimpleClock, ¤t_state, &[]); + ton_wallet::wallet_v3::estimate_seqno_offset(¤t_state, &[]); ton_wallet::wallet_v3::prepare_transfer( - &SimpleClock, &public_key, ¤t_state, seqno_offset, gifts, - expiration, + expired_at, )? } AccountType::SafeMultisig => { @@ -363,7 +358,8 @@ impl TonClient { .outputs .first() .ok_or(TonClientError::RecipientNotFound)?; - let destination = nekoton_utils::repack_address(&recipient.recipient_address.0)?; + let destination = + StdAddr::from_str(&recipient.recipient_address.0).map_err(From::from)?; let amount = recipient .value .to_u128() @@ -374,8 +370,6 @@ impl TonClient { None => return Err(TonClientError::CustodiansNotFound.into()), }; - let body = payload_cell.map(SliceData::load_cell).transpose()?; - let gift = ton_wallet::Gift { flags: flags.into(), bounce, @@ -385,31 +379,27 @@ impl TonClient { state_init: None, }; ton_wallet::multisig::prepare_transfer( - &SimpleClock, MultisigType::SafeMultisigWallet, &public_key, has_multiple_owners, address.clone(), gift, - expiration, + expired_at, )? } AccountType::EverWallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); - let destination = nekoton_utils::repack_address(&item.recipient_address.0)?; + let destination = + StdAddr::from_str(&item.recipient_address.0).map_err(From::from)?; let amount = item .value .to_u128() .ok_or(TonClientError::ParseBigDecimal)?; - let body = payload_cell - .as_ref() - .map(|c| SliceData::load_cell(c.clone())) - .transpose()?; gifts.push(ton_wallet::Gift { flags: flags.into(), bounce, @@ -420,42 +410,40 @@ impl TonClient { }); } ton_wallet::ever_wallet::prepare_transfer( - &SimpleClock, &public_key, ¤t_state, address.clone(), gifts, - expiration, + expired_at, )? } }; - let unsigned_message = match transfer_action { - TransferAction::Sign(unsigned_message) => unsigned_message, - TransferAction::DeployFirst => { - return Err(TonClientError::AccountNotDeployed(address.to_string()).into()) - } - }; - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, - }; - let data_to_sign = ton_abi::extend_signature_with_id( - unsigned_message.hash(), - self.ton_core.signature_id(), - ); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + + let mut key = [0u8; 32]; + key.copy_from_slice(&private_key); + + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; + + let cell_builder = CellBuilder::build_from(&owned_message).map_err(From::from)?; + let message_hash = cell_builder.repr_hash(); + let sent_transaction = SentTransaction { id: transaction.id, - message_hash: signed_message.message.hash()?.to_hex_string(), - account_workchain_id: address.workchain_id(), - account_hex: address.address().to_hex_string(), + message_hash: message_hash.to_string(), + account_workchain_id: address.workchain as i32, + account_hex: address.address.to_string(), original_value: Some(original_value), original_outputs: Some(original_outputs), aborted: false, bounce, }; - Ok((sent_transaction, signed_message)) + Ok(PrepareResult { + sent_transaction, + owned_message, + expired_at, + }) } pub async fn prepare_confirm_transaction( @@ -463,37 +451,40 @@ impl TonClient { transaction: TransactionConfirm, public_key: &[u8], private_key: &[u8], - ) -> Result<(SentTransaction, SignedMessage), Error> { - let public_key = VerifyingKey::from_bytes(public_key)?; - let address = nekoton_utils::repack_address(&transaction.address.0)?; + ) -> Result { + let mut key = [0u8; 32]; + key.copy_from_slice(&public_key); + + let public_key = VerifyingKey::from_bytes(&key)?; + + let address = StdAddr::from_str(&transaction.address.0).map_err(From::from)?; - let account_workchain_id = address.workchain_id(); - let account_hex = address.address().to_hex_string(); + let account_workchain_id = address.workchain as i32; + let account_hex = address.address.to_string(); + + let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; let unsigned_message = ton_wallet::multisig::prepare_confirm_transaction( - &SimpleClock, MultisigType::SafeMultisigWallet, &public_key, address, transaction.transaction_id, - Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT), + expired_at, )?; - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, - }; + let mut key = [0u8; 32]; + key.copy_from_slice(&private_key); + + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; - let data_to_sign = ton_abi::extend_signature_with_id( - unsigned_message.hash(), - self.ton_core.signature_id(), - ); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + let cell_builder = CellBuilder::build_from(&owned_message).map_err(From::from)?; + let message_hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { id: transaction.id, - message_hash: signed_message.message.hash()?.to_hex_string(), + message_hash: message_hash.to_string(), account_workchain_id, account_hex, original_value: None, @@ -502,19 +493,23 @@ impl TonClient { bounce: false, }; - Ok((sent_transaction, signed_message)) + Ok(PrepareResult { + sent_transaction, + owned_message, + expired_at, + }) } pub async fn get_token_address_info( &self, - owner: &MsgAddressInt, - root_address: &MsgAddressInt, + owner: &StdAddr, + root_address: &StdAddr, ) -> Result { - let root_account = UInt256::from_be_bytes(&root_address.address().get_bytestring(0)); + let root_account = root_address.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; let token_address = get_token_wallet_address(&root_contract, owner)?; - let token_account = UInt256::from_be_bytes(&token_address.address().get_bytestring(0)); + let token_account = token_address.address; let token_contract = match self.ton_core.get_contract_state(&token_account) { Ok(contract) => contract, Err(_) => { @@ -526,21 +521,17 @@ impl TonClient { }; let (version, network_balance) = get_token_wallet_basic_info(&token_contract)?; - let sync_u_time = token_contract.timings.current_utime(&SimpleClock) as i64; - - let (last_transaction_hash, last_transaction_lt) = - utils::parse_last_transaction(&token_contract.last_transaction_id); Ok(NetworkTokenAddressData { - workchain_id: token_address.workchain_id(), - hex: token_address.address().to_hex_string(), + workchain_id: token_address.workchain as i32, + hex: token_address.address.to_string(), root_address: root_address.to_string(), version: version.to_string(), network_balance, - account_status: token_contract.account.storage.state.into(), - last_transaction_hash, - last_transaction_lt, - sync_u_time, + account_status: token_contract.account.state.into(), + last_transaction_hash: Some(token_contract.last_transaction_hash.to_string()), + last_transaction_lt: Some(token_contract.account.last_trans_lt.to_string()), + sync_u_time: 0, // TODO: fix }) } @@ -551,24 +542,24 @@ impl TonClient { private_key: &[u8], account_type: &AccountType, custodians: &Option, - ) -> Result<(SentTransaction, SignedMessage), Error> { - let owner = nekoton_utils::repack_address(&input.from_address.0)?; + ) -> Result { + let owner = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; let token_owner_db = self .sqlx_client .get_token_address( - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), input.root_address.0.clone(), ) .await?; - let token_wallet = nekoton_utils::repack_address(&token_owner_db.address)?; - - let recipient = nekoton_utils::repack_address(&input.recipient_address.0)?; - let destination = nekoton::core::models::TransferRecipient::OwnerWallet(recipient); + let token_wallet = + StdAddr::from_str(&token_owner_db.address).map_err(anyhow::Error::from)?; + let destination = + StdAddr::from_str(&input.recipient_address.0).map_err(anyhow::Error::from)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => nekoton_utils::repack_address(send_gas_to.0.as_str())?, + Some(send_gas_to) => StdAddr::from_str(&send_gas_to.0).map_err(anyhow::Error::from)?, None => owner.clone(), }; @@ -580,15 +571,13 @@ impl TonClient { let attached_amount = input.fee.to_u128().ok_or(TonClientError::ParseBigDecimal)?; // parse input payload - let payload_cell = match &input.payload { - None => None, - Some(s) => { - let bytes = base64::decode(s).map_err(anyhow::Error::from)?; - let mut slice = &bytes[..]; - let tree_of_cells = deserialize_tree_of_cells(&mut slice)?; - Some(tree_of_cells) - } - }; + + let body = input + .payload + .as_ref() + .map(|s| Boc::decode_base64(s)) + .transpose() + .map_err(anyhow::Error::from)?; let internal_message = prepare_token_transfer( owner.clone(), @@ -599,7 +588,7 @@ impl TonClient { send_gas_to, input.notify_receiver, attached_amount, - payload_cell.unwrap_or_default(), + body.unwrap_or_default(), )?; let res = build_token_transaction( @@ -623,25 +612,27 @@ impl TonClient { private_key: &[u8], account_type: &AccountType, custodians: &Option, - ) -> Result<(SentTransaction, SignedMessage), Error> { - let owner = nekoton_utils::repack_address(&input.from_address.0)?; + ) -> Result { + let owner = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; let token_owner_db = self .sqlx_client .get_token_address( - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), input.root_address.0.clone(), ) .await?; - let token_wallet = nekoton_utils::repack_address(&token_owner_db.address)?; + + let token_wallet = + StdAddr::from_str(&token_owner_db.address).map_err(anyhow::Error::from)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => nekoton_utils::repack_address(send_gas_to.0.as_str())?, + Some(send_gas_to) => StdAddr::from_str(&send_gas_to.0).map_err(anyhow::Error::from)?, None => owner.clone(), }; - let callback_to = nekoton_utils::repack_address(input.callback_to.0.as_str())?; + let callback_to = StdAddr::from_str(&input.callback_to.0).map_err(anyhow::Error::from)?; let version = token_owner_db.version.into(); @@ -682,12 +673,13 @@ impl TonClient { private_key: &[u8], account_type: &AccountType, custodians: &Option, - ) -> Result<(SentTransaction, SignedMessage), Error> { - let owner = nekoton_utils::repack_address(&input.owner_address.0)?; - let root_token = nekoton_utils::repack_address(&input.root_address.0)?; - let recipient = nekoton_utils::repack_address(&input.recipient_address.0)?; + ) -> Result { + let owner = StdAddr::from_str(&input.owner_address.0).map_err(anyhow::Error::from)?; + let root_token = StdAddr::from_str(&input.root_address.0).map_err(anyhow::Error::from)?; + let recipient = + StdAddr::from_str(&input.recipient_address.0).map_err(anyhow::Error::from)?; - let root_account = UInt256::from_be_bytes(&root_token.address().get_bytestring(0)); + let root_account = root_token.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; let version = get_root_token_version(&root_contract)?; @@ -704,7 +696,7 @@ impl TonClient { .ok_or(TonClientError::ParseBigUint)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => nekoton_utils::repack_address(send_gas_to.0.as_str())?, + Some(send_gas_to) => StdAddr::from_str(&send_gas_to.0).map_err(anyhow::Error::from)?, None => owner.clone(), }; @@ -739,12 +731,13 @@ impl TonClient { pub async fn send_transaction( &self, - account: UInt256, - signed_message: SignedMessage, + account: HashBytes, + owned_message: OwnedMessage, + expire_at: u32, ) -> Result { let status = self .ton_core - .send_ton_message(&account, &signed_message.message, signed_message.expire_at) + .send_ton_message(account, owned_message, expire_at) .await?; Ok(status) @@ -752,8 +745,8 @@ impl TonClient { pub fn add_pending_message( &self, - account: UInt256, - message_hash: UInt256, + account: HashBytes, + message_hash: HashBytes, expire_at: u32, ) -> Result, Error> { let status = self @@ -789,9 +782,9 @@ impl TonClient { pub async fn run_local( &self, - contract_address: UInt256, - function: ton_abi::Function, - input: &[ton_abi::Token], + contract_address: HashBytes, + function: Function, + input: &[NamedAbiValue], responsible: bool, ) -> anyhow::Result> { use nekoton_abi::FunctionExt; @@ -824,10 +817,10 @@ impl TonClient { bounce: bool, account_type: &AccountType, custodians: &Option, - function: Option, - params: Option>, - ) -> Result { - let unsigned_message = self + function: Option, + params: Option>, + ) -> Result<(OwnedMessage, u32), Error> { + let (unsigned_message, expire_at) = self .prepare_generic_message( sender_addr, public_key, @@ -842,21 +835,14 @@ impl TonClient { ) .await?; - let public_key = VerifyingKey::from_bytes(public_key).unwrap_or_default(); + let mut key = [0u8; 32]; + key.copy_from_slice(&private_key); - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, - }; + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); - let data_to_sign = ton_abi::extend_signature_with_id( - unsigned_message.hash(), - self.ton_core.signature_id(), - ); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; - Ok(signed_message) + Ok((owned_message, expire_at)) } pub async fn prepare_generic_message( @@ -869,26 +855,32 @@ impl TonClient { bounce: bool, account_type: &AccountType, custodians: &Option, - function: Option, - params: Option>, - ) -> Result, Error> { - let address = nekoton_utils::repack_address(sender_addr)?; - let public_key = VerifyingKey::from_bytes(public_key)?; + function: Option, + params: Option>, + ) -> Result<(UnsignedExternalMessage, u32), Error> { + let mut key = [0u8; 32]; + key.copy_from_slice(&public_key); - let expiration = Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT); + let public_key = VerifyingKey::from_bytes(&key)?; - let function_data = function.and_then(|x| { + let address = StdAddr::from_str(&sender_addr).map_err(From::from)?; + + let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + + let function_data = function.and_then(|function| { let tokens = params.unwrap_or_default(); - let (func, _) = MessageBuilder::new(&x).build(); - func.encode_internal_input(&tokens).ok() + function.encode_internal_input(&tokens).ok() }); - let body = function_data.map(SliceData::load_builder).transpose()?; + let body = function_data + .map(|data| data.build()) + .transpose() + .map_err(anyhow::Error::from)?; - let destination = nekoton_utils::repack_address(target_addr)?; + let destination = StdAddr::from_str(&target_addr).map_err(From::from)?; let amount = value.to_u128().ok_or(TonClientError::ParseBigDecimal)?; - let transfer_action = match account_type { + let unsigned_message = match account_type { AccountType::Wallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; let gifts = vec![ton_wallet::Gift { @@ -901,15 +893,14 @@ impl TonClient { }]; let seqno_offset = - ton_wallet::wallet_v3::estimate_seqno_offset(&SimpleClock, ¤t_state, &[]); + ton_wallet::wallet_v3::estimate_seqno_offset(¤t_state, &[]); ton_wallet::wallet_v3::prepare_transfer( - &SimpleClock, &public_key, ¤t_state, seqno_offset, gifts, - expiration, + expired_at, )? } AccountType::SafeMultisig => { @@ -928,17 +919,16 @@ impl TonClient { }; ton_wallet::multisig::prepare_transfer( - &SimpleClock, MultisigType::SafeMultisigWallet, &public_key, has_multiple_owners, address, gift, - expiration, + expired_at, )? } AccountType::HighloadWallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; let gift = ton_wallet::Gift { @@ -951,15 +941,14 @@ impl TonClient { }; ton_wallet::highload_wallet_v2::prepare_transfer( - &SimpleClock, &public_key, ¤t_state, vec![gift], - expiration, + expired_at, )? } AccountType::EverWallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; let gift = ton_wallet::Gift { @@ -972,27 +961,19 @@ impl TonClient { }; ton_wallet::ever_wallet::prepare_transfer( - &SimpleClock, &public_key, ¤t_state, address, vec![gift], - expiration, + expired_at, )? } }; - let unsigned_message = match transfer_action { - TransferAction::Sign(unsigned_message) => unsigned_message, - TransferAction::DeployFirst => { - return Err(TonClientError::AccountNotDeployed(target_addr.to_string()).into()) - } - }; - Ok(unsigned_message) + Ok((unsigned_message, expired_at)) } - pub fn add_ton_account_subscription(&self, account: UInt256) { - let account = HashBytes::from_slice(account.as_slice()); + pub fn add_ton_account_subscription(&self, account: HashBytes) { self.ton_core.add_ton_account_subscription([account]) } @@ -1034,13 +1015,13 @@ impl TonClientError { fn build_token_transaction( ton_core: &Arc, id: Uuid, - owner: MsgAddressInt, + owner: StdAddr, public_key: &[u8], private_key: &[u8], account_type: &AccountType, custodians: &Option, internal_message: InternalMessage, -) -> anyhow::Result<(SentTransaction, SignedMessage)> { +) -> anyhow::Result { let flags = TransactionSendOutputType::default(); let bounce = internal_message.bounce; @@ -1048,13 +1029,16 @@ fn build_token_transaction( let amount = internal_message.amount; let body = Some(internal_message.body); - let expiration = Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT); + let mut key = [0u8; 32]; + key.copy_from_slice(&public_key); - let public_key = VerifyingKey::from_bytes(public_key).unwrap_or_default(); + let public_key = VerifyingKey::from_bytes(&key)?; - let transfer_action = match account_type { + let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + + let unsigned_message = match account_type { AccountType::HighloadWallet => { - let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); + let account = owner.address; let current_state = ton_core.get_contract_state(&account)?.account; let gift = ton_wallet::Gift { @@ -1067,15 +1051,14 @@ fn build_token_transaction( }; ton_wallet::highload_wallet_v2::prepare_transfer( - &SimpleClock, &public_key, ¤t_state, vec![gift], - expiration, + expired_at, )? } AccountType::Wallet => { - let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); + let account = owner.address; let current_state = ton_core.get_contract_state(&account)?.account; let gifts = vec![ton_wallet::Gift { @@ -1087,16 +1070,14 @@ fn build_token_transaction( state_init: None, }]; - let seqno_offset = - ton_wallet::wallet_v3::estimate_seqno_offset(&SimpleClock, ¤t_state, &[]); + let seqno_offset = ton_wallet::wallet_v3::estimate_seqno_offset(¤t_state, &[]); ton_wallet::wallet_v3::prepare_transfer( - &SimpleClock, &public_key, ¤t_state, seqno_offset, gifts, - expiration, + expired_at, )? } AccountType::SafeMultisig => { @@ -1115,17 +1096,16 @@ fn build_token_transaction( }; ton_wallet::multisig::prepare_transfer( - &SimpleClock, MultisigType::SafeMultisigWallet, &public_key, has_multiple_owners, owner.clone(), gift, - expiration, + expired_at, )? } AccountType::EverWallet => { - let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); + let account = owner.address; let current_state = ton_core.get_contract_state(&account)?.account; let gift = ton_wallet::Gift { @@ -1138,45 +1118,41 @@ fn build_token_transaction( }; ton_wallet::ever_wallet::prepare_transfer( - &SimpleClock, &public_key, ¤t_state, owner.clone(), vec![gift], - expiration, + expired_at, )? } }; - let unsigned_message = match transfer_action { - TransferAction::Sign(unsigned_message) => unsigned_message, - TransferAction::DeployFirst => { - return Err(TonClientError::AccountNotDeployed(owner.to_string()).into()) - } - }; + let mut key = [0u8; 32]; + key.copy_from_slice(&private_key); - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, - }; + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let owned_message = unsigned_message.sign(&key_pair, ton_core.signature_id())?; - let data_to_sign = - ton_abi::extend_signature_with_id(unsigned_message.hash(), ton_core.signature_id()); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + let cell_builder = CellBuilder::build_from(&owned_message).map_err(From::from)?; + let hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { id, - message_hash: signed_message.message.hash()?.to_hex_string(), - account_workchain_id: owner.workchain_id(), - account_hex: owner.address().to_hex_string(), + message_hash: hash.to_string(), + account_workchain_id: owner.workchain as i32, + account_hex: owner.address.to_string(), original_value: None, original_outputs: None, aborted: false, bounce, }; - Ok((sent_transaction, signed_message)) + Ok(PrepareResult { + sent_transaction, + owned_message, + expired_at, + }) } const TYCHO_TESTNET_CHAIN_ID: i32 = -4000; diff --git a/src/client/ton/utils.rs b/src/client/ton/utils.rs index d961322..ba6951b 100644 --- a/src/client/ton/utils.rs +++ b/src/client/ton/utils.rs @@ -1,4 +1,7 @@ use nekoton_abi::LastTransactionId; +use tycho_types::models::OwnedMessage; + +use crate::models::SentTransaction; pub fn parse_last_transaction( last_transaction: &LastTransactionId, @@ -13,3 +16,10 @@ pub fn parse_last_transaction( (last_transaction_hash, last_transaction_lt) } + +#[derive(Debug)] +pub struct PrepareResult { + pub sent_transaction: SentTransaction, + pub owned_message: OwnedMessage, + pub expired_at: u32, +} diff --git a/src/models/existing_contract.rs b/src/models/existing_contract.rs index d58284e..50812e3 100644 --- a/src/models/existing_contract.rs +++ b/src/models/existing_contract.rs @@ -1,6 +1,5 @@ use tycho_types::cell::HashBytes; - #[derive(Clone, Debug)] pub struct ExistingContract { pub account: tycho_types::models::Account, diff --git a/src/models/mod.rs b/src/models/mod.rs index 232ea27..51a0b45 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -2,6 +2,7 @@ pub use self::account_enums::*; pub use self::account_transaction_event::*; pub use self::address::*; pub use self::blockchain::*; +pub use self::existing_contract::*; pub use self::key::*; pub use self::last_key_blocks::*; pub use self::metrics::*; @@ -13,12 +14,12 @@ pub use self::token_transaction_events::*; pub use self::token_transactions::*; pub use self::transaction_events::*; pub use self::transactions::*; -pub use self::existing_contract::*; mod account_enums; mod account_transaction_event; mod address; mod blockchain; +mod existing_contract; mod key; mod last_key_blocks; mod metrics; @@ -30,4 +31,3 @@ mod token_transaction_events; mod token_transactions; mod transaction_events; mod transactions; -mod existing_contract; diff --git a/src/models/token_balance.rs b/src/models/token_balance.rs index 41470fc..1aa3f70 100644 --- a/src/models/token_balance.rs +++ b/src/models/token_balance.rs @@ -1,4 +1,5 @@ use bigdecimal::BigDecimal; +use tycho_types::models::StdAddr; use crate::models::*; @@ -25,13 +26,10 @@ pub struct NetworkTokenAddressData { } impl NetworkTokenAddressData { - pub fn uninit( - owner: &ton_block::MsgAddressInt, - root: &ton_block::MsgAddressInt, - ) -> NetworkTokenAddressData { + pub fn uninit(owner: &StdAddr, root: &StdAddr) -> NetworkTokenAddressData { NetworkTokenAddressData { - workchain_id: owner.workchain_id(), - hex: owner.address().to_hex_string(), + workchain_id: owner.workchain as i32, + hex: owner.address.to_string(), root_address: root.to_string(), version: Default::default(), account_status: AccountStatus::UnInit, diff --git a/src/services/ton.rs b/src/services/ton.rs index 239fc80..f8cba6f 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -1,3 +1,4 @@ +use std::any; use std::convert::TryInto; use std::str::FromStr; use std::sync::{Arc, Weak}; @@ -13,6 +14,7 @@ use ton_abi::{Param, Token, TokenValue}; use ton_block::{GetRepresentationHash, MsgAddressInt, Serializable}; use ton_types::{BuilderData, UInt256}; use tycho_types::cell::HashBytes; +use tycho_types::models::{OwnedMessage, StdAddr}; use uuid::Uuid; use crate::api::*; @@ -109,13 +111,13 @@ impl TonService { service_id: &ServiceId, address: Address, ) -> Result<(AddressDb, NetworkAddressData), Error> { - let account = repack_address(&address.0)?; + let account = StdAddr::from_str(&address.0).map_err(anyhow::Error::from)?; let address = self .sqlx_client .get_address( *service_id, - account.workchain_id(), - account.address().to_hex_string(), + account.workchain as i32, + account.address.to_string(), ) .await?; let network = self.ton_api_client.get_address_info(&account).await?; @@ -128,13 +130,13 @@ impl TonService { service_id: &ServiceId, address: Address, ) -> Result { - let account = repack_address(&address.0)?; + let account = StdAddr::from_str(&address.0).map_err(anyhow::Error::from)?; let address = self .sqlx_client .get_address( *service_id, - account.workchain_id(), - account.address().to_hex_string(), + account.workchain as i32, + account.address.to_string(), ) .await?; @@ -188,7 +190,11 @@ impl TonService { .await?; } - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expired_at, + } = self .ton_api_client .prepare_transaction( input, @@ -201,16 +207,17 @@ impl TonService { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expired_at, ) .await?; @@ -1179,9 +1186,10 @@ impl TonService { message_hash: String, account_hex: String, account_workchain_id: i32, - signed_message: OwnerMessage, + signed_message: OwnedMessage, non_blocking: bool, with_db_update: bool, + expired_at: u32, ) -> Result<(), Error> { let ton_service = Arc::downgrade(self); @@ -1359,7 +1367,7 @@ async fn send_transaction( message_hash: String, account_hex: String, account_workchain_id: i32, - signed_message: SignedMessage, + signed_message: OwnedMessage, with_db_update: bool, ) -> Result<(), Error> { let ton_service = match ton_service.upgrade() { @@ -1367,7 +1375,7 @@ async fn send_transaction( None => return Err(TonServiceError::ServiceUnavailable.into()), }; - let account = UInt256::from_be_bytes(&hex::decode(&account_hex)?); + let account = HashBytes::from_str(&account_hex).map_err(anyhow::Error::from)?; let status = ton_service .ton_api_client diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index e6e77b9..8a0a833 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -14,6 +14,7 @@ use tycho_core::storage::CoreStorage; use tycho_executor::ExecutorParams; use tycho_executor::ParsedConfig; use tycho_types::boc::Boc; +use tycho_types::cell::CellBuilder; use tycho_types::cell::HashBytes; use tycho_types::cell::Lazy; use tycho_types::cell::Load; @@ -83,19 +84,19 @@ impl TonCore { pub async fn send_ton_message( &self, - account: &UInt256, - message: &ton_block::Message, + account: HashBytes, + owned_message: OwnedMessage, expire_at: u32, ) -> Result { self.context - .send_ton_message(account, message, expire_at) + .send_ton_message(account, owned_message, expire_at) .await } pub fn add_pending_message( &self, - account: UInt256, - message_hash: UInt256, + account: HashBytes, + message_hash: HashBytes, expire_at: u32, ) -> Result> { self.context @@ -191,23 +192,22 @@ impl TonCoreContext { async fn send_ton_message( &self, - account: &UInt256, - message: &ton_block::Message, + account: HashBytes, + owned_message: OwnedMessage, expire_at: u32, ) -> Result { - match message.header() { - ton_block::CommonMsgInfo::ExtInMsgInfo(header) => header.dst.workchain_id(), + match &owned_message.info { + MsgInfo::ExtIn(ext) => ext.dst.workchain(), _ => return Err(TonCoreError::ExternalTonMessageExpected.into()), }; - let cells = message.write_to_new_cell()?.into_cell()?; - let serialized = ton_types::serialize_toc(&cells)?; + let cell = CellBuilder::build_from(owned_message)?; + let message_hash = *cell.repr_hash(); + let serialized = Boc::encode(cell); - let rx = self.messages_queue.add_message( - HashBytes::from_slice(account.as_slice()), - HashBytes::from_slice(cells.repr_hash().as_slice()), - expire_at, - )?; + let rx = self + .messages_queue + .add_message(account, message_hash, expire_at)?; self.blockchain_rpc_client .broadcast_external_message(&serialized) @@ -274,15 +274,12 @@ impl TonCoreContext { fn add_pending_message( &self, - account: UInt256, - message_hash: UInt256, + account: HashBytes, + message_hash: HashBytes, expire_at: u32, ) -> Result> { - self.messages_queue.add_message( - HashBytes::from_slice(account.as_slice()), - HashBytes::from_slice(message_hash.as_slice()), - expire_at, - ) + self.messages_queue + .add_message(account, message_hash, expire_at) } } diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index 77a56d1..91e7d91 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -18,8 +18,8 @@ use tycho_block_util::block::BlockStuff; use tycho_block_util::state::{RefMcStateHandle, ShardStateStuff}; use tycho_vm::StackValue; -use crate::ton_core::*; use crate::models::ExistingContract; +use crate::ton_core::*; pub struct TonSubscriber { // tip block timestamp diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index 1d07760..3af0322 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -1,74 +1,69 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton::core::models::{ - RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion, TransferRecipient, -}; -use nekoton::core::InternalMessage; +use nekoton::core::models::{RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion}; use nekoton_abi::{BigUint128, BigUint256, ExecutionContext, MessageBuilder}; use nekoton_contracts::tip3_any::{RootTokenContractState, TokenWalletContractState}; use nekoton_contracts::{old_tip3, tip3_1}; use nekoton_utils::SimpleClock; use num_bigint::BigUint; -use ton_block::MsgAddressInt; -use ton_types::{SliceData, UInt256}; +use tycho_types::cell::{Cell, HashBytes}; +use tycho_types::models::StdAddr; use crate::models::ExistingContract; const INITIAL_BALANCE: u64 = 100_000_000; // 0.1 TON +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct InternalMessage { + #[serde( + with = "serde_optional_address", + skip_serializing_if = "Option::is_none" + )] + pub source: Option, + #[serde(with = "serde_address")] + pub destination: StdAddr, + #[serde(with = "serde_string")] + pub amount: u128, + pub bounce: bool, + #[serde(with = "serde_boc")] + pub body: Cell, +} + pub fn prepare_token_transfer( - owner: MsgAddressInt, - token_wallet: MsgAddressInt, + owner: StdAddr, + token_wallet: StdAddr, version: TokenWalletVersion, - destination: TransferRecipient, + destination: StdAddr, tokens: BigUint, - send_gas_to: MsgAddressInt, + send_gas_to: StdAddr, notify_receiver: bool, attached_amount: u128, - payload: ton_types::Cell, + payload: Cell, ) -> Result { let (function, input) = match version { TokenWalletVersion::OldTip3v4 => { use old_tip3::token_wallet_contract; - match destination { - TransferRecipient::TokenWallet(token_wallet) => { - MessageBuilder::new(token_wallet_contract::transfer()) - .arg(token_wallet) // to - .arg(BigUint128(tokens)) // tokens - } - TransferRecipient::OwnerWallet(owner_wallet) => { - MessageBuilder::new(token_wallet_contract::transfer_to_recipient()) - .arg(BigUint256(Default::default())) // recipient_public_key - .arg(owner_wallet) // recipient_address - .arg(BigUint128(tokens)) // tokens - .arg(BigUint128(INITIAL_BALANCE.into())) // deploy_grams - } - } - .arg(BigUint128(Default::default())) // grams / transfer_grams - .arg(send_gas_to) // send_gas_to - .arg(notify_receiver) // notify_receiver - .arg(payload) // payload - .build() + MessageBuilder::new(token_wallet_contract::transfer_to_recipient()) + .arg(BigUint256(Default::default())) // recipient_public_key + .arg(owner_wallet) // recipient_address + .arg(BigUint128(tokens)) // tokens + .arg(BigUint128(INITIAL_BALANCE.into())) // deploy_grams + .arg(BigUint128(Default::default())) // grams / transfer_grams + .arg(send_gas_to) // send_gas_to + .arg(notify_receiver) // notify_receiver + .arg(payload) // payload + .build() } TokenWalletVersion::Tip3 => { use tip3_1::token_wallet_contract; - match destination { - TransferRecipient::TokenWallet(token_wallet) => { - MessageBuilder::new(token_wallet_contract::transfer_to_wallet()) - .arg(BigUint128(tokens)) // amount - .arg(token_wallet) // recipient token wallet - } - TransferRecipient::OwnerWallet(owner_wallet) => { - MessageBuilder::new(token_wallet_contract::transfer()) - .arg(BigUint128(tokens)) // amount - .arg(owner_wallet) // recipient - .arg(BigUint128(INITIAL_BALANCE.into())) // deployWalletValue - } - } - .arg(send_gas_to) // remainingGasTo - .arg(notify_receiver) // notify - .arg(payload) // payload - .build() + MessageBuilder::new(token_wallet_contract::transfer()) + .arg(BigUint128(tokens)) // amount + .arg(owner_wallet) // recipient + .arg(BigUint128(INITIAL_BALANCE.into())) // deployWalletValue + .arg(send_gas_to) // remainingGasTo + .arg(notify_receiver) // notify + .arg(payload) // payload + .build() } }; @@ -84,14 +79,14 @@ pub fn prepare_token_transfer( } pub fn prepare_token_burn( - owner: MsgAddressInt, - token_wallet: MsgAddressInt, + owner: StdAddr, + token_wallet: StdAddr, version: TokenWalletVersion, tokens: BigUint, - send_gas_to: MsgAddressInt, - callback_to: MsgAddressInt, + send_gas_to: StdAddr, + callback_to: StdAddr, attached_amount: u128, - payload: ton_types::Cell, + payload: Cell, ) -> Result { let (function, input) = match version { TokenWalletVersion::OldTip3v4 => { @@ -127,16 +122,16 @@ pub fn prepare_token_burn( } pub fn prepare_token_mint( - owner: MsgAddressInt, - root_token: MsgAddressInt, + owner: StdAddr, + root_token: StdAddr, version: TokenWalletVersion, tokens: BigUint, - recipient: MsgAddressInt, + recipient: StdAddr, deploy_wallet_value: BigUint, - send_gas_to: MsgAddressInt, + send_gas_to: StdAddr, notify: bool, attached_amount: u128, - payload: ton_types::Cell, + payload: Cell, ) -> Result { let (function, input) = match version { TokenWalletVersion::OldTip3v4 => return Err(TokenWalletError::MintNotSupported.into()), @@ -166,8 +161,8 @@ pub fn prepare_token_mint( pub fn get_token_wallet_address( root_contract: &ExistingContract, - owner: &MsgAddressInt, -) -> Result { + owner: &StdAddr, +) -> Result { let root_contract_state = RootTokenContractState(ExecutionContext { clock: &SimpleClock, account_stuff: &root_contract.account, @@ -180,8 +175,8 @@ pub fn get_token_wallet_address( pub fn get_token_wallet_account( root_contract: &ExistingContract, - owner: &MsgAddressInt, -) -> Result { + owner: &StdAddr, +) -> Result { let root_contract_state = RootTokenContractState(ExecutionContext { clock: &SimpleClock, account_stuff: &root_contract.account, @@ -190,8 +185,7 @@ pub fn get_token_wallet_account( let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; let token_wallet_address = root_contract_state.get_wallet_address(version, owner)?; - let token_wallet_account = - UInt256::from_be_bytes(&token_wallet_address.address().get_bytestring(0)); + let token_wallet_account = token_wallet_address.address; Ok(token_wallet_account) } From c0d07ec588adda6ca5accb246e3582ec8eaff9d2 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Tue, 3 Feb 2026 16:58:14 +0300 Subject: [PATCH 17/59] fix ton service --- src/api/controllers/misc.rs | 9 +- src/client/ton/mod.rs | 85 +++++++++--------- src/client/ton/utils.rs | 25 ------ src/server.rs | 6 +- src/services/storage.rs | 26 +++--- src/services/ton.rs | 171 ++++++++++++++++++++++-------------- 6 files changed, 169 insertions(+), 153 deletions(-) delete mode 100644 src/client/ton/utils.rs diff --git a/src/api/controllers/misc.rs b/src/api/controllers/misc.rs index 14104da..234a25c 100644 --- a/src/api/controllers/misc.rs +++ b/src/api/controllers/misc.rs @@ -95,7 +95,7 @@ pub async fn post_prepare_generic_message( ) .await?; - ctx.memory_storage.add_message(unsigned_message.clone()); + ctx.memory_storage.add_message(unsigned_message); let elapsed = start.elapsed(); histogram!("execution_time_seconds", elapsed, "method" => "prepareGenericMessage"); @@ -114,18 +114,19 @@ pub async fn post_send_signed_message( let res = match ctx.memory_storage.get_message(&req.hash) { Some(message) => { + let expire_at = message.expire_at(); let signature: [u8; 64] = hex::decode(req.signature) .map_err(|_| ControllersError::WrongInput("Bad signature format".to_string()))? .try_into() .map_err(|_| ControllersError::WrongInput("Bad signature format".to_string()))?; - let signed_message = message - .sign(&signature) + let owned_message = message + .with_signature(&ed25519_dalek::Signature::from_bytes(&signature)) .map_err(|_| ControllersError::WrongInput("Bad signature format".to_string()))?; let hash = ctx .ton_service - .send_signed_message(req.sender_addr, req.hash, signed_message) + .send_signed_message(req.sender_addr, req.hash, owned_message, expire_at) .await?; Ok(SignedMessageHashResponse { diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index b72bd29..dd71e7d 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use std::sync::Arc; -use anyhow::anyhow; use axum::http::StatusCode; use bigdecimal::{BigDecimal, ToPrimitive}; use ed25519_dalek::VerifyingKey; @@ -16,7 +15,6 @@ use tycho_util::time::now_sec; use uuid::Uuid; use crate::api::*; -use crate::client::ton::utils::PrepareResult; use crate::models::*; use crate::prelude::*; use crate::services::*; @@ -26,8 +24,6 @@ use crate::utils::ton_wallet::multisig::DeployParams; use crate::utils::ton_wallet::MultisigType; use crate::utils::*; -mod utils; - #[derive(Clone)] pub struct TonClient { ton_core: Arc, @@ -49,9 +45,10 @@ impl TonClient { .await? .into_iter() .map(|item| { - StdAddr::from_str(&format!("{}:{}", item.workchain_id, item.hex)).trust_me() + StdAddr::from_str(&format!("{}:{}", item.workchain_id, item.hex)) + .map_err(From::from) }) - .collect::>(); + .collect::>>()?; // Subscribe to ton accounts let owner_accounts = owner_addresses @@ -193,31 +190,31 @@ impl TonClient { key.copy_from_slice(&public_key); let public_key = VerifyingKey::from_bytes(&key)?; - let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; let unsigned_message = match address.account_type { AccountType::SafeMultisig => { - let custodians: Vec = - serde_json::from_value(address.custodians_public_keys.clone().trust_me()) - .trust_me(); + let custodians: Vec = serde_json::from_value( + address.custodians_public_keys.clone().unwrap_or_default(), + )?; let owners = custodians .into_iter() .map(|item| { let mut key = [0u8; 32]; - key.copy_from_slice(&hex::decode(item).trust_me()); - VerifyingKey::from_bytes(&key).trust_me() + key.copy_from_slice(&hex::decode(item).map_err(From::from)?); + VerifyingKey::from_bytes(&key).map_err(From::from) }) - .collect::>(); + .collect::, Error>>()?; ton_wallet::multisig::prepare_deploy( &public_key, MultisigType::SafeMultisigWallet, address.workchain_id as i8, - expired_at, + expire_at, DeployParams { owners: &owners, - req_confirms: address.confirmations.trust_me() as u8, + req_confirms: address.confirmations.unwrap_or_default() as u8, expiration_time: None, }, )? @@ -225,7 +222,7 @@ impl TonClient { AccountType::EverWallet => ton_wallet::ever_wallet::prepare_deploy( &public_key, address.workchain_id as i8, - expired_at, + expire_at, )?, AccountType::HighloadWallet | AccountType::Wallet => { return Ok(None); @@ -256,7 +253,7 @@ impl TonClient { Ok(Some(PrepareResult { sent_transaction, owned_message, - expired_at, + expire_at, })) } @@ -279,7 +276,7 @@ impl TonClient { let address = StdAddr::from_str(&transaction.from_address.0).map_err(From::from)?; - let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; // parse input payload let body = transaction @@ -316,7 +313,7 @@ impl TonClient { &public_key, ¤t_state, gifts, - expired_at, + expire_at, )? } AccountType::Wallet => { @@ -350,7 +347,7 @@ impl TonClient { ¤t_state, seqno_offset, gifts, - expired_at, + expire_at, )? } AccountType::SafeMultisig => { @@ -384,7 +381,7 @@ impl TonClient { has_multiple_owners, address.clone(), gift, - expired_at, + expire_at, )? } AccountType::EverWallet => { @@ -414,7 +411,7 @@ impl TonClient { ¤t_state, address.clone(), gifts, - expired_at, + expire_at, )? } }; @@ -442,7 +439,7 @@ impl TonClient { Ok(PrepareResult { sent_transaction, owned_message, - expired_at, + expire_at, }) } @@ -462,14 +459,14 @@ impl TonClient { let account_workchain_id = address.workchain as i32; let account_hex = address.address.to_string(); - let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; let unsigned_message = ton_wallet::multisig::prepare_confirm_transaction( MultisigType::SafeMultisigWallet, &public_key, address, transaction.transaction_id, - expired_at, + expire_at, )?; let mut key = [0u8; 32]; @@ -496,7 +493,7 @@ impl TonClient { Ok(PrepareResult { sent_transaction, owned_message, - expired_at, + expire_at, }) } @@ -820,7 +817,7 @@ impl TonClient { function: Option, params: Option>, ) -> Result<(OwnedMessage, u32), Error> { - let (unsigned_message, expire_at) = self + let unsigned_message = self .prepare_generic_message( sender_addr, public_key, @@ -839,6 +836,7 @@ impl TonClient { key.copy_from_slice(&private_key); let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + let expire_at = unsigned_message.expire_at(); let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; @@ -857,7 +855,7 @@ impl TonClient { custodians: &Option, function: Option, params: Option>, - ) -> Result<(UnsignedExternalMessage, u32), Error> { + ) -> Result { let mut key = [0u8; 32]; key.copy_from_slice(&public_key); @@ -865,7 +863,7 @@ impl TonClient { let address = StdAddr::from_str(&sender_addr).map_err(From::from)?; - let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; let function_data = function.and_then(|function| { let tokens = params.unwrap_or_default(); @@ -900,7 +898,7 @@ impl TonClient { ¤t_state, seqno_offset, gifts, - expired_at, + expire_at, )? } AccountType::SafeMultisig => { @@ -924,7 +922,7 @@ impl TonClient { has_multiple_owners, address, gift, - expired_at, + expire_at, )? } AccountType::HighloadWallet => { @@ -944,7 +942,7 @@ impl TonClient { &public_key, ¤t_state, vec![gift], - expired_at, + expire_at, )? } AccountType::EverWallet => { @@ -965,12 +963,12 @@ impl TonClient { ¤t_state, address, vec![gift], - expired_at, + expire_at, )? } }; - Ok((unsigned_message, expired_at)) + Ok(unsigned_message) } pub fn add_ton_account_subscription(&self, account: HashBytes) { @@ -1034,7 +1032,7 @@ fn build_token_transaction( let public_key = VerifyingKey::from_bytes(&key)?; - let expired_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; let unsigned_message = match account_type { AccountType::HighloadWallet => { @@ -1054,7 +1052,7 @@ fn build_token_transaction( &public_key, ¤t_state, vec![gift], - expired_at, + expire_at, )? } AccountType::Wallet => { @@ -1077,7 +1075,7 @@ fn build_token_transaction( ¤t_state, seqno_offset, gifts, - expired_at, + expire_at, )? } AccountType::SafeMultisig => { @@ -1101,7 +1099,7 @@ fn build_token_transaction( has_multiple_owners, owner.clone(), gift, - expired_at, + expire_at, )? } AccountType::EverWallet => { @@ -1122,7 +1120,7 @@ fn build_token_transaction( ¤t_state, owner.clone(), vec![gift], - expired_at, + expire_at, )? } }; @@ -1151,8 +1149,15 @@ fn build_token_transaction( Ok(PrepareResult { sent_transaction, owned_message, - expired_at, + expire_at, }) } const TYCHO_TESTNET_CHAIN_ID: i32 = -4000; + +#[derive(Debug)] +pub struct PrepareResult { + pub sent_transaction: SentTransaction, + pub owned_message: OwnedMessage, + pub expire_at: u32, +} diff --git a/src/client/ton/utils.rs b/src/client/ton/utils.rs deleted file mode 100644 index ba6951b..0000000 --- a/src/client/ton/utils.rs +++ /dev/null @@ -1,25 +0,0 @@ -use nekoton_abi::LastTransactionId; -use tycho_types::models::OwnedMessage; - -use crate::models::SentTransaction; - -pub fn parse_last_transaction( - last_transaction: &LastTransactionId, -) -> (Option, Option) { - let (last_transaction_hash, last_transaction_lt) = match last_transaction { - LastTransactionId::Exact(transaction_id) => ( - Some(transaction_id.hash.to_hex_string()), - Some(transaction_id.lt.to_string()), - ), - LastTransactionId::Inexact { .. } => (None, None), - }; - - (last_transaction_hash, last_transaction_lt) -} - -#[derive(Debug)] -pub struct PrepareResult { - pub sent_transaction: SentTransaction, - pub owned_message: OwnedMessage, - pub expired_at: u32, -} diff --git a/src/server.rs b/src/server.rs index 895b2b9..a18ce98 100644 --- a/src/server.rs +++ b/src/server.rs @@ -271,14 +271,14 @@ impl EngineContext { let now = chrono::Utc::now().timestamp() as u32; // Delete expired guards - self.guards.retain(|_, (_, expired_at)| now < *expired_at); + self.guards.retain(|_, (_, expire_at)| now < *expire_at); match self.guards.entry(account) { Entry::Occupied(entry) => entry.get().0.clone(), Entry::Vacant(entry) => { - let expired_at = now + 5 * DEFAULT_EXPIRATION_TIMEOUT; + let expire_at = now + 5 * DEFAULT_EXPIRATION_TIMEOUT; entry - .insert((Arc::new(Mutex::default()), expired_at)) + .insert((Arc::new(Mutex::default()), expire_at)) .value() .0 .clone() diff --git a/src/services/storage.rs b/src/services/storage.rs index 6e5ac3b..943a4a0 100644 --- a/src/services/storage.rs +++ b/src/services/storage.rs @@ -1,29 +1,27 @@ -use std::time::SystemTime; - -use nekoton::crypto::UnsignedMessage; -use nekoton_utils::TrustMe; +use tycho_types::{abi::UnsignedExternalMessage, cell::CellBuilder}; +use tycho_util::time::now_sec; use crate::utils::FxDashMap; #[derive(Default)] pub struct StorageHandler { - message_collection: FxDashMap>, + message_collection: FxDashMap, } impl StorageHandler { - pub fn add_message(&self, message: Box) -> String { - let key = hex::encode(message.hash()); + pub fn add_message(&self, message: UnsignedExternalMessage) -> String { + let cell_builder = CellBuilder::build_from(message.without_signature().unwrap()).unwrap(); + let message_hash = *cell_builder.repr_hash(); + let key = message_hash.to_string(); self.message_collection.insert(key.clone(), message); key } - pub fn get_message(&self, hash: &str) -> Option> { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .trust_me() - .as_secs() as u32; - self.message_collection.retain(|_, v| v.expire_at() > now); - let message = self.message_collection.get(hash).map(|x| x.value().clone()); + pub fn get_message(&self, hash: &str) -> Option { + let now = now_sec(); + self.message_collection + .retain(|_, message| message.expire_at() > now); + let message = self.message_collection.get(hash).map(|x| x.value()); message } } diff --git a/src/services/ton.rs b/src/services/ton.rs index f8cba6f..844bfa3 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -1,4 +1,3 @@ -use std::any; use std::convert::TryInto; use std::str::FromStr; use std::sync::{Arc, Weak}; @@ -13,7 +12,8 @@ use ton_abi::contract::ABI_VERSION_2_2; use ton_abi::{Param, Token, TokenValue}; use ton_block::{GetRepresentationHash, MsgAddressInt, Serializable}; use ton_types::{BuilderData, UInt256}; -use tycho_types::cell::HashBytes; +use tycho_types::abi::UnsignedExternalMessage; +use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{OwnedMessage, StdAddr}; use uuid::Uuid; @@ -57,9 +57,8 @@ impl TonService { // Resend transactions for transaction in transactions { - let account = UInt256::from_be_bytes(&hex::decode(transaction.account_hex.clone())?); - let message_hash = - UInt256::from_be_bytes(&hex::decode(transaction.message_hash.clone())?); + let account = HashBytes::from_str(&transaction.account_hex)?; + let message_hash = HashBytes::from_str(&transaction.message_hash)?; let expire_at = transaction.created_at.and_utc().timestamp() as u32 + DEFAULT_EXPIRATION_TIMEOUT; @@ -148,7 +147,7 @@ impl TonService { service_id: &ServiceId, input: TransactionSend, ) -> Result { - let address = repack_address(&input.from_address.0)?; + let address = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; let network = self.ton_api_client.get_address_info(&address).await?; for transaction_output in input.outputs.iter() { @@ -175,8 +174,8 @@ impl TonService { .sqlx_client .get_address( *service_id, - address.workchain_id(), - address.address().to_hex_string(), + address.workchain as i32, + address.address.to_string(), ) .await?; @@ -193,7 +192,7 @@ impl TonService { let PrepareResult { sent_transaction, owned_message, - expired_at, + expire_at, } = self .ton_api_client .prepare_transaction( @@ -217,7 +216,7 @@ impl TonService { owned_message, true, true, - expired_at, + expire_at, ) .await?; @@ -232,14 +231,14 @@ impl TonService { service_id: &ServiceId, input: TransactionConfirm, ) -> Result { - let address = repack_address(&input.address.0)?; + let address = StdAddr::from_str(&input.address.0).map_err(anyhow::Error::from)?; let address_db = self .sqlx_client .get_address( *service_id, - address.workchain_id(), - address.address().to_hex_string(), + address.workchain as i32, + address.address.to_string(), ) .await?; @@ -259,23 +258,28 @@ impl TonService { .await?; } - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_confirm_transaction(input, &public_key, &private_key) .await?; let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -542,19 +546,20 @@ impl TonService { service_id: &ServiceId, address: &Address, ) -> Result, Error> { - let account = repack_address(&address.0)?; + let account = StdAddr::from_str(&address.0).map_err(anyhow::Error::from)?; let balances = self .sqlx_client .get_token_balances( *service_id, - account.workchain_id(), - account.address().to_hex_string(), + account.workchain as i32, + account.address.to_string(), ) .await?; let mut result = Vec::with_capacity(balances.len()); for balance in balances { - let root_address = repack_address(&balance.root_address)?; + let root_address = + StdAddr::from_str(&balance.root_address).map_err(anyhow::Error::from)?; let network = self .ton_api_client @@ -577,13 +582,13 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = repack_address(&input.from_address.0)?; + let owner = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; let address_db = self .sqlx_client .get_address( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), ) .await?; @@ -600,8 +605,8 @@ impl TonService { .sqlx_client .get_token_balance( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), input.root_address.0.clone(), ) .await?; @@ -627,7 +632,11 @@ impl TonService { .await?; } - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_token_transaction( input, @@ -640,16 +649,17 @@ impl TonService { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -669,13 +679,13 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = repack_address(&input.from_address.0)?; + let owner = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; let address_db = self .sqlx_client .get_address( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), ) .await?; @@ -692,8 +702,8 @@ impl TonService { .sqlx_client .get_token_balance( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), input.root_address.0.clone(), ) .await?; @@ -719,7 +729,11 @@ impl TonService { .await?; } - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_token_burn( input, @@ -732,16 +746,17 @@ impl TonService { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -766,13 +781,13 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = repack_address(&input.owner_address.0)?; + let owner = StdAddr::from_str(&input.owner_address.0).map_err(anyhow::Error::from)?; let address_db = self .sqlx_client .get_address( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), ) .await?; @@ -790,7 +805,11 @@ impl TonService { let public_key = hex::decode(address_db.public_key.clone())?; let private_key = decrypt_private_key(&address_db.private_key, key, &address_db.id)?; - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_token_mint( input, @@ -803,16 +822,17 @@ impl TonService { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -869,7 +889,7 @@ impl TonService { headers: Vec, responsible: bool, ) -> Result { - let account_addr = UInt256::from_str(account_addr)?; + let account_addr = HashBytes::from_str(&account_addr).map_err(anyhow::Error::from)?; let input_params: Vec = inputs.iter().map(|x| x.param.clone()).collect(); @@ -956,7 +976,7 @@ impl TonService { let public_key = hex::decode(address_db.public_key.clone())?; let private_key = decrypt_private_key(&address_db.private_key, key, &address_db.id)?; - let signed_message = self + let (owned_message, expire_at) = self .ton_api_client .prepare_signed_generic_message( sender_addr, @@ -973,9 +993,12 @@ impl TonService { ) .await?; + let cell_builder = CellBuilder::build_from(owned_message).map_err(anyhow::Error::from)?; + let message_hash = *cell_builder.repr_hash(); + let sent_transaction = SentTransaction { id: transaction_id, - message_hash: signed_message.message.hash()?.to_hex_string(), + message_hash: message_hash.to_string(), account_workchain_id: sender.workchain_id(), account_hex: sender.address().to_hex_string(), original_value: Some(value), @@ -993,9 +1016,10 @@ impl TonService { transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -1016,7 +1040,7 @@ impl TonService { account_type: &AccountType, custodians: &Option, function_details: Option, - ) -> Result, Error> { + ) -> Result { let (function, values) = match function_details { Some(details) => { let function = nekoton_abi::FunctionBuilder::new(&details.function_name) @@ -1087,31 +1111,35 @@ impl TonService { self: &Arc, sender_addr: String, hash: String, - msg: SignedMessage, + owned_message: OwnedMessage, + expire_at: u32, ) -> Result { - let addr = MsgAddressInt::from_str(&sender_addr)?; + let addr = StdAddr::from_str(&sender_addr).map_err(anyhow::Error::from)?; self.ton_api_client - .add_ton_account_subscription(addr.hash()?); + .add_ton_account_subscription(addr.address); + + let cell_builder = + CellBuilder::build_from(owned_message.clone()).map_err(anyhow::Error::from)?; + let message_hash = *cell_builder.repr_hash(); self.send_transaction( hash, - addr.address().to_hex_string(), - addr.workchain_id(), - msg.clone(), + addr.address.to_string(), + addr.workchain as i32, + owned_message, true, false, + expire_at, ) .await?; - let hash = msg.message.hash().map(|x| x.to_hex_string())?; - - Ok(hash) + Ok(message_hash.to_string()) } pub async fn add_account_subscription(self: &Arc, address: String) -> Result<(), Error> { - let address = MsgAddressInt::from_str(&address)?; + let addr = StdAddr::from_str(&address).map_err(anyhow::Error::from)?; self.ton_api_client - .add_ton_account_subscription(address.hash()?); + .add_ton_account_subscription(addr.address); Ok(()) } @@ -1186,10 +1214,10 @@ impl TonService { message_hash: String, account_hex: String, account_workchain_id: i32, - signed_message: OwnedMessage, + owned_message: OwnedMessage, non_blocking: bool, with_db_update: bool, - expired_at: u32, + expire_at: u32, ) -> Result<(), Error> { let ton_service = Arc::downgrade(self); @@ -1200,8 +1228,9 @@ impl TonService { message_hash, account_hex, account_workchain_id, - signed_message, + owned_message, with_db_update, + expire_at, ) .await? } @@ -1213,8 +1242,9 @@ impl TonService { message_hash, account_hex, account_workchain_id, - signed_message, + owned_message, with_db_update, + expire_at, ), ); } @@ -1235,19 +1265,25 @@ impl TonService { .prepare_deploy(address, public_key, private_key) .await?; - if let Some((payload, signed_message)) = payload { + if let Some(PrepareResult { + sent_transaction, + owned_message, + expire_at, + }) = payload + { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash, transaction.account_hex, transaction.account_workchain_id, - signed_message, + owned_message, false, true, + expire_at, ) .await?; @@ -1367,8 +1403,9 @@ async fn send_transaction( message_hash: String, account_hex: String, account_workchain_id: i32, - signed_message: OwnedMessage, + owned_message: OwnedMessage, with_db_update: bool, + expire_at: u32, ) -> Result<(), Error> { let ton_service = match ton_service.upgrade() { Some(ton_service) => ton_service, @@ -1379,7 +1416,7 @@ async fn send_transaction( let status = ton_service .ton_api_client - .send_transaction(account, signed_message) + .send_transaction(account, owned_message, expire_at) .await?; if status == MessageStatus::Expired && with_db_update { From 4b7db010d0598afb87f623dd3d4827b47f424b35 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Tue, 3 Feb 2026 17:20:57 +0300 Subject: [PATCH 18/59] fix wallets --- src/client/ton/mod.rs | 36 +++++++++++----------- src/services/ton.rs | 23 ++++++-------- src/utils/ton_wallet/ever_wallet.rs | 9 +++--- src/utils/ton_wallet/highload_wallet_v2.rs | 12 ++++---- src/utils/ton_wallet/mod.rs | 4 +-- src/utils/ton_wallet/multisig.rs | 34 +++++++++++--------- src/utils/ton_wallet/wallet_v3.rs | 12 ++++---- src/utils/ton_wallet/wallet_v3v4.rs | 12 ++++---- src/utils/ton_wallet/wallet_v5r1.rs | 24 +++++++-------- 9 files changed, 83 insertions(+), 83 deletions(-) diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index dd71e7d..84dab58 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -202,10 +202,10 @@ impl TonClient { .into_iter() .map(|item| { let mut key = [0u8; 32]; - key.copy_from_slice(&hex::decode(item).map_err(From::from)?); - VerifyingKey::from_bytes(&key).map_err(From::from) + key.copy_from_slice(&hex::decode(item).map_err(anyhow::Error::from)?); + VerifyingKey::from_bytes(&key).map_err(anyhow::Error::from) }) - .collect::, Error>>()?; + .collect::, anyhow::Error>>()?; ton_wallet::multisig::prepare_deploy( &public_key, @@ -236,7 +236,7 @@ impl TonClient { let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; - let cell_builder = CellBuilder::build_from(&owned_message).map_err(From::from)?; + let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { @@ -274,7 +274,7 @@ impl TonClient { let public_key = VerifyingKey::from_bytes(&key)?; - let address = StdAddr::from_str(&transaction.from_address.0).map_err(From::from)?; + let address = StdAddr::from_str(&transaction.from_address.0).map_err(anyhow::Error::from)?; let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; @@ -283,7 +283,7 @@ impl TonClient { .payload .map(|s| Boc::decode_base64(s)) .transpose() - .map_err(From::from)?; + .map_err(anyhow::Error::from)?; let unsigned_message = match account_type { AccountType::HighloadWallet => { @@ -294,7 +294,7 @@ impl TonClient { for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); let destination = - StdAddr::from_str(&item.recipient_address.0).map_err(From::from)?; + StdAddr::from_str(&item.recipient_address.0).map_err(anyhow::Error::from)?; let amount = item .value .to_u128() @@ -305,7 +305,7 @@ impl TonClient { bounce, destination, amount, - body, + body: body.clone(), state_init: None, }); } @@ -325,7 +325,7 @@ impl TonClient { .first() .ok_or(TonClientError::RecipientNotFound)?; let destination = - StdAddr::from_str(&recipient.recipient_address.0).map_err(From::from)?; + StdAddr::from_str(&recipient.recipient_address.0).map_err(anyhow::Error::from)?; let amount = recipient .value .to_u128() @@ -356,7 +356,7 @@ impl TonClient { .first() .ok_or(TonClientError::RecipientNotFound)?; let destination = - StdAddr::from_str(&recipient.recipient_address.0).map_err(From::from)?; + StdAddr::from_str(&recipient.recipient_address.0).map_err(anyhow::Error::from)?; let amount = recipient .value .to_u128() @@ -392,7 +392,7 @@ impl TonClient { for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); let destination = - StdAddr::from_str(&item.recipient_address.0).map_err(From::from)?; + StdAddr::from_str(&item.recipient_address.0).map_err(anyhow::Error::from)?; let amount = item .value .to_u128() @@ -402,7 +402,7 @@ impl TonClient { bounce, destination, amount, - body, + body: body.clone(), state_init: None, }); } @@ -423,7 +423,7 @@ impl TonClient { let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; - let cell_builder = CellBuilder::build_from(&owned_message).map_err(From::from)?; + let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let message_hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { @@ -454,7 +454,7 @@ impl TonClient { let public_key = VerifyingKey::from_bytes(&key)?; - let address = StdAddr::from_str(&transaction.address.0).map_err(From::from)?; + let address = StdAddr::from_str(&transaction.address.0).map_err(anyhow::Error::from)?; let account_workchain_id = address.workchain as i32; let account_hex = address.address.to_string(); @@ -476,7 +476,7 @@ impl TonClient { let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; - let cell_builder = CellBuilder::build_from(&owned_message).map_err(From::from)?; + let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let message_hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { @@ -861,7 +861,7 @@ impl TonClient { let public_key = VerifyingKey::from_bytes(&key)?; - let address = StdAddr::from_str(&sender_addr).map_err(From::from)?; + let address = StdAddr::from_str(&sender_addr).map_err(anyhow::Error::from)?; let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; @@ -874,7 +874,7 @@ impl TonClient { .transpose() .map_err(anyhow::Error::from)?; - let destination = StdAddr::from_str(&target_addr).map_err(From::from)?; + let destination = StdAddr::from_str(&target_addr).map_err(anyhow::Error::from)?; let amount = value.to_u128().ok_or(TonClientError::ParseBigDecimal)?; let unsigned_message = match account_type { AccountType::Wallet => { @@ -1132,7 +1132,7 @@ fn build_token_transaction( let owned_message = unsigned_message.sign(&key_pair, ton_core.signature_id())?; - let cell_builder = CellBuilder::build_from(&owned_message).map_err(From::from)?; + let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { diff --git a/src/services/ton.rs b/src/services/ton.rs index 844bfa3..ea7784a 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -5,13 +5,9 @@ use std::sync::{Arc, Weak}; use axum::http::StatusCode; use bigdecimal::BigDecimal; use chrono::Utc; -use nekoton::crypto::{SignedMessage, UnsignedMessage}; -use nekoton_utils::{repack_address, unpack_std_smc_addr, TrustMe}; use serde_json::Value; use ton_abi::contract::ABI_VERSION_2_2; use ton_abi::{Param, Token, TokenValue}; -use ton_block::{GetRepresentationHash, MsgAddressInt, Serializable}; -use ton_types::{BuilderData, UInt256}; use tycho_types::abi::UnsignedExternalMessage; use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{OwnedMessage, StdAddr}; @@ -100,9 +96,7 @@ impl TonService { } pub async fn check_address(&self, address: Address) -> Result { - Ok(MsgAddressInt::from_str(&address.0).is_ok() - || (unpack_std_smc_addr(&address.0, false).is_ok()) - || (unpack_std_smc_addr(&address.0, true).is_ok())) + Ok(StdAddr::from_str(&address.0).is_ok()) } pub async fn get_address_balance( @@ -960,14 +954,15 @@ impl TonService { None => (None, None), }; - let sender = repack_address(sender_addr)?; + + let sender = StdAddr::from_str(&sender_addr).map_err(anyhow::Error::from)?; let address_db = self .sqlx_client .get_address( *service_id, - sender.workchain_id(), - sender.address().to_hex_string(), + sender.workchain as i32, + sender.address.to_string(), ) .await?; @@ -999,8 +994,8 @@ impl TonService { let sent_transaction = SentTransaction { id: transaction_id, message_hash: message_hash.to_string(), - account_workchain_id: sender.workchain_id(), - account_hex: sender.address().to_hex_string(), + account_workchain_id: sender.workchain as i32, + account_hex: sender.address.to_string(), original_value: Some(value), original_outputs: None, aborted: false, @@ -1151,7 +1146,7 @@ impl TonService { .into_iter() .map(|item| { let mut result = HashBytes::default(); - hex::decode_to_slice(item.hex, &mut result.0).trust_me(); + hex::decode_to_slice(item.hex, &mut result.0).unwrap(); result }); @@ -1172,7 +1167,7 @@ impl TonService { } let addresses = address_dbs.into_iter().map(|item| { let mut result = HashBytes::default(); - hex::decode_to_slice(item.hex, &mut result.0).trust_me(); + hex::decode_to_slice(item.hex, &mut result.0).unwrap(); result }); diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs index 1531e7d..3cdc2bc 100644 --- a/src/utils/ton_wallet/ever_wallet.rs +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use ed25519_dalek::VerifyingKey; use tycho_types::{ abi::{AbiHeaderType, AbiValue, AbiVersion, Function, NamedAbiType, UnsignedExternalMessage}, cell::{CellBuilder, HashBytes}, @@ -12,7 +13,7 @@ use crate::utils::ton_wallet::{Gift, TonWalletDetails}; use crate::utils::wallets::code::ever_wallet; pub fn prepare_deploy( - public_key: &PublicKey, + public_key: &VerifyingKey, workchain: i8, expire_at: u32, ) -> Result { @@ -44,7 +45,7 @@ pub fn prepare_deploy( } pub fn prepare_transfer( - public_key: &PublicKey, + public_key: &VerifyingKey, current_state: &Account, address: StdAddr, gifts: Vec, @@ -128,14 +129,14 @@ pub fn is_ever_wallet(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { +pub fn compute_contract_address(public_key: &VerifyingKey, workchain_id: i8) -> Result { let state = prepare_state_init(public_key)?; let binding = CellBuilder::build_from(state)?; let hash = binding.repr_hash(); Ok(StdAddr::new(workchain_id, *hash)) } -pub fn prepare_state_init(public_key: &PublicKey) -> Result { +pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { let mut builder = CellBuilder::new(); builder.store_u256(&HashBytes::from(public_key.to_bytes()))?; builder.store_u64(0)?; diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 15e37d6..5c75348 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use anyhow::Result; -use ed25519_dalek::PublicKey; +use ed25519_dalek::VerifyingKey; use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, CellFamily, HashBytes, Load, Store}, @@ -17,7 +17,7 @@ use crate::utils::wallets::code::highload_wallet_v2; use super::{Gift, TonWalletDetails}; pub fn prepare_deploy( - public_key: &PublicKey, + public_key: &VerifyingKey, workchain: i8, expire_at: u32, ) -> Result { @@ -36,13 +36,13 @@ pub fn prepare_deploy( Ok(unsigned_message) } -pub fn prepare_state_init(public_key: &PublicKey) -> Result { +pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); init_data.make_state_init() } pub fn prepare_transfer( - public_key: &PublicKey, + public_key: &VerifyingKey, current_state: &Account, gifts: Vec, expire_at: u32, @@ -107,7 +107,7 @@ pub fn is_highload_wallet_v2(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { +pub fn compute_contract_address(public_key: &VerifyingKey, workchain_id: i8) -> Result { InitData::from_key(public_key) .with_wallet_id(WALLET_ID) .compute_addr(workchain_id) @@ -139,7 +139,7 @@ impl InitData { &self.public_key.0 } - pub fn from_key(key: &PublicKey) -> Self { + pub fn from_key(key: &VerifyingKey) -> Self { Self { wallet_id: 0, last_cleaned: 0, diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs index 84fd640..5062023 100644 --- a/src/utils/ton_wallet/mod.rs +++ b/src/utils/ton_wallet/mod.rs @@ -2,7 +2,7 @@ use std::num::NonZeroU8; use std::str::FromStr; use anyhow::Result; -use ed25519_dalek::PublicKey; +use ed25519_dalek::VerifyingKey; use serde::{Deserialize, Serialize}; use nekoton_utils::*; @@ -185,7 +185,7 @@ impl std::fmt::Display for WalletType { } pub fn compute_address( - public_key: &PublicKey, + public_key: &VerifyingKey, wallet_type: WalletType, workchain_id: i8, ) -> Result { diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index a623614..a478d9c 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::convert::TryFrom; use anyhow::Result; -use ed25519_dalek::PublicKey; +use ed25519_dalek::VerifyingKey; use tycho_types::{ abi::{AbiValue, FromAbi, Function, IntoAbi, NamedAbiValue, UnsignedExternalMessage}, cell::{Cell, CellBuilder, CellDataBuilder, HashBytes, Load}, @@ -17,13 +17,13 @@ use super::{Gift, TonWalletDetails}; #[derive(Copy, Clone, Debug)] pub struct DeployParams<'a> { - pub owners: &'a [PublicKey], + pub owners: &'a [VerifyingKey], pub req_confirms: u8, pub expiration_time: Option, } impl<'a> DeployParams<'a> { - pub fn single_custodian(pubkey: &'a PublicKey) -> Self { + pub fn single_custodian(pubkey: &'a VerifyingKey) -> Self { Self { owners: std::slice::from_ref(pubkey), req_confirms: 1, @@ -33,7 +33,7 @@ impl<'a> DeployParams<'a> { } pub fn prepare_deploy( - public_key: &PublicKey, + public_key: &VerifyingKey, multisig_type: MultisigType, workchain: i8, expire_at: u32, @@ -84,7 +84,7 @@ pub fn prepare_deploy( pub fn prepare_confirm_transaction( multisig_type: MultisigType, - public_key: &PublicKey, + public_key: &VerifyingKey, address: StdAddr, transaction_id: u64, expire_at: u32, @@ -106,7 +106,7 @@ pub fn prepare_confirm_transaction( pub fn prepare_transfer( multisig_type: MultisigType, - public_key: &PublicKey, + public_key: &VerifyingKey, has_multiple_owners: bool, address: StdAddr, gift: Gift, @@ -166,7 +166,7 @@ pub fn prepare_transfer( pub fn prepare_code_update( multisig_type: MultisigType, - public_key: &PublicKey, + public_key: &VerifyingKey, address: StdAddr, new_code_hash: &[u8; 32], expire_at: u32, @@ -194,7 +194,7 @@ pub fn prepare_code_update( pub fn prepare_confirm_update( multisig_type: MultisigType, - public_key: &PublicKey, + public_key: &VerifyingKey, address: StdAddr, update_id: u64, expire_at: u32, @@ -216,7 +216,7 @@ pub fn prepare_confirm_update( pub fn prepare_execute_update( multisig_type: MultisigType, - public_key: &PublicKey, + public_key: &VerifyingKey, update_id: u64, code: Option, address: StdAddr, @@ -389,7 +389,7 @@ pub fn guess_multisig_type(code_hash: &HashBytes) -> Option { } pub fn compute_contract_address( - public_key: &PublicKey, + public_key: &VerifyingKey, multisig_type: MultisigType, workchain_id: i8, ) -> Result { @@ -427,7 +427,7 @@ pub fn ton_wallet_details(multisig_type: MultisigType) -> TonWalletDetails { } pub fn prepare_state_init( - public_key: &PublicKey, + public_key: &VerifyingKey, multisig_type: MultisigType, ) -> Result { let mut state_init = multisig_type.state_init()?; @@ -780,7 +780,7 @@ fn extend_pending_update( } fn make_ext_message( - public_key: &PublicKey, + public_key: &VerifyingKey, address: StdAddr, expire_at: u32, function: &'static Function, @@ -823,9 +823,13 @@ mod tests { #[test] fn correct_address() { - let key = PublicKey::from_bytes( - &hex::decode("5ace46d93d8f3932499df9f2bc7ef787385e16965e7797258948febd186de7f6") - .unwrap(), + let key = hex::decode("5ace46d93d8f3932499df9f2bc7ef787385e16965e7797258948febd186de7f6") + .unwrap(); + + let key = key.try_into().unwrap(); + + let key = VerifyingKey::from_bytes( + &key, ) .unwrap(); diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index b454a44..8dd4a50 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use anyhow::Result; -use ed25519_dalek::PublicKey; +use ed25519_dalek::VerifyingKey; use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes}, @@ -18,7 +18,7 @@ use crate::utils::{ }; pub fn prepare_deploy( - public_key: &PublicKey, + public_key: &VerifyingKey, workchain: i8, expire_at: u32, ) -> Result { @@ -38,7 +38,7 @@ pub fn prepare_deploy( Ok(unsigned_message) } -pub fn prepare_state_init(public_key: &PublicKey) -> Result { +pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); init_data.make_state_init() } @@ -87,7 +87,7 @@ pub fn estimate_seqno_offset( } pub fn prepare_transfer( - public_key: &PublicKey, + public_key: &VerifyingKey, current_state: &Account, seqno_offset: u32, gifts: Vec, @@ -151,7 +151,7 @@ pub fn is_wallet_v3(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { +pub fn compute_contract_address(public_key: &VerifyingKey, workchain_id: i8) -> Result { let state_init = InitData::from_key(public_key) .with_wallet_id(WALLET_ID) .make_state_init()?; @@ -187,7 +187,7 @@ impl InitData { &self.public_key.0 } - pub fn from_key(key: &PublicKey) -> Self { + pub fn from_key(key: &VerifyingKey) -> Self { Self { seqno: 0, wallet_id: 0, diff --git a/src/utils/ton_wallet/wallet_v3v4.rs b/src/utils/ton_wallet/wallet_v3v4.rs index a5ebaab..08a5f3f 100644 --- a/src/utils/ton_wallet/wallet_v3v4.rs +++ b/src/utils/ton_wallet/wallet_v3v4.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use anyhow::Result; -use ed25519_dalek::PublicKey; +use ed25519_dalek::VerifyingKey; use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes}, @@ -17,7 +17,7 @@ use crate::utils::{ }; pub fn prepare_deploy( - public_key: &PublicKey, + public_key: &VerifyingKey, workchain: i8, expire_at: u32, version: WalletVersion, @@ -38,13 +38,13 @@ pub fn prepare_deploy( Ok(unsigned_message) } -pub fn prepare_state_init(public_key: &PublicKey, version: WalletVersion) -> Result { +pub fn prepare_state_init(public_key: &VerifyingKey, version: WalletVersion) -> Result { let init_data = InitData::from_key(public_key).with_subwallet_id(WALLET_ID); init_data.make_state_init(version) } pub fn prepare_transfer( - public_key: &PublicKey, + public_key: &VerifyingKey, current_state: &Account, seqno_offset: u32, gifts: Vec, @@ -139,7 +139,7 @@ pub fn is_wallet_v4r2(code_hash: &HashBytes) -> bool { } pub fn compute_contract_address( - public_key: &PublicKey, + public_key: &VerifyingKey, workchain_id: i8, version: WalletVersion, ) -> Result { @@ -175,7 +175,7 @@ impl InitData { &self.public_key.0 } - pub fn from_key(key: &PublicKey) -> Self { + pub fn from_key(key: &VerifyingKey) -> Self { Self { seqno: 0, wallet_id: 0, diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index feb0295..8d425c8 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use anyhow::Result; -use ed25519_dalek::PublicKey; +use ed25519_dalek::VerifyingKey; use tycho_types::{ abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes, Lazy, Load}, @@ -20,7 +20,7 @@ const SIGNED_EXTERNAL_PREFIX: u32 = 0x7369676E; const SIGNED_INTERNAL_PREFIX: u32 = 0x73696E74; pub fn prepare_deploy( - public_key: &PublicKey, + public_key: &VerifyingKey, workchain: i8, expire_at: u32, ) -> Result { @@ -39,18 +39,18 @@ pub fn prepare_deploy( Ok(unsigned_message) } -pub fn prepare_state_init(public_key: &PublicKey) -> Result { +pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { let init_data = make_init_data(public_key); init_data.make_state_init() } -pub fn make_init_data(public_key: &PublicKey) -> InitData { +pub fn make_init_data(public_key: &VerifyingKey) -> InitData { InitData::from_key(public_key) .with_wallet_id(WALLET_ID) .with_is_signature_allowed(true) } -pub fn get_init_data(current_state: &Account, public_key: &PublicKey) -> Result<(InitData, bool)> { +pub fn get_init_data(current_state: &Account, public_key: &VerifyingKey) -> Result<(InitData, bool)> { match current_state.state { AccountState::Active(ref state_init) => match &state_init.data { Some(data) => Ok((InitData::try_from(data)?, false)), @@ -69,7 +69,7 @@ pub fn get_init_data_from_state_init(init: &StateInit) -> Result { } pub fn prepare_transfer( - public_key: &PublicKey, + public_key: &VerifyingKey, current_state: &Account, seqno_offset: u32, gifts: Vec, @@ -123,7 +123,7 @@ pub fn is_wallet_v5r1(code_hash: &HashBytes) -> bool { code_hash.as_slice() == CODE_HASH } -pub fn compute_contract_address(public_key: &PublicKey, workchain_id: i8) -> Result { +pub fn compute_contract_address(public_key: &VerifyingKey, workchain_id: i8) -> Result { make_init_data(public_key).compute_addr(workchain_id) } @@ -156,7 +156,7 @@ impl InitData { &self.public_key.0 } - pub fn from_key(key: &PublicKey) -> Self { + pub fn from_key(key: &VerifyingKey) -> Self { Self { is_signature_allowed: false, seqno: 0, @@ -317,7 +317,7 @@ enum WalletV5Error { #[cfg(test)] mod tests { - use ed25519_dalek::PublicKey; + use ed25519_dalek::VerifyingKey; use tycho_types::{ boc::Boc, cell::Load, @@ -346,7 +346,7 @@ mod tests { assert_eq!(init_data.wallet_id, WALLET_ID); assert_eq!(init_data.extensions, None); - let public_key = PublicKey::from_bytes(init_data.public_key.as_slice())?; + let public_key = VerifyingKey::from_bytes(init_data.public_key.as_slice().try_into().unwrap())?; let address = compute_contract_address(&public_key, 0)?; assert_eq!( address.to_string(), @@ -376,7 +376,7 @@ mod tests { // let in_msg_body = ton_types::deserialize_tree_of_cells(&mut payload.as_slice())?; // let in_msg_body_slice = SliceData::load_cell(in_msg_body)?; // - // let public_key = PublicKey::from_bytes(public_key_bytes.as_slice())?; + // let public_key = VerifyingKey::from_bytes(public_key_bytes.as_slice())?; // // let result = check_signature(in_msg_body_slice, public_key, Some(2000))?; // assert!(result); @@ -385,7 +385,7 @@ mod tests { // // fn check_signature( // mut in_msg_body: SliceData, - // public_key: PublicKey, + // public_key: VerifyingKey, // signature_id: Option, // ) -> anyhow::Result { // let signature_binding = in_msg_body From 4524ffb87801c00f65fe57e392dc013730e8fce1 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 5 Feb 2026 10:15:06 +0300 Subject: [PATCH 19/59] fix token wallets --- src/client/ton/mod.rs | 19 +- src/services/ton.rs | 1 - src/ton_core/monitoring/token_transaction.rs | 13 +- .../monitoring/token_transaction_parser.rs | 2 +- src/ton_core/ton_subscriber/mod.rs | 62 ++--- src/utils/mod.rs | 72 +++++ src/utils/token_wallet.rs | 120 ++++---- src/utils/token_wallets/mod.rs | 258 +++++++++++++++++ src/utils/token_wallets/models.rs | 263 ++++++++++++++++++ src/utils/ton_wallet/multisig.rs | 9 +- src/utils/ton_wallet/wallet_v5r1.rs | 8 +- src/utils/tx_context.rs | 119 +++----- 12 files changed, 748 insertions(+), 198 deletions(-) create mode 100644 src/utils/token_wallets/mod.rs create mode 100644 src/utils/token_wallets/models.rs diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 84dab58..1fd4908 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -274,7 +274,8 @@ impl TonClient { let public_key = VerifyingKey::from_bytes(&key)?; - let address = StdAddr::from_str(&transaction.from_address.0).map_err(anyhow::Error::from)?; + let address = + StdAddr::from_str(&transaction.from_address.0).map_err(anyhow::Error::from)?; let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; @@ -293,8 +294,8 @@ impl TonClient { let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); - let destination = - StdAddr::from_str(&item.recipient_address.0).map_err(anyhow::Error::from)?; + let destination = StdAddr::from_str(&item.recipient_address.0) + .map_err(anyhow::Error::from)?; let amount = item .value .to_u128() @@ -324,8 +325,8 @@ impl TonClient { .outputs .first() .ok_or(TonClientError::RecipientNotFound)?; - let destination = - StdAddr::from_str(&recipient.recipient_address.0).map_err(anyhow::Error::from)?; + let destination = StdAddr::from_str(&recipient.recipient_address.0) + .map_err(anyhow::Error::from)?; let amount = recipient .value .to_u128() @@ -355,8 +356,8 @@ impl TonClient { .outputs .first() .ok_or(TonClientError::RecipientNotFound)?; - let destination = - StdAddr::from_str(&recipient.recipient_address.0).map_err(anyhow::Error::from)?; + let destination = StdAddr::from_str(&recipient.recipient_address.0) + .map_err(anyhow::Error::from)?; let amount = recipient .value .to_u128() @@ -391,8 +392,8 @@ impl TonClient { let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); - let destination = - StdAddr::from_str(&item.recipient_address.0).map_err(anyhow::Error::from)?; + let destination = StdAddr::from_str(&item.recipient_address.0) + .map_err(anyhow::Error::from)?; let amount = item .value .to_u128() diff --git a/src/services/ton.rs b/src/services/ton.rs index ea7784a..5d93119 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -954,7 +954,6 @@ impl TonService { None => (None, None), }; - let sender = StdAddr::from_str(&sender_addr).map_err(anyhow::Error::from)?; let address_db = self diff --git a/src/ton_core/monitoring/token_transaction.rs b/src/ton_core/monitoring/token_transaction.rs index 6dff406..aa5a193 100644 --- a/src/ton_core/monitoring/token_transaction.rs +++ b/src/ton_core/monitoring/token_transaction.rs @@ -1,12 +1,13 @@ use std::sync::Arc; use anyhow::Result; -use nekoton::core::models::*; + use tokio::sync::mpsc; use ton_types::UInt256; use crate::ton_core::monitoring::*; use crate::ton_core::*; +use crate::utils::token_wallets::models::TokenWalletTransaction; pub struct TokenTransaction { context: Arc, @@ -80,13 +81,13 @@ impl TokenTransaction { #[derive(Debug)] pub struct TokenTransactionContext { - pub account: UInt256, - pub block_hash: UInt256, + pub account: HashBytes, + pub block_hash: HashBytes, pub block_utime: u32, - pub transaction_hash: UInt256, - pub transaction: ton_block::Transaction, + pub transaction_hash: HashBytes, + pub transaction: Transaction, pub token_state: ExistingContract, - pub in_msg: ton_block::Message, + pub in_msg: OwnedMessage, } #[derive(Debug)] diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index c0a5463..760c5cf 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -1,12 +1,12 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton::core::models::{TokenIncomingTransfer, TokenWalletTransaction}; use num_bigint::BigUint; use ton_block::{GetRepresentationHash, MsgAddressInt}; use ton_types::{AccountId, BuilderData}; use uuid::Uuid; use crate::ton_core::*; +use crate::utils::token_wallets::models::{TokenIncomingTransfer, TokenWalletTransaction}; struct ParseContext<'a> { sqlx_client: &'a SqlxClient, diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index 91e7d91..eaa0051 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -340,7 +340,7 @@ impl StateSubscription { let hash = *value.repr_hash(); value.load().map(|tx| (tx, hash)) }); - let (transaction, hash) = match result { + let (transaction, transaction_hash) = match result { Ok((tx, transaction_hash)) => (tx, transaction_hash), Err(e) => { tracing::error!( @@ -353,33 +353,30 @@ impl StateSubscription { } }; - let account = UInt256::with_array(account.0); - let transaction_hash = UInt256::with_array(hash.0); - let block_hash = UInt256::with_array(block_hash.0); - let transaction = conver_to_old_transaction(&transaction)?; + let account = *account; + let block_hash = *block_hash; // Skip non-ordinary transactions - let transaction_info = match transaction.description.read_struct() { - Ok(ton_block::TransactionDescr::Ordinary(info)) => info, + let transaction_info = match transaction.load_info() { + Ok(TxInfo::Ordinary(info)) => info, _ => continue, }; - let in_msg = match transaction - .in_msg - .as_ref() - .map(|message| (message, message.read_struct())) + let in_msg = match transaction.in_msg.as_ref() { - Some((message_cell, Ok(message))) => { - if matches!(message.header(), ton_block::CommonMsgInfo::ExtInMsgInfo(_)) { + Some(in_msg_cell) => { + let in_msg = OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)?; + if matches!(in_msg.info, MsgInfo::ExtIn(_)) { messages_queue.deliver_message( - HashBytes::from_slice(account.as_slice()), - HashBytes::from_slice(message_cell.hash().as_slice()), + account, + *CellBuilder::build_from(&in_msg)?.repr_hash(), ); } - message + in_msg } _ => continue, }; + let ctx = TxContext { block_info_gen_utime: block_info.gen_utime, block_hash: &block_hash, @@ -402,7 +399,7 @@ impl StateSubscription { Err(e) => { tracing::error!( "Failed to handle transaction {} for account {}: {:?}", - hash.to_string(), + transaction_hash.to_string(), account.to_string(), e ); @@ -436,7 +433,7 @@ impl TokenSubscription { let hash = *value.repr_hash(); value.load().map(|tx| (tx, hash)) }); - let (transaction, hash) = match result { + let (transaction, transaction_hash) = match result { Ok((tx, transaction_hash)) => (tx, transaction_hash), Err(e) => { tracing::error!( @@ -449,13 +446,11 @@ impl TokenSubscription { } }; - let account = UInt256::with_array(account.0); - let transaction_hash = UInt256::with_array(hash.0); - let block_hash = UInt256::with_array(block_hash.0); - let transaction = conver_to_old_transaction(&transaction)?; + let account = *account; + let block_hash = *block_hash; // Skip non-ordinary transactions - let transaction_info = match transaction.description.read_struct() { - Ok(ton_block::TransactionDescr::Ordinary(info)) => info, + let transaction_info = match transaction.load_info() { + Ok(TxInfo::Ordinary(info)) => info, _ => continue, }; @@ -478,23 +473,20 @@ impl TokenSubscription { .ok_or_else(|| TonCoreError::AccountNotExist(account.to_string()))?; let (token_wallet_details, ..) = get_token_wallet_details(&token_contract)?; - let owner_account = UInt256::from_be_bytes( + let owner_account = &token_wallet_details .owner_address - .address() - .get_bytestring(0), - ); + ; if state_subscriptions - .get(&HashBytes::from_slice(owner_account.as_slice())) + .get(owner_account) .is_some() { - let in_msg = match transaction - .in_msg - .as_ref() - .map(|message| (message, message.read_struct())) + let in_msg = match transaction.in_msg.as_ref() { - Some((_, Ok(message))) => message, + Some(in_msg_cell) => { + OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)? + } _ => continue, }; @@ -521,7 +513,7 @@ impl TokenSubscription { Err(e) => { tracing::error!( "Failed to handle token transaction {} for account {}: {:?}", - hash.to_string(), + transaction_hash.to_string(), account.to_string(), e ); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 0f46f19..fc9e970 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -20,6 +20,7 @@ pub mod mnemonic; mod pending_messages_queue; mod shard_utils; mod token_wallet; +pub mod token_wallets; pub mod ton_wallet; mod tx_context; mod wallets; @@ -83,3 +84,74 @@ macro_rules! declare_function { } pub(crate) use declare_function; + +pub mod serde_string { + use std::str::FromStr; + + use serde::de::Error; + use serde::{Deserialize, Serialize}; + + pub fn serialize(data: &dyn std::fmt::Display, serializer: S) -> Result + where + S: serde::Serializer, + { + data.to_string().serialize(serializer) + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + T: FromStr, + T::Err: std::fmt::Display, + { + String::deserialize(deserializer) + .and_then(|data| T::from_str(&data).map_err(D::Error::custom)) + } +} + +pub mod serde_address { + use std::str::FromStr; + + use serde::de::Error; + use serde::Deserialize; + use tycho_types::models::StdAddr; + + pub fn serialize(data: &StdAddr, 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)?; + StdAddr::from_str(&data).map_err(|_| D::Error::custom("Invalid address")) + } +} + +pub mod serde_cell { + use serde::de::Error; + use serde::Deserialize; + use tycho_types::boc::Boc; + use tycho_types::cell::Cell; + + pub fn serialize(data: &Cell, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let bytes = Boc::encode_base64(data); + serializer.serialize_str(&bytes) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data = String::deserialize(deserializer)?; + let cell = Boc::decode_base64(&data).map_err(|_| D::Error::custom("Invalid cell"))?; + Ok(cell) + } +} diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index 3af0322..df204be 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -1,31 +1,25 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton::core::models::{RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion}; -use nekoton_abi::{BigUint128, BigUint256, ExecutionContext, MessageBuilder}; +use nekoton_abi::ExecutionContext; use nekoton_contracts::tip3_any::{RootTokenContractState, TokenWalletContractState}; -use nekoton_contracts::{old_tip3, tip3_1}; use nekoton_utils::SimpleClock; use num_bigint::BigUint; +use num_traits::ToPrimitive; +use tycho_types::abi::AbiValue; use tycho_types::cell::{Cell, HashBytes}; use tycho_types::models::StdAddr; use crate::models::ExistingContract; +use crate::utils::token_wallets::models::{RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion}; -const INITIAL_BALANCE: u64 = 100_000_000; // 0.1 TON +const INITIAL_BALANCE: u128 = 100_000_000; // 0.1 -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug)] pub struct InternalMessage { - #[serde( - with = "serde_optional_address", - skip_serializing_if = "Option::is_none" - )] pub source: Option, - #[serde(with = "serde_address")] pub destination: StdAddr, - #[serde(with = "serde_string")] pub amount: u128, pub bounce: bool, - #[serde(with = "serde_boc")] pub body: Cell, } @@ -40,34 +34,28 @@ pub fn prepare_token_transfer( attached_amount: u128, payload: Cell, ) -> Result { - let (function, input) = match version { + let (function, tokens) = match version { TokenWalletVersion::OldTip3v4 => { - use old_tip3::token_wallet_contract; - MessageBuilder::new(token_wallet_contract::transfer_to_recipient()) - .arg(BigUint256(Default::default())) // recipient_public_key - .arg(owner_wallet) // recipient_address - .arg(BigUint128(tokens)) // tokens - .arg(BigUint128(INITIAL_BALANCE.into())) // deploy_grams - .arg(BigUint128(Default::default())) // grams / transfer_grams - .arg(send_gas_to) // send_gas_to - .arg(notify_receiver) // notify_receiver - .arg(payload) // payload - .build() + return Err(TokenWalletError::NotSupported.into()); } TokenWalletVersion::Tip3 => { - use tip3_1::token_wallet_contract; - MessageBuilder::new(token_wallet_contract::transfer()) - .arg(BigUint128(tokens)) // amount - .arg(owner_wallet) // recipient - .arg(BigUint128(INITIAL_BALANCE.into())) // deployWalletValue - .arg(send_gas_to) // remainingGasTo - .arg(notify_receiver) // notify - .arg(payload) // payload - .build() + use crate::utils::token_wallets; + let function = token_wallets::transfer(); + let tokens = [ + AbiValue::uint(128, tokens.to_u128().unwrap()).named("amount"), + AbiValue::address(destination).named("recipient"), + AbiValue::uint(128, INITIAL_BALANCE).named("deployWalletValue"), + AbiValue::address(send_gas_to).named("remainingGasTo"), + AbiValue::Bool(notify_receiver).named("notify"), + AbiValue::Cell(payload).named("payload"), + ] + .to_vec(); + (function, tokens) } }; - let body = SliceData::load_builder(function.encode_internal_input(&input)?)?; + let external_input = function.encode_external(&tokens); + let (_, body) = external_input.build_input_without_signature()?; Ok(InternalMessage { source: Some(owner), @@ -88,29 +76,27 @@ pub fn prepare_token_burn( attached_amount: u128, payload: Cell, ) -> Result { - let (function, input) = match version { + let (function, tokens) = match version { TokenWalletVersion::OldTip3v4 => { - use old_tip3::token_wallet_contract; - MessageBuilder::new(token_wallet_contract::burn_by_owner()) - .arg(BigUint128(tokens)) // amount - .arg(0u128) // grams - .arg(send_gas_to) // remainingGasTo - .arg(callback_to) // callback_address - .arg(payload) // payload - .build() + return Err(TokenWalletError::NotSupported.into()); } TokenWalletVersion::Tip3 => { - use tip3_1::token_wallet_contract; - MessageBuilder::new(token_wallet_contract::burnable::burn()) - .arg(BigUint128(tokens)) // amount - .arg(send_gas_to) // remainingGasTo - .arg(callback_to) // callbackTo - .arg(payload) // payload - .build() + use crate::utils::token_wallets; + + let function = token_wallets::burnable::burn(); + let tokens = [ + AbiValue::uint(128, tokens.to_u128().unwrap()).named("amount"), + AbiValue::address(send_gas_to).named("remainingGasTo"), + AbiValue::address(callback_to).named("callbackTo"), + AbiValue::Cell(payload).named("payload"), + ] + .to_vec(); + (function, tokens) } }; - let body = SliceData::load_builder(function.encode_internal_input(&input)?)?; + let external_input = function.encode_external(&tokens); + let (_, body) = external_input.build_input_without_signature()?; Ok(InternalMessage { source: Some(owner), @@ -133,22 +119,26 @@ pub fn prepare_token_mint( attached_amount: u128, payload: Cell, ) -> Result { - let (function, input) = match version { - TokenWalletVersion::OldTip3v4 => return Err(TokenWalletError::MintNotSupported.into()), + let (function, tokens) = match version { + TokenWalletVersion::OldTip3v4 => return Err(TokenWalletError::NotSupported.into()), TokenWalletVersion::Tip3 => { - use tip3_1::root_token_contract; - MessageBuilder::new(root_token_contract::mint()) - .arg(BigUint128(tokens)) // amount - .arg(recipient) // recipient - .arg(BigUint128(deploy_wallet_value)) // deployWalletValue - .arg(send_gas_to) // remainingGasTo - .arg(notify) // notify - .arg(payload) // payload - .build() + use crate::utils::token_wallets; + let function = token_wallets::mint(); + let tokens = [ + AbiValue::uint(128, tokens.to_u128().unwrap()).named("amount"), + AbiValue::address(recipient).named("recipient"), + AbiValue::uint(128, deploy_wallet_value).named("deployWalletValue"), + AbiValue::address(send_gas_to).named("remainingGasTo"), + AbiValue::Bool(notify).named("notify"), + AbiValue::Cell(payload).named("payload"), + ] + .to_vec(); + (function, tokens) } }; - let body = SliceData::load_builder(function.encode_internal_input(&input)?)?; + let external_input = function.encode_external(&tokens); + let (_, body) = external_input.build_input_without_signature()?; Ok(InternalMessage { source: Some(owner), @@ -234,6 +224,6 @@ pub fn get_root_token_version(root_contract: &ExistingContract) -> Result &'static Function { + declare_function! { + name: "owner", + inputs: vec![AbiType::Uint(32).named("answerId")], + outputs: vec![AbiType::Address.named("owner")], + } +} + +#[derive(Debug, Clone)] +pub struct TransferInputs { + pub amount: u128, + pub recipient: StdAddr, + pub deploy_wallet_value: u128, + pub remaining_gas_to: StdAddr, + pub notify: bool, + pub payload: Cell, +} + +impl TransferInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("recipient"), + AbiType::Uint(128).named("deployWalletValue"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ] + } +} + +/// Transfer tokens and optionally deploy token wallet for the recipient +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - amount of tokens to transfer +/// * `recipient: address` - tokens recipient address +/// * `deployWalletValue: uint128` - how much EVERs to attach to the token wallet deploy +/// * `remainingGasTo: address` - remaining gas receiver +/// * `notify: bool` - notify receiver on incoming transfer +/// * `payload: cell` - arbitrary payload +/// +pub fn transfer() -> &'static Function { + declare_function! { + name: "transfer", + inputs: TransferInputs::abi_type(), + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone)] +pub struct TransferToWalletInputs { + pub amount: u128, + pub recipient_token_wallet: StdAddr, + pub remaining_gas_to: StdAddr, + pub notify: bool, + pub payload: Cell, +} + +impl TransferToWalletInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("recipientTokenWallet"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ] + } +} + +/// Transfer tokens using token wallet address +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - amount of tokens to transfer +/// * `recipientTokenWallet: address` - recipient token wallet +/// * `remainingGasTo: address` - remaining gas receiver +/// * `notify: bool` - notify receiver on incoming transfer +/// * `payload: cell` - arbitrary payload +/// +pub fn transfer_to_wallet() -> &'static Function { + declare_function! { + name: "transferToWallet", + inputs: TransferToWalletInputs::abi_type(), + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone)] +pub struct AcceptTransferInputs { + pub amount: u128, + pub sender: StdAddr, + pub remaining_gas_to: StdAddr, + pub notify: bool, + pub payload: Cell, +} + +impl AcceptTransferInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("sender"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ] + } +} + +/// Callback for transfer operation +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - how much tokens to receive +/// * `sender: address` - token wallet owner address +/// * `remainingGasTo` - +/// * `notify` - +/// +/// +/// TODO: fill docs +/// +pub fn accept_transfer() -> &'static Function { + declare_function! { + function_id: 0x67A0B95F, + name: "acceptTransfer", + inputs: AcceptTransferInputs::abi_type(), + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone)] +pub struct AcceptMintInputs { + pub amount: u128, + pub remaining_gas_to: StdAddr, + pub notify: bool, + pub payload: Cell, +} + +impl AcceptMintInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ] + } +} + +/// Accept minted tokens from root +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - how much tokens to receive +/// * `remainingGasTo: address` - remaining gas receiver +/// * `notify: bool` - notify receiver on incoming mint +/// * `payload: cell` - arbitrary payload +/// +pub fn accept_mint() -> &'static Function { + declare_function! { + function_id: 0x4384F298, + name: "acceptMint", + inputs: AcceptMintInputs::abi_type(), + outputs: Vec::new(), + } +} + +pub mod burnable { + use super::*; + + #[derive(Debug, Clone)] + pub struct BurnInputs { + pub amount: u128, + pub remaining_gas_to: StdAddr, + pub callback_to: StdAddr, + pub payload: Cell, + } + + impl BurnInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("remainingGasTo"), + AbiType::Address.named("callbackTo"), + AbiType::Cell.named("payload"), + ] + } + } + + /// TODO: fill docs + pub fn burn() -> &'static Function { + declare_function! { + name: "burn", + inputs: BurnInputs::abi_type(), + outputs: Vec::new(), + } + } +} + +/// Mint tokens to recipient with deploy wallet optional +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - how much tokens to mint +/// * `recipient: address` - minted tokens owner address +/// * `deployWalletValue: uint128` - how much EVERs to send to wallet on deployment +/// * `remainingGasTo: address` - address where to send excess gas +/// * `notify: bool` - whether to notify the recipient +/// * `payload: cell` - arbitrary payload +/// +pub fn mint() -> &'static Function { + declare_function! { + name: "mint", + inputs: vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("recipient"), + AbiType::Uint(128).named("deployWalletValue"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ], + outputs: Vec::new(), + } +} diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs new file mode 100644 index 0000000..2492aa2 --- /dev/null +++ b/src/utils/token_wallets/models.rs @@ -0,0 +1,263 @@ +use num_bigint::BigUint; +use serde::{Deserialize, Serialize}; +use tycho_types::{cell::{Cell, HashBytes}, models::{OrdinaryTxInfo, StdAddr, Transaction}}; + +use crate::utils::{serde_address, serde_cell, serde_string}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case", tag = "type", content = "data")] +pub enum TokenWalletTransaction { + IncomingTransfer(TokenIncomingTransfer), + OutgoingTransfer(TokenOutgoingTransfer), + SwapBack(TokenSwapBack), + #[serde(with = "serde_string")] + Accept(BigUint), + #[serde(with = "serde_string")] + TransferBounced(BigUint), + #[serde(with = "serde_string")] + SwapBackBounced(BigUint), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenIncomingTransfer { + #[serde(with = "serde_string")] + pub tokens: BigUint, + /// Not the address of the token wallet, but the address of its owner + #[serde(with = "serde_address")] + pub sender_address: StdAddr, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TokenOutgoingTransfer { + pub to: TransferRecipient, + #[serde(with = "serde_string")] + pub tokens: BigUint, + /// token transfer payload + #[serde(with = "serde_cell")] + pub payload: Cell, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type", content = "data")] +pub enum TransferRecipient { + #[serde(with = "serde_address")] + OwnerWallet(StdAddr), + #[serde(with = "serde_address")] + TokenWallet(StdAddr), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct TokenSwapBack { + #[serde(with = "serde_string")] + pub tokens: BigUint, + #[serde(with = "serde_address")] + pub callback_address: StdAddr, + /// ETH address or something else + #[serde(with = "serde_cell")] + pub callback_payload: Cell, +} + + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum TokenWalletVersion { + /// Third iteration of token wallets, but with fixed bugs + /// [implementation](https://github.com/broxus/ton-eth-bridge-token-contracts/tree/74905260499d79cf7cb0d89a6eb572176fc1fcd5) + OldTip3v4, + /// Latest iteration with completely new standard + /// [implementation](https://github.com/broxus/ton-eth-bridge-token-contracts/tree/9168190f218fd05a64269f5f24295c69c4840d94) + Tip3, +} + + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct RootTokenContractDetails { + /// Token ecosystem version + pub version: TokenWalletVersion, + /// Full currency name + pub name: String, + /// Short currency name + pub symbol: String, + /// Decimals + pub decimals: u8, + /// Root owner contract address. Used as proxy address in Tip3v1 + #[serde(with = "serde_address")] + pub owner_address: StdAddr, + #[serde(with = "serde_string")] + pub total_supply: BigUint, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenWalletDetails { + /// Linked root token contract address + #[serde(with = "serde_address")] + pub root_address: StdAddr, + + /// Owner wallet address + #[serde(with = "serde_address")] + pub owner_address: StdAddr, + + #[serde(with = "serde_string")] + pub balance: u128, +} + +#[derive(thiserror::Error, Debug)] +pub enum Tip3Error { + #[error("Unknown version")] + UnknownVersion, + #[error("Wallet not deployed")] + WalletNotDeployed, +} + +pub struct TokenWalletContractState<'a>(pub ExecutionContext<'a>); + +impl TokenWalletContractState<'_> { + pub fn get_code_hash(&self) -> anyhow::Result { + match &self.0.account_stuff.storage.state { + ton_block::AccountState::AccountActive { state_init, .. } => { + let code = state_init + .code + .as_ref() + .ok_or(Tip3Error::WalletNotDeployed)?; + Ok(code.repr_hash()) + } + _ => Err(Tip3Error::WalletNotDeployed.into()), + } + } + + pub fn get_balance(&self, version: TokenWalletVersion) -> anyhow::Result { + match version { + TokenWalletVersion::OldTip3v4 => old_tip3::TokenWalletContract(self.0).balance(), + TokenWalletVersion::Tip3 => tip3::TokenWalletContract(self.0).balance(), + } + } + + pub fn get_details(&self, version: TokenWalletVersion) -> anyhow::Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + let details = old_tip3::TokenWalletContract(self.0).get_details()?; + + TokenWalletDetails { + root_address: details.root_address, + owner_address: details.owner_address, + balance: details.balance, + } + } + TokenWalletVersion::Tip3 => { + let token_wallet = tip3::TokenWalletContract(self.0); + let root_address = token_wallet.root()?; + let balance = token_wallet.balance()?; + + let token_wallet = tip3_1::TokenWalletContract(self.0); + let owner_address = token_wallet.owner()?; + + TokenWalletDetails { + root_address, + owner_address, + balance, + } + } + }) + } + + pub fn get_version(&self) -> anyhow::Result { + if let Ok(true) = tip6::SidContract(self.0).supports_interfaces(&[ + tip3::token_wallet_contract::INTERFACE_ID, + tip3_1::token_wallet_contract::INTERFACE_ID, + ]) { + return Ok(TokenWalletVersion::Tip3); + } + + match old_tip3::TokenWalletContract(self.0).get_version()? { + 4 => Ok(TokenWalletVersion::OldTip3v4), + _ => Err(Tip3Error::UnknownVersion.into()), + } + } +} + + +pub fn parse_token_transaction( + tx: &Transaction, + info: &OrdinaryTxInfo, + version: TokenWalletVersion, +) -> Option { + if info.aborted { + return None; + } + + let in_msg = tx.in_msg.as_ref()?.read_struct().ok()?; + + let mut body = in_msg.body()?; + let function_id = read_function_id(&body).ok()?; + + let header = in_msg.int_header()?; + + let functions = TokenWalletFunctions::for_version(version); + + if header.bounced { + body.move_by(32).ok()?; + let function_id = read_function_id(&body).ok()?; + body.move_by(32).ok()?; + + if function_id == functions.accept_transfer.input_id { + return Some(TokenWalletTransaction::TransferBounced( + body.get_next_u128().ok()?.into(), + )); + } + + if function_id == functions.accept_burn.input_id { + Some(TokenWalletTransaction::SwapBackBounced( + body.get_next_u128().ok()?.into(), + )) + } else { + None + } + } else if function_id == functions.accept_mint.input_id { + let inputs = functions.accept_mint.decode_input(body, true, false).ok()?; + + Accept::try_from((InputMessage(inputs), version)) + .map(|Accept { tokens }| TokenWalletTransaction::Accept(tokens)) + .ok() + } else if function_id == functions.transfer_to_wallet.input_id { + let inputs = functions + .transfer_to_wallet + .decode_input(body, true, false) + .ok()?; + + TokenOutgoingTransfer::try_from(( + InputMessage(inputs), + TransferType::ByTokenWalletAddress, + version, + )) + .map(TokenWalletTransaction::OutgoingTransfer) + .ok() + } else if function_id == functions.transfer.input_id { + let inputs = functions.transfer.decode_input(body, true, false).ok()?; + + TokenOutgoingTransfer::try_from(( + InputMessage(inputs), + TransferType::ByOwnerWalletAddress, + version, + )) + .map(TokenWalletTransaction::OutgoingTransfer) + .ok() + } else if function_id == functions.accept_transfer.input_id { + let inputs = functions + .accept_transfer + .decode_input(body, true, false) + .ok()?; + + TokenIncomingTransfer::try_from((InputMessage(inputs), version)) + .map(TokenWalletTransaction::IncomingTransfer) + .ok() + } else if function_id == functions.burn.input_id { + let inputs = functions.burn.decode_input(body, true, false).ok()?; + + TokenSwapBack::try_from((InputMessage(inputs), version)) + .map(TokenWalletTransaction::SwapBack) + .ok() + } else { + None + } +} diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index a478d9c..26ba11f 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -824,14 +824,11 @@ mod tests { #[test] fn correct_address() { let key = hex::decode("5ace46d93d8f3932499df9f2bc7ef787385e16965e7797258948febd186de7f6") - .unwrap(); + .unwrap(); let key = key.try_into().unwrap(); - - let key = VerifyingKey::from_bytes( - &key, - ) - .unwrap(); + + let key = VerifyingKey::from_bytes(&key).unwrap(); assert_eq!( compute_contract_address(&key, MultisigType::SetcodeMultisigWallet24h, 0) diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index 8d425c8..cbaa069 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -50,7 +50,10 @@ pub fn make_init_data(public_key: &VerifyingKey) -> InitData { .with_is_signature_allowed(true) } -pub fn get_init_data(current_state: &Account, public_key: &VerifyingKey) -> Result<(InitData, bool)> { +pub fn get_init_data( + current_state: &Account, + public_key: &VerifyingKey, +) -> Result<(InitData, bool)> { match current_state.state { AccountState::Active(ref state_init) => match &state_init.data { Some(data) => Ok((InitData::try_from(data)?, false)), @@ -346,7 +349,8 @@ mod tests { assert_eq!(init_data.wallet_id, WALLET_ID); assert_eq!(init_data.extensions, None); - let public_key = VerifyingKey::from_bytes(init_data.public_key.as_slice().try_into().unwrap())?; + let public_key = + VerifyingKey::from_bytes(init_data.public_key.as_slice().try_into().unwrap())?; let address = compute_contract_address(&public_key, 0)?; assert_eq!( address.to_string(), diff --git a/src/utils/tx_context.rs b/src/utils/tx_context.rs index 6c994a3..95e66d3 100644 --- a/src/utils/tx_context.rs +++ b/src/utils/tx_context.rs @@ -1,6 +1,10 @@ +use anyhow::Result; use tokio::sync::oneshot; -use ton_types::UInt256; -use tycho_types::models::BlockId; +use tycho_types::{ + abi::{Function, NamedAbiValue}, + cell::{CellSlice, HashBytes}, + models::{BlockId, MsgInfo, OrdinaryTxInfo, OwnedMessage, Transaction}, +}; use crate::models::ExistingContract; @@ -20,22 +24,19 @@ pub struct StateContext<'a> { #[derive(Copy, Clone)] pub struct TxContext<'a> { pub block_info_gen_utime: u32, - pub block_hash: &'a UInt256, - pub account: &'a UInt256, - pub transaction_hash: &'a UInt256, - pub transaction_info: &'a ton_block::TransactionDescrOrdinary, - pub transaction: &'a ton_block::Transaction, - pub in_msg: &'a ton_block::Message, - pub token_transaction: &'a Option, + pub block_hash: &'a HashBytes, + pub account: &'a HashBytes, + pub transaction_hash: &'a HashBytes, + pub transaction_info: &'a OrdinaryTxInfo, + pub transaction: &'a Transaction, + pub in_msg: &'a OwnedMessage, + pub token_transaction: &'a Option, pub token_state: &'a Option, } impl TxContext<'_> { - pub fn in_msg_internal(&self) -> Option<&ton_block::Message> { - if matches!( - self.in_msg.header(), - ton_block::CommonMsgInfo::IntMsgInfo(_) - ) { + pub fn in_msg_internal(&self) -> Option<&OwnedMessage> { + if matches!(self.in_msg.info, MsgInfo::Int(_)) { Some(self.in_msg) } else { None @@ -43,81 +44,53 @@ impl TxContext<'_> { } #[allow(dead_code)] - pub fn in_msg_external(&self) -> Option<&ton_block::Message> { - if matches!( - self.in_msg.header(), - ton_block::CommonMsgInfo::ExtInMsgInfo(_) - ) { + pub fn in_msg_external(&self) -> Option<&OwnedMessage> { + if matches!(self.in_msg.info, MsgInfo::ExtIn(_)) { Some(self.in_msg) } else { None } } #[allow(dead_code)] - pub fn find_function_output( - &self, - function: &ton_abi::Function, - ) -> Option> { - let mut result = None; - self.transaction - .out_msgs - .iterate(|ton_block::InRefValue(message)| { - // Skip all messages except external outgoing - if !matches!(message.header(), ton_block::CommonMsgInfo::ExtOutMsgInfo(_)) { - return Ok(true); - } + pub fn find_function_output(&self, function: &Function) -> Result>> { + for message in self.transaction.iter_out_msgs() { + let message = message?; + // Skip all messages except external outgoing + if !matches!(message.info, MsgInfo::ExtOut(_)) { + continue; + } - // Handle body if it exists - let body = match message.body() { - Some(body) => body, - None => return Ok(true), - }; + let function_id = message.body.get_u32(0)?; + if function_id != function.output_id { + continue; + } - let function_id = nekoton_abi::read_function_id(&body)?; - if function_id != function.output_id { - return Ok(true); + match function.decode_output(message.body) { + Ok(tokens) => { + return Ok(Some(tokens)); } - - Ok(match function.decode_output(body, false) { - Ok(tokens) => { - result = Some(tokens); - false - } - Err(_) => true, - }) - }) - .ok(); - result + Err(_) => continue, + } + } + Ok(None) } #[allow(dead_code)] pub fn iterate_events(&self, mut f: F) where - F: FnMut(u32, ton_types::SliceData), + F: FnMut(u32, CellSlice<'_>), { - self.transaction - .out_msgs - .iterate(|ton_block::InRefValue(message)| { - // Skip all messages except external outgoing - if !matches!(message.header(), ton_block::CommonMsgInfo::ExtOutMsgInfo(_)) { - return Ok(true); - } + for message in self.transaction.iter_out_msgs() { + let Ok(message) = message else { continue }; + // Skip all messages except external outgoing + if !matches!(message.info, MsgInfo::ExtOut(_)) { + continue; + } - // Handle body if it exists - let body = match message.body() { - Some(body) => body, - None => return Ok(true), - }; - - // Parse function id - if let Ok(function_id) = nekoton_abi::read_function_id(&body) { - f(function_id, body) - } - - // Process all messages - Ok(true) - }) - .ok(); + if let Ok(function_id) = message.body.get_u32(0){ + f(function_id, message.body) + } + } } } From 4b9da081261e5a2fbea73ffc850c8bb09b5ffaec Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 5 Feb 2026 12:04:31 +0300 Subject: [PATCH 20/59] fix parsing transactions --- src/api/error.rs | 10 - src/api/requests/transactions.rs | 1 - src/api/responses/address.rs | 1 - src/api/responses/misc.rs | 1 - src/api/responses/transactions.rs | 10 +- src/client/callback/mod.rs | 1 - src/client/ton/mod.rs | 2 - src/models/account_enums.rs | 1 - src/models/owners_cache.rs | 24 +- src/models/transactions.rs | 7 +- src/services/ton.rs | 102 +++---- src/settings.rs | 7 +- src/sqlx_client/transactions.rs | 13 +- src/ton_core/mod.rs | 7 +- src/ton_core/monitoring/token_transaction.rs | 2 +- .../monitoring/token_transaction_parser.rs | 12 +- src/ton_core/monitoring/ton_transaction.rs | 6 +- .../monitoring/ton_transaction_parser.rs | 280 ++++++++++-------- src/ton_core/ton_subscriber/mod.rs | 25 +- src/utils/mod.rs | 4 +- src/utils/token_wallet.rs | 4 +- src/utils/token_wallets/models.rs | 8 +- src/utils/ton_wallet/mod.rs | 2 +- src/utils/tx_context.rs | 4 +- 24 files changed, 257 insertions(+), 277 deletions(-) diff --git a/src/api/error.rs b/src/api/error.rs index eaddb77..46839c8 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -46,9 +46,6 @@ pub enum Error { #[error("an error occurred with oneshot channel")] RecvError(#[from] oneshot::error::RecvError), - #[error("an error occurred with tokens")] - TokensJson(#[from] nekoton_abi::TokensJsonError), - #[error("an error occurred with hex conversion")] FromHexError(#[from] hex::FromHexError), @@ -88,7 +85,6 @@ impl Error { | Self::Anyhow(_) | Self::Ed25519(_) | Self::RecvError(_) - | Self::TokensJson(_) | Self::FromHexError(_) | Self::TryFromSliceError(_) => StatusCode::INTERNAL_SERVER_ERROR, Error::TonService(e) => e.status_code(), @@ -140,12 +136,6 @@ impl Error { tracing::error!("RecvError error: {:?}", e); } - Self::TokensJson(ref e) => { - // TODO: we probably want to use `tracing` instead - // so that this gets linked to the HTTP request by `TraceLayer`. - tracing::error!("TokensJson error: {:?}", e); - } - Self::FromHexError(ref e) => { // TODO: we probably want to use `tracing` instead // so that this gets linked to the HTTP request by `TraceLayer`. diff --git a/src/api/requests/transactions.rs b/src/api/requests/transactions.rs index d37bda3..302ad7c 100644 --- a/src/api/requests/transactions.rs +++ b/src/api/requests/transactions.rs @@ -1,6 +1,5 @@ use bigdecimal::BigDecimal; use derive_more::Constructor; -use nekoton_utils::TrustMe; use num_traits::FromPrimitive; use schemars::JsonSchema; diff --git a/src/api/responses/address.rs b/src/api/responses/address.rs index 8de8900..53e18d4 100644 --- a/src/api/responses/address.rs +++ b/src/api/responses/address.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use bigdecimal::BigDecimal; use derive_more::Constructor; -use nekoton_utils::TrustMe; use tycho_types::models::StdAddr; use schemars::JsonSchema; diff --git a/src/api/responses/misc.rs b/src/api/responses/misc.rs index b704685..8e56b2f 100644 --- a/src/api/responses/misc.rs +++ b/src/api/responses/misc.rs @@ -1,5 +1,4 @@ use crate::models::WhitelistedTokenFromDb; -use nekoton_contracts::tip3_any::TokenWalletVersion; use schemars::JsonSchema; use serde::Serialize; diff --git a/src/api/responses/transactions.rs b/src/api/responses/transactions.rs index 2a17530..8b9462c 100644 --- a/src/api/responses/transactions.rs +++ b/src/api/responses/transactions.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use bigdecimal::BigDecimal; -use nekoton_utils::pack_std_smc_addr; use tycho_types::models::StdAddr; use schemars::JsonSchema; @@ -91,15 +90,14 @@ impl From for TransactionDataResponse { .into_iter() .map(|output| { let output_address = - nekoton_utils::repack_address(&output.recipient_address.0) - .unwrap_or_default(); + StdAddr::from_str(&output.recipient_address.0).unwrap_or_default(); let output_base64url = - Address(pack_std_smc_addr(true, &output_address, true).unwrap()); + Address(output_address.display_base64_url(true).to_string()); TransactionOutput { value: output.value, recipient: Account { - workchain_id: output_address.workchain_id(), - hex: Address(output_address.address().to_hex_string()), + workchain_id: output_address.workchain as i32, + hex: Address(output_address.address.to_string()), base64url: output_base64url, }, } diff --git a/src/client/callback/mod.rs b/src/client/callback/mod.rs index d60fe89..d3b21c4 100644 --- a/src/client/callback/mod.rs +++ b/src/client/callback/mod.rs @@ -1,6 +1,5 @@ use anyhow::Result; use chrono::Utc; -use nekoton_utils::TrustMe; use reqwest::{Method, StatusCode, Url}; use crate::models::*; diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 1fd4908..85da57e 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -785,8 +785,6 @@ impl TonClient { input: &[NamedAbiValue], responsible: bool, ) -> anyhow::Result> { - use nekoton_abi::FunctionExt; - let state = match self.ton_core.get_contract_state(&contract_address) { Ok(a) => a, Err(e) => { diff --git a/src/models/account_enums.rs b/src/models/account_enums.rs index 5b00ca4..6acfae2 100644 --- a/src/models/account_enums.rs +++ b/src/models/account_enums.rs @@ -1,6 +1,5 @@ use std::str::FromStr; -use nekoton::core::models::TokenWalletVersion; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use strum_macros::EnumString; diff --git a/src/models/owners_cache.rs b/src/models/owners_cache.rs index bb294ea..a377e69 100644 --- a/src/models/owners_cache.rs +++ b/src/models/owners_cache.rs @@ -3,10 +3,8 @@ use std::str::FromStr; use std::sync::Arc; use lru::LruCache; -use nekoton::core::models::TokenWalletVersion; -use nekoton_utils::TrustMe; use parking_lot::Mutex; -use ton_block::MsgAddressInt; +use ton_block::StdAddr; use crate::models::sqlx::*; use crate::sqlx_client::*; @@ -14,12 +12,12 @@ use crate::sqlx_client::*; #[derive(Clone)] /// Maps token wallet address to Owner info pub struct OwnersCache { - cache: Arc>>, + cache: Arc>>, db: SqlxClient, } impl OwnersCache { - pub async fn get(&self, address: &MsgAddressInt) -> Option { + pub async fn get(&self, address: &StdAddr) -> Option { let info = { let mut lock = self.cache.lock(); lock.get(address).cloned() @@ -33,12 +31,12 @@ impl OwnersCache { .await .ok()?; OwnerInfo { - owner_address: MsgAddressInt::from_str(&format!( + owner_address: StdAddr::from_str(&format!( "{}:{}", got.owner_account_workchain_id, got.owner_account_hex )) .trust_me(), - root_address: MsgAddressInt::from_str(&got.root_address).trust_me(), + root_address: StdAddr::from_str(&got.root_address).trust_me(), code_hash: got.code_hash, version: got.version.into(), } @@ -46,7 +44,7 @@ impl OwnersCache { }; Some(info) } - pub async fn insert(&self, key: MsgAddressInt, value: OwnerInfo) { + pub async fn insert(&self, key: StdAddr, value: OwnerInfo) { { self.cache.lock().put(key.clone(), value.clone()); } @@ -67,8 +65,8 @@ impl OwnersCache { #[derive(Clone, Debug)] pub struct OwnerInfo { - pub owner_address: MsgAddressInt, - pub root_address: MsgAddressInt, + pub owner_address: StdAddr, + pub root_address: StdAddr, pub code_hash: Vec, pub version: TokenWalletVersion, } @@ -80,14 +78,14 @@ impl OwnersCache { let mut cache = LruCache::new(NonZeroUsize::new(5000).trust_me()); balances.into_iter().for_each(|x| { cache.put( - MsgAddressInt::from_str(&x.address).trust_me(), + StdAddr::from_str(&x.address).trust_me(), OwnerInfo { - owner_address: MsgAddressInt::from_str(&format!( + owner_address: StdAddr::from_str(&format!( "{}:{}", x.owner_account_workchain_id, x.owner_account_hex )) .trust_me(), - root_address: MsgAddressInt::from_str(&x.root_address).trust_me(), + root_address: StdAddr::from_str(&x.root_address).trust_me(), code_hash: x.code_hash, version: x.version.into(), }, diff --git a/src/models/transactions.rs b/src/models/transactions.rs index 12cf206..66ab975 100644 --- a/src/models/transactions.rs +++ b/src/models/transactions.rs @@ -1,6 +1,7 @@ use bigdecimal::BigDecimal; use serde::{Deserialize, Serialize}; use ton_abi::Param; +use tycho_types::abi::{AbiHeaderType, NamedAbiType, NamedAbiValue}; use uuid::Uuid; use crate::models::*; @@ -188,7 +189,7 @@ pub struct InputParam { #[derive(Clone, Debug, Deserialize)] pub struct FunctionDetails { pub function_name: String, - pub input_params: Vec, - pub output_params: Vec, - pub headers: Vec, + pub input_params: Vec, + pub output_params: Vec, + pub headers: Vec, } diff --git a/src/services/ton.rs b/src/services/ton.rs index 5d93119..48041c6 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -6,9 +6,9 @@ use axum::http::StatusCode; use bigdecimal::BigDecimal; use chrono::Utc; use serde_json::Value; -use ton_abi::contract::ABI_VERSION_2_2; -use ton_abi::{Param, Token, TokenValue}; -use tycho_types::abi::UnsignedExternalMessage; +use tycho_types::abi::{ + AbiHeaderType, AbiVersion, Function, NamedAbiType, NamedAbiValue, UnsignedExternalMessage, +}; use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{OwnedMessage, StdAddr}; use uuid::Uuid; @@ -878,26 +878,27 @@ impl TonService { self: &Arc, account_addr: &str, function_name: &str, - inputs: Vec, - outputs: Vec, - headers: Vec, + inputs: Vec, + outputs: Vec, + headers: Vec, responsible: bool, ) -> Result { let account_addr = HashBytes::from_str(&account_addr).map_err(anyhow::Error::from)?; - let input_params: Vec = inputs.iter().map(|x| x.param.clone()).collect(); + let input_params: Vec = inputs + .iter() + .map(|x| x.value.get_type().named(x.name.into())) + .collect(); - let function = nekoton_abi::FunctionBuilder::new(function_name) - .abi_version(ABI_VERSION_2_2) - .headers(headers) - .inputs(input_params) - .outputs(outputs) + let function = Function::builder(AbiVersion::V2_2, function_name) + .with_headers(headers) + .with_inputs(input_params) + .with_outputs(outputs) .build(); - let input = parse_abi_tokens(inputs)?; let output = match self .ton_api_client - .run_local(account_addr, function, input.as_slice(), responsible) + .run_local(account_addr, function, &inputs, responsible) .await? { Some(output) => output, @@ -914,8 +915,7 @@ impl TonService { None => return Err(TonServiceError::ExecuteContract.into()), }; - let res = nekoton_abi::make_abi_tokens(tokens.as_slice())?; - Ok(res) + Ok(tokens) } pub async fn prepare_and_send_signed_generic_message( @@ -933,23 +933,21 @@ impl TonService { ) -> Result { let (function, values) = match function_details { Some(details) => { - let function = nekoton_abi::FunctionBuilder::new(&details.function_name) - .abi_version(ABI_VERSION_2_2) - .headers(details.headers) - .inputs( - details - .input_params - .clone() - .into_iter() - .map(|x| x.param) - .collect::>(), - ) - .outputs(details.output_params) - .build(); - - let tokens = parse_abi_tokens(details.input_params)?; + let function = + Function::builder(AbiVersion::V2_2, details.function_name.to_string()) + .with_headers(details.headers) + .with_inputs( + details + .input_params + .clone() + .into_iter() + .map(|x| x.value.get_type().named(x.name.into())) + .collect::>(), + ) + .with_outputs(details.output_params) + .build(); - (Some(function), Some(tokens)) + (Some(function), Some(details.input_params)) } None => (None, None), }; @@ -1037,23 +1035,21 @@ impl TonService { ) -> Result { let (function, values) = match function_details { Some(details) => { - let function = nekoton_abi::FunctionBuilder::new(&details.function_name) - .abi_version(ABI_VERSION_2_2) - .headers(details.headers) - .inputs( - details - .input_params - .clone() - .into_iter() - .map(|x| x.param) - .collect::>(), - ) - .outputs(details.output_params) - .build(); - - let tokens = parse_abi_tokens(details.input_params)?; + let function = + Function::builder(AbiVersion::V2_2, details.function_name.to_string()) + .with_headers(details.headers) + .with_inputs( + details + .input_params + .clone() + .into_iter() + .map(|x| x.value.get_type().named(x.name.into())) + .collect::>(), + ) + .with_outputs(details.output_params) + .build(); - (Some(function), Some(tokens)) + (Some(function), Some(details.input_params)) } None => (None, None), }; @@ -1427,16 +1423,6 @@ async fn send_transaction( Ok(()) } -fn parse_abi_tokens(params: Vec) -> Result, Error> { - let mut tokens = Vec::::new(); - for i in params { - let token = nekoton_abi::parse_abi_token(&i.param, i.value)?; - tokens.push(token); - } - - Ok(tokens) -} - enum NotifyType { Transaction, TokenTransaction, diff --git a/src/settings.rs b/src/settings.rs index 1790d93..cdd8b25 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -3,7 +3,6 @@ use std::path::Path; use anyhow::{Context, Result}; use argon2::password_hash::PasswordHasher; -use nekoton_utils::TrustMe; use regex; use serde::{Deserialize, Deserializer, Serialize}; @@ -72,16 +71,14 @@ fn default_key() -> Vec { let mut options = argon2::ParamsBuilder::default(); let options = options .output_len(32) //chacha key size - .and_then(|x| x.clone().params()) - .trust_me(); + .and_then(|x| x.clone().params())?; // Argon2 with default params (Argon2id v19) let argon2 = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, options); let key = argon2 - .hash_password(secret.as_bytes(), &salt) - .trust_me() + .hash_password(secret.as_bytes(), &salt)? .hash .context("No hash")? .as_bytes() diff --git a/src/sqlx_client/transactions.rs b/src/sqlx_client/transactions.rs index 68b75de..4e090fd 100644 --- a/src/sqlx_client/transactions.rs +++ b/src/sqlx_client/transactions.rs @@ -1,12 +1,14 @@ +use std::str::FromStr; + use anyhow::{Context, Result}; use chrono::prelude::*; +use tycho_types::models::StdAddr; use uuid::Uuid; use crate::models::*; use crate::sqlx_client::*; use itertools::Itertools; -use nekoton_utils::{repack_address, TrustMe}; use sqlx::postgres::PgArguments; use sqlx::Arguments; use sqlx::Row; @@ -295,7 +297,7 @@ impl SqlxClient { let mut tx = self.pool.begin().await?; let transaction_id = Uuid::new_v4(); let transaction_timestamp = - DateTime::from_timestamp(payload.transaction_timestamp.trust_me() as i64, 0) + DateTime::from_timestamp(payload.transaction_timestamp.unwrap_or_default() as i64, 0) .context("Invalid transaction timestamp")? .naive_utc(); @@ -711,14 +713,13 @@ pub fn filter_transaction_query( } if let Some(account) = account { - if let Ok(account) = repack_address(&account) { + if let Ok(account) = StdAddr::from_str(&account) { updates.push(format!(" AND account_workchain_id = ${} ", *args_len + 1,)); *args_len += 1; - args.add(account.workchain_id()) - .map_err(sqlx::Error::Encode)?; + args.add(account.workchain).map_err(sqlx::Error::Encode)?; updates.push(format!(" AND account_hex = ${} ", *args_len + 1,)); *args_len += 1; - args.add(account.address().to_hex_string()) + args.add(account.address.to_string()) .expect("Failed to add query") } } diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index 8a0a833..917d4f8 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -2,13 +2,8 @@ use std::str::FromStr; use std::sync::Arc; use anyhow::{Context, Result}; -use nekoton_abi::*; -use nekoton_utils::Clock; -use nekoton_utils::SimpleClock; use parking_lot::Mutex; use tokio::sync::{mpsc, oneshot}; -use ton_block::Serializable; -use ton_types::UInt256; use tycho_core::blockchain_rpc::BlockchainRpcClient; use tycho_core::storage::CoreStorage; use tycho_executor::ExecutorParams; @@ -220,7 +215,7 @@ impl TonCoreContext { #[allow(unused)] fn send_local( &self, - account: &UInt256, + account: &HashBytes, message_base64: &str, expire_at: u32, ) -> Result> { diff --git a/src/ton_core/monitoring/token_transaction.rs b/src/ton_core/monitoring/token_transaction.rs index aa5a193..cf1e9b0 100644 --- a/src/ton_core/monitoring/token_transaction.rs +++ b/src/ton_core/monitoring/token_transaction.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::Result; use tokio::sync::mpsc; -use ton_types::UInt256; +use ton_types::HashBytes; use crate::ton_core::monitoring::*; use crate::ton_core::*; diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index 760c5cf..6bab8a4 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -1,7 +1,7 @@ use anyhow::Result; use bigdecimal::BigDecimal; use num_bigint::BigUint; -use ton_block::{GetRepresentationHash, MsgAddressInt}; +use ton_block::{GetRepresentationHash, StdAddr}; use ton_types::{AccountId, BuilderData}; use uuid::Uuid; @@ -58,7 +58,7 @@ async fn internal_transfer_send( payload_cell: Option, parse_ctx: ParseContext<'_>, ) -> Result { - let address = MsgAddressInt::with_standart( + let address = StdAddr::with_standart( None, ton_block::BASE_WORKCHAIN_ID as i8, AccountId::from(token_transaction_ctx.account), @@ -112,7 +112,7 @@ async fn internal_transfer_receive( token_transfer: TokenIncomingTransfer, parse_ctx: ParseContext<'_>, ) -> Result { - let address = MsgAddressInt::with_standart( + let address = StdAddr::with_standart( None, ton_block::BASE_WORKCHAIN_ID as i8, AccountId::from(token_transaction_ctx.account), @@ -169,7 +169,7 @@ async fn internal_transfer_bounced( tokens: BigUint, parse_ctx: ParseContext<'_>, ) -> Result { - let address = MsgAddressInt::with_standart( + let address = StdAddr::with_standart( None, ton_block::BASE_WORKCHAIN_ID as i8, AccountId::from(token_transaction_ctx.account), @@ -214,7 +214,7 @@ async fn internal_transfer_mint( tokens: BigUint, parse_ctx: ParseContext<'_>, ) -> Result { - let address = MsgAddressInt::with_standart( + let address = StdAddr::with_standart( None, ton_block::BASE_WORKCHAIN_ID as i8, AccountId::from(token_transaction_ctx.account), @@ -255,7 +255,7 @@ async fn internal_transfer_mint( } async fn get_token_wallet_info( - contract_address: &MsgAddressInt, + contract_address: &StdAddr, parse_ctx: &ParseContext<'_>, contract: &ExistingContract, ) -> Result { diff --git a/src/ton_core/monitoring/ton_transaction.rs b/src/ton_core/monitoring/ton_transaction.rs index 87d1c0c..5307a71 100644 --- a/src/ton_core/monitoring/ton_transaction.rs +++ b/src/ton_core/monitoring/ton_transaction.rs @@ -85,10 +85,10 @@ impl TonTransaction { #[derive(Debug)] pub struct TonTransactionEvent { - pub account: UInt256, + pub account: HashBytes, pub block_utime: u32, - pub transaction_hash: UInt256, - pub transaction: ton_block::Transaction, + pub transaction_hash: HashBytes, + pub transaction: Transaction, pub state: HandleTransactionStatusTx, } diff --git a/src/ton_core/monitoring/ton_transaction_parser.rs b/src/ton_core/monitoring/ton_transaction_parser.rs index a4a35dc..60058a4 100644 --- a/src/ton_core/monitoring/ton_transaction_parser.rs +++ b/src/ton_core/monitoring/ton_transaction_parser.rs @@ -1,44 +1,44 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton::core::models::{MultisigTransaction, TransactionError}; -use nekoton::core::ton_wallet::MultisigType; use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; -use ton_block::{CommonMsgInfo, GetRepresentationHash, MsgAddressInt}; -use ton_types::AccountId; use uuid::Uuid; -use crate::ton_core::*; +use crate::{ton_core::*, utils::ton_wallet::MultisigType}; + +#[derive(thiserror::Error, Debug, Copy, Clone)] +pub enum TransactionError { + #[error("Invalid transaction structure")] + InvalidStructure, + #[error("Unsupported transaction type")] + Unsupported, +} pub async fn parse_ton_transaction( - account: UInt256, + account: HashBytes, block_utime: u32, - transaction_hash: UInt256, - transaction: ton_block::Transaction, + transaction_hash: HashBytes, + transaction: Transaction, ) -> Result { - let in_msg = match &transaction.in_msg { - Some(message) => message - .read_struct() - .map_err(|_| TransactionError::InvalidStructure)?, + let in_msg = match transaction.in_msg.as_ref() { + Some(in_msg_cell) => OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)?, None => return Err(TransactionError::Unsupported.into()), }; - let address = MsgAddressInt::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(account), - )?; + + let address = StdAddr::new(0, account); let sender_address = get_sender_address(&transaction)?; let (sender_workchain_id, sender_hex) = match &sender_address { Some(address) => ( - Some(address.workchain_id()), - Some(address.address().to_hex_string()), + Some(address.workchain as i32), + Some(address.address.to_string()), ), None => (None, None), }; - let message_hash = in_msg.hash()?.to_hex_string(); - let transaction_hash = Some(transaction_hash.to_hex_string()); + let cell_builder = CellBuilder::build_from(&in_msg).map_err(anyhow::Error::from)?; + let message_hash = cell_builder.repr_hash().to_string(); + let transaction_hash = Some(transaction_hash.to_string()); let transaction_lt = BigDecimal::from_u64(transaction.lt); let transaction_scan_lt = Some(transaction.lt as i64); let transaction_timestamp = block_utime; @@ -46,7 +46,7 @@ pub async fn parse_ton_transaction( let messages_hash = Some(serde_json::to_value(get_messages_hash(&transaction)?)?); let fee = BigDecimal::from_u128(compute_fees(&transaction)); let value = BigDecimal::from_u128(compute_value(&transaction)); - let balance_change = BigDecimal::from_i128(nekoton_utils::compute_balance_change(&transaction)); + let balance_change = BigDecimal::from_i128(compute_balance_change(&transaction)); let multisig_transaction_id = nekoton::core::parsing::parse_multisig_transaction( MultisigType::SafeMultisigWallet, &transaction, @@ -57,8 +57,8 @@ pub async fn parse_ton_transaction( _ => None, }); - let parsed = match in_msg.header() { - CommonMsgInfo::IntMsgInfo(header) => { + let parsed = match in_msg.info { + MsgInfo::Int(header) => { CaughtTonTransaction::Create(CreateReceiveTransaction { id: Uuid::new_v4(), message_hash, @@ -69,8 +69,8 @@ pub async fn parse_ton_transaction( transaction_timestamp, sender_workchain_id, sender_hex, - account_workchain_id: address.workchain_id(), - account_hex: address.address().to_hex_string(), + account_workchain_id: address.workchain as i32, + account_hex: address.address.to_string(), messages, messages_hash, data: None, // TODO @@ -87,11 +87,11 @@ pub async fn parse_ton_transaction( multisig_transaction_id, }) } - CommonMsgInfo::ExtInMsgInfo(_) => { + MsgInfo::ExtIn(_) => { CaughtTonTransaction::UpdateSent(UpdateSentTransaction { message_hash, - account_workchain_id: address.workchain_id(), - account_hex: address.address().to_hex_string(), + account_workchain_id: address.workchain as i32, + account_hex: address.address.to_string(), input: UpdateSendTransaction { transaction_hash, transaction_lt, @@ -111,118 +111,155 @@ pub async fn parse_ton_transaction( }, }) } - CommonMsgInfo::ExtOutMsgInfo(_) => return Err(TransactionError::InvalidStructure.into()), + MsgInfo::ExtOut(_) => return Err(TransactionError::InvalidStructure.into()), }; Ok(parsed) } -fn get_sender_address(transaction: &ton_block::Transaction) -> Result> { +fn get_sender_address(transaction: &Transaction) -> Result> { let in_msg = transaction - .in_msg - .as_ref() - .ok_or(TransactionError::InvalidStructure)? - .read_struct()?; - Ok(in_msg.src()) + .load_in_msg()? + .ok_or(TransactionError::InvalidStructure)?; + match in_msg.info { + MsgInfo::Int(info) => Ok(info.src.as_std().cloned()), + MsgInfo::ExtIn(_) => Ok(None), + MsgInfo::ExtOut(info) => Ok(info.src.as_std().cloned()), + } } -fn get_messages(transaction: &ton_block::Transaction) -> Result> { +fn get_messages(transaction: &Transaction) -> Result> { let mut out_msgs = Vec::new(); - transaction - .out_msgs - .iterate(|ton_block::InRefValue(item)| { - let fee = match item.get_fee()? { - Some(fee) => Some( - BigDecimal::from_u128(fee.as_u128()) + for message in transaction.iter_out_msgs() { + let message = message?; + let (fee, value, recipient) = match &message.info { + MsgInfo::Int(info) => ( + Some( + BigDecimal::from_u128(info.fwd_fee.into_inner()) .ok_or(TransactionError::InvalidStructure)?, ), - None => None, - }; - - let value = match item.get_value() { - Some(value) => Some( - BigDecimal::from_u128(value.grams.as_u128()) + Some( + BigDecimal::from_u128(info.value.tokens.into_inner()) .ok_or(TransactionError::InvalidStructure)?, ), - None => None, - }; - - let recipient = match item.header().get_dst_address() { - Some(dst) => Some(MessageRecipient { - hex: dst.address().to_hex_string(), - base64url: nekoton_utils::pack_std_smc_addr(true, &dst, true)?, - workchain_id: dst.workchain_id(), + info.dst.as_std().map(|dst| MessageRecipient { + hex: dst.address.to_string(), + base64url: dst.display_base64_url(true).to_string(), + workchain_id: dst.workchain as i32, }), - None => None, - }; - - out_msgs.push(Message { - fee, - value, - recipient, - message_hash: item.hash()?.to_hex_string(), - }); - - Ok(true) - }) - .map_err(|_| TransactionError::InvalidStructure)?; + ), + MsgInfo::ExtIn(info) => ( + None, + None, + info.dst.as_std().map(|dst| MessageRecipient { + hex: dst.address.to_string(), + base64url: dst.display_base64_url(true).to_string(), + workchain_id: dst.workchain as i32, + }), + ), + MsgInfo::ExtOut(_) => (None, None, None), + }; + + let cell_builder = CellBuilder::build_from(&message)?; + let message_hash = cell_builder.repr_hash().to_string(); + + out_msgs.push(Message { + fee, + value, + recipient, + message_hash, + }); + } Ok(out_msgs) } -fn get_messages_hash(transaction: &ton_block::Transaction) -> Result> { +fn get_messages_hash(transaction: &Transaction) -> Result> { let mut hashes = Vec::new(); - transaction - .out_msgs - .iterate(|ton_block::InRefValue(item)| { - hashes.push(item.hash()?.to_hex_string()); - Ok(true) - }) - .map_err(|_| TransactionError::InvalidStructure)?; + for message in transaction.iter_out_msgs() { + let message = message?; + let cell_builder = CellBuilder::build_from(&message)?; + hashes.push(cell_builder.repr_hash().to_string()); + } Ok(hashes) } -fn compute_value(transaction: &ton_block::Transaction) -> u128 { +fn compute_value(transaction: &Transaction) -> u128 { let mut value = 0; - if let Some(in_msg) = transaction - .in_msg - .as_ref() - .and_then(|data| data.read_struct().ok()) - { - if let ton_block::CommonMsgInfo::IntMsgInfo(header) = in_msg.header() { - value += header.value.grams.as_u128(); + if let Ok(Some(in_msg)) = transaction.load_in_msg() { + if let MsgInfo::Int(header) = in_msg.info { + value += header.value.tokens.into_inner(); } } - let _ = transaction.out_msgs.iterate(|out_msg| { - if let CommonMsgInfo::IntMsgInfo(header) = out_msg.0.header() { - value += header.value.grams.as_u128(); + for message in transaction.iter_out_msgs() { + let message = message.unwrap(); + if let MsgInfo::Int(header) = message.info { + value += header.value.tokens.into_inner(); } - Ok(true) - }); + } value } -fn compute_fees(transaction: &ton_block::Transaction) -> u128 { - let mut fees = 0; - if let Ok(ton_block::TransactionDescr::Ordinary(description)) = - transaction.description.read_struct() - { - fees = nekoton_utils::compute_total_transaction_fees(transaction, &description) +fn compute_fees(transaction: &Transaction) -> u128 { + let mut total_fees = 0; + if let Ok(TxInfo::Ordinary(info)) = transaction.load_info() { + total_fees += compute_total_transaction_fees(transaction, &info); } - fees + total_fees } -fn is_aborted(transaction: &ton_block::Transaction) -> bool { +pub fn compute_balance_change(transaction: &Transaction) -> i128 { + let mut diff = 0; + + if let Ok(Some(in_msg)) = transaction.load_in_msg() { + if let MsgInfo::Int(header) = in_msg.info { + diff += header.value.tokens.into_inner() as i128; + } + } + + for message in transaction.iter_out_msgs() { + let message = message.unwrap(); + if let MsgInfo::Int(header) = message.info { + diff -= header.value.tokens.into_inner() as i128; + } + } + + if let Ok(TxInfo::Ordinary(info)) = transaction.load_info() { + diff -= compute_total_transaction_fees(transaction, &info) as i128; + } + + diff +} + +pub fn compute_total_transaction_fees(transaction: &Transaction, info: &OrdinaryTxInfo) -> u128 { + let mut total_fees = transaction.total_fees.tokens.into_inner(); + if let Some(phase) = &info.action_phase { + total_fees += phase + .total_fwd_fees + .as_ref() + .map(|tokens| tokens.into_inner()) + .unwrap_or_default(); + total_fees -= phase + .total_action_fees + .as_ref() + .map(|tokens| tokens.into_inner()) + .unwrap_or_default(); + }; + if let Some(BouncePhase::Executed(phase)) = &info.bounce_phase { + total_fees += phase.fwd_fees.into_inner(); + } + total_fees +} + +fn is_aborted(transaction: &Transaction) -> bool { let mut aborted = false; - if let Ok(ton_block::TransactionDescr::Ordinary(description)) = - transaction.description.read_struct() - { - aborted = description.aborted + if let Ok(TxInfo::Ordinary(info)) = transaction.load_info() { + aborted = info.aborted } aborted } @@ -247,10 +284,9 @@ struct MessageRecipient { #[cfg(test)] mod tests { use super::*; - use ton_block::{Deserializable, MsgAddressInt, Transaction}; fn mock_transaction_with_message() -> Transaction { - Transaction::construct_from_base64( + let binding = Boc::decode_base64( "te6ccgECEAEAAwgAA7d+QDCcWfS7Pd3OhqYgoQVempmo2OKQO5sOYx6EZBcyIbAAAuGThxKAhf1hAS\ h02tBmYeWRHurQLFdhsiPgWeGNTbabaiPlZZ9gAALhk4cSgGZnCjIwADSAIfqQaAUEAQIXBAkFUFwjGIAhHJARA\ wIAb8mKaBBMG8AMAAAAAAAEAAIAAAADVRiS8otLi359fajChkMh4j7YPNNVzsOUbNa9QsXWtVZBkDxsAJ5IegwV\ @@ -265,26 +301,26 @@ mod tests { wQAAgBBa0QhOP1bKLS5gSbcEj5AP6sELkypNssupdc0rEL8tMA4BQ4AQWtEITj9Wyi0uYEm3BI+QD+rBC5MqTbL\ LqXXNKxC/LTgPAAA=", ) - .unwrap() - } - - fn mock_transaction_without_message() -> Transaction { - Transaction::default() + .unwrap(); + let mut cell = binding.as_slice().unwrap(); + Transaction::load_from(&mut cell).unwrap() } fn mock_native_transaction() -> Transaction { - Transaction::construct_from_base64( + let binding = Boc::decode_base64( "te6ccgECBQEAAQ8AA7VxLMcNYtT0Y0vHvF0Y6p6uYuZ3ru6E15MPbdMAiDOW+TAAAxF6kJyoOBX6Ew\ /7kDzBL0X5vbiyJUQxs8oqMCx81lJVpHEGWGhQAALk17a3eDZv7sCQAABgJyfoAwIBABUMwE5PyQF9eEABIACCc\ qeMvpds7qXtp0X7fcfK29e715cYDMD4djDoZFaoV2+IniF4UEqnl0mRBkkJUofiHH0OEnxt4bqWdhOvrktU02MB\ AaAEALFIAQWtEITj9Wyi0uYEm3BI+QD+rBC5MqTbLLqXXNKxC/LTAASzHDWLU9GNLx7xdGOqermLmd67uhNeTD2\ 3TAIgzlvk0BfXhAAGCiwwAABiL1ITlQTN/dgSQA==", ) - .unwrap() + .unwrap(); + let mut cell = binding.as_slice().unwrap(); + Transaction::load_from(&mut cell).unwrap() } fn mock_tip3_transaction() -> Transaction { - Transaction::construct_from_base64( + let binding = Boc::decode_base64( "te6ccgECDAEAAl0AA7V/QK7VX0Cd/1ZlF9CjnQU/zjx5R/+gPcjjC/w75jghPeAAAxF68tckc9Q9f/\ cDtEaGB89WcFPg7Kg/ufjqtloFybIORllBjolwAAMRevLXJGZv7tMQADR8gi0IBQQBAhUECQT+XD4YfDDMEQMCA\ G/Jg9CQTAosIAAAAAAABAACAAAAA/Sl/SUL5ko0FMc/s2rL0MTaDiZjYIA0X+j0FcjV3p3wQFAWDACeRzeMFHQo\ @@ -296,7 +332,9 @@ mod tests { N/dpiwAkBa2eguV8AAAAAAAAAAAAACRhOcqAAgBBa0QhOP1bKLS5gSbcEj5AP6sELkypNssupdc0rEL8tMAoBQ4\ AQWtEITj9Wyi0uYEm3BI+QD+rBC5MqTbLLqXXNKxC/LSgLAAA=", ) - .unwrap() + .unwrap(); + let mut cell = binding.as_slice().unwrap(); + Transaction::load_from(&mut cell).unwrap() } #[test] @@ -307,7 +345,7 @@ mod tests { assert_eq!( result.unwrap(), Some( - MsgAddressInt::from_str( + StdAddr::from_str( "0:fd7cb9aa109bec4fd39f3b8c3a21b661caacbc161a8c6331be6bb88a4e7ff720" ) .unwrap() @@ -323,7 +361,7 @@ mod tests { assert_eq!( result.unwrap(), Some( - MsgAddressInt::from_str( + StdAddr::from_str( "0:fd7cb9aa109bec4fd39f3b8c3a21b661caacbc161a8c6331be6bb88a4e7ff720" ) .unwrap() @@ -339,19 +377,11 @@ mod tests { assert_eq!( result.unwrap(), Some( - MsgAddressInt::from_str( + StdAddr::from_str( "0:82d6884271fab6516973024db8247c807f56085c99526d965d4bae695885f969" ) .unwrap() ) ); } - - #[test] - fn test_get_sender_address_without_message() { - // Simulate a transaction without an incoming message - let transaction = mock_transaction_without_message(); - let result = get_sender_address(&transaction); - assert!(result.is_err()); // Expect an error - } } diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index eaa0051..8049bff 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -361,10 +361,9 @@ impl StateSubscription { _ => continue, }; - let in_msg = match transaction.in_msg.as_ref() - { + let in_msg = match transaction.in_msg.as_ref() { Some(in_msg_cell) => { - let in_msg = OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)?; + let in_msg = OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)?; if matches!(in_msg.info, MsgInfo::ExtIn(_)) { messages_queue.deliver_message( account, @@ -376,7 +375,6 @@ impl StateSubscription { _ => continue, }; - let ctx = TxContext { block_info_gen_utime: block_info.gen_utime, block_hash: &block_hash, @@ -473,20 +471,11 @@ impl TokenSubscription { .ok_or_else(|| TonCoreError::AccountNotExist(account.to_string()))?; let (token_wallet_details, ..) = get_token_wallet_details(&token_contract)?; - let owner_account = - &token_wallet_details - .owner_address - ; - - if state_subscriptions - .get(owner_account) - .is_some() - { - let in_msg = match transaction.in_msg.as_ref() - { - Some(in_msg_cell) => { - OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)? - } + let owner_account = &token_wallet_details.owner_address; + + if state_subscriptions.get(owner_account).is_some() { + let in_msg = match transaction.in_msg.as_ref() { + Some(in_msg_cell) => OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)?, _ => continue, }; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index fc9e970..4406397 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -28,11 +28,11 @@ mod wallets; pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; -pub fn conver_to_old_transaction(transaction: &Transaction) -> Result { +pub fn conver_to_old_transaction(transaction: &Transaction) -> Result { let cell = CellBuilder::build_from(transaction)?; let bytes = Boc::encode(cell); let cell = ton_types::deserialize_tree_of_cells(&mut &*bytes)?; - ton_block::Transaction::construct_from_cell(cell) + Transaction::construct_from_cell(cell) } macro_rules! declare_function { diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index df204be..800d823 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -10,7 +10,9 @@ use tycho_types::cell::{Cell, HashBytes}; use tycho_types::models::StdAddr; use crate::models::ExistingContract; -use crate::utils::token_wallets::models::{RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion}; +use crate::utils::token_wallets::models::{ + RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion, +}; const INITIAL_BALANCE: u128 = 100_000_000; // 0.1 diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs index 2492aa2..a150f66 100644 --- a/src/utils/token_wallets/models.rs +++ b/src/utils/token_wallets/models.rs @@ -1,6 +1,9 @@ use num_bigint::BigUint; use serde::{Deserialize, Serialize}; -use tycho_types::{cell::{Cell, HashBytes}, models::{OrdinaryTxInfo, StdAddr, Transaction}}; +use tycho_types::{ + cell::{Cell, HashBytes}, + models::{OrdinaryTxInfo, StdAddr, Transaction}, +}; use crate::utils::{serde_address, serde_cell, serde_string}; @@ -58,7 +61,6 @@ pub struct TokenSwapBack { pub callback_payload: Cell, } - #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum TokenWalletVersion { /// Third iteration of token wallets, but with fixed bugs @@ -69,7 +71,6 @@ pub enum TokenWalletVersion { Tip3, } - #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct RootTokenContractDetails { /// Token ecosystem version @@ -176,7 +177,6 @@ impl TokenWalletContractState<'_> { } } - pub fn parse_token_transaction( tx: &Transaction, info: &OrdinaryTxInfo, diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs index 5062023..7e7a7d9 100644 --- a/src/utils/ton_wallet/mod.rs +++ b/src/utils/ton_wallet/mod.rs @@ -5,11 +5,11 @@ use anyhow::Result; use ed25519_dalek::VerifyingKey; use serde::{Deserialize, Serialize}; -use nekoton_utils::*; use tycho_types::abi::FromAbi; use tycho_types::cell::{Cell, HashBytes}; use tycho_types::models::{StateInit, StdAddr}; +use crate::utils::serde_string; use crate::utils::wallets; pub use self::multisig::MultisigType; diff --git a/src/utils/tx_context.rs b/src/utils/tx_context.rs index 95e66d3..075aedf 100644 --- a/src/utils/tx_context.rs +++ b/src/utils/tx_context.rs @@ -87,8 +87,8 @@ impl TxContext<'_> { continue; } - if let Ok(function_id) = message.body.get_u32(0){ - f(function_id, message.body) + if let Ok(function_id) = message.body.get_u32(0) { + f(function_id, message.body) } } } From 4ea91ece42e983d0228956f7ff3d61e990ed82cf Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 5 Feb 2026 13:29:19 +0300 Subject: [PATCH 21/59] fix token parsing --- src/api/requests/misc.rs | 1 - src/api/responses/address.rs | 6 +- src/api/responses/misc.rs | 2 +- src/client/callback/mod.rs | 2 +- src/models/account_enums.rs | 5 +- src/models/owners_cache.rs | 30 +++--- src/settings.rs | 6 +- src/ton_core/mod.rs | 3 +- src/ton_core/monitoring/token_transaction.rs | 1 - .../monitoring/token_transaction_parser.rs | 101 +++++++----------- src/ton_core/ton_subscriber/mod.rs | 2 - src/utils/token_wallets/models.rs | 11 ++ 12 files changed, 79 insertions(+), 91 deletions(-) diff --git a/src/api/requests/misc.rs b/src/api/requests/misc.rs index db5d711..5397185 100644 --- a/src/api/requests/misc.rs +++ b/src/api/requests/misc.rs @@ -2,7 +2,6 @@ use bigdecimal::BigDecimal; use schemars::JsonSchema; use serde::Deserialize; -use ton_abi::Param; use uuid::Uuid; use crate::api::any_schema; diff --git a/src/api/responses/address.rs b/src/api/responses/address.rs index 53e18d4..4169879 100644 --- a/src/api/responses/address.rs +++ b/src/api/responses/address.rs @@ -107,7 +107,7 @@ pub struct AddressBalanceDataResponse { impl AddressBalanceDataResponse { pub fn new(a: AddressDb, b: NetworkAddressData) -> Self { - let account = StdAddr::from_str(&format!("{}:{}", a.workchain_id, a.hex)).trust_me(); + let account = StdAddr::from_str(&format!("{}:{}", a.workchain_id, a.hex)).unwrap(); let base64url = Address(account.display_base64_url(true).to_string()); Self { @@ -171,7 +171,7 @@ pub struct AddressInfoDataResponse { impl AddressInfoDataResponse { pub fn new(a: AddressDb) -> Self { - let account = StdAddr::from_str(&format!("{}:{}", a.workchain_id, a.hex)).trust_me(); + let account = StdAddr::from_str(&format!("{}:{}", a.workchain_id, a.hex)).unwrap(); let base64url = Address(account.display_base64_url(true).to_string()); Self { @@ -235,7 +235,7 @@ pub struct TokenBalanceDataResponse { impl TokenBalanceDataResponse { pub fn new(a: TokenBalanceFromDb, b: NetworkTokenAddressData) -> Self { let account = - StdAddr::from_str(&format!("{}:{}", a.account_workchain_id, a.account_hex)).trust_me(); + StdAddr::from_str(&format!("{}:{}", a.account_workchain_id, a.account_hex)).unwrap(); let base64url = Address(account.display_base64_url(true).to_string()); Self { diff --git a/src/api/responses/misc.rs b/src/api/responses/misc.rs index 8e56b2f..64d5004 100644 --- a/src/api/responses/misc.rs +++ b/src/api/responses/misc.rs @@ -1,4 +1,4 @@ -use crate::models::WhitelistedTokenFromDb; +use crate::{models::WhitelistedTokenFromDb, utils::token_wallets::models::TokenWalletVersion}; use schemars::JsonSchema; use serde::Serialize; diff --git a/src/client/callback/mod.rs b/src/client/callback/mod.rs index d3b21c4..bbf089e 100644 --- a/src/client/callback/mod.rs +++ b/src/client/callback/mod.rs @@ -12,7 +12,7 @@ pub struct CallbackClient { impl CallbackClient { pub fn new() -> Self { Self { - client: reqwest::ClientBuilder::new().build().trust_me(), + client: reqwest::ClientBuilder::new().build().unwrap(), } } } diff --git a/src/models/account_enums.rs b/src/models/account_enums.rs index 6acfae2..31d5da0 100644 --- a/src/models/account_enums.rs +++ b/src/models/account_enums.rs @@ -5,7 +5,10 @@ use serde::{Deserialize, Serialize}; use strum_macros::EnumString; use tycho_types::models::{AccountState, StdAddr}; -use crate::models::{Address, AddressDb}; +use crate::{ + models::{Address, AddressDb}, + utils::token_wallets::models::TokenWalletVersion, +}; #[derive( Debug, Default, Deserialize, Serialize, Clone, JsonSchema, Eq, PartialEq, sqlx::Type, Copy, diff --git a/src/models/owners_cache.rs b/src/models/owners_cache.rs index a377e69..45e17e0 100644 --- a/src/models/owners_cache.rs +++ b/src/models/owners_cache.rs @@ -4,10 +4,11 @@ use std::sync::Arc; use lru::LruCache; use parking_lot::Mutex; -use ton_block::StdAddr; +use tycho_types::models::StdAddr; use crate::models::sqlx::*; use crate::sqlx_client::*; +use crate::utils::token_wallets::models::TokenWalletVersion; #[derive(Clone)] /// Maps token wallet address to Owner info @@ -35,8 +36,8 @@ impl OwnersCache { "{}:{}", got.owner_account_workchain_id, got.owner_account_hex )) - .trust_me(), - root_address: StdAddr::from_str(&got.root_address).trust_me(), + .unwrap(), + root_address: StdAddr::from_str(&got.root_address).unwrap(), code_hash: got.code_hash, version: got.version.into(), } @@ -50,8 +51,8 @@ impl OwnersCache { } let owner = TokenOwnerFromDb { address: key.to_string(), - owner_account_workchain_id: value.owner_address.workchain_id(), - owner_account_hex: value.owner_address.address().to_hex_string(), + owner_account_workchain_id: value.owner_address.workchain as i32, + owner_account_hex: value.owner_address.address.to_string(), root_address: value.root_address.to_string(), code_hash: value.code_hash, created_at: chrono::Utc::now().naive_utc(), //doesn't matter @@ -75,17 +76,20 @@ impl OwnersCache { pub async fn new(sqlx_client: SqlxClient) -> Result { let balances = sqlx_client.get_all_token_owners().await?; // no more than 10 mb - let mut cache = LruCache::new(NonZeroUsize::new(5000).trust_me()); + let mut cache = LruCache::new(NonZeroUsize::new(5000).unwrap()); balances.into_iter().for_each(|x| { + let k = StdAddr::from_str(&x.address).unwrap(); + let owner_address = StdAddr::from_str(&format!( + "{}:{}", + x.owner_account_workchain_id, x.owner_account_hex + )) + .unwrap(); + let root_address = StdAddr::from_str(&x.root_address).unwrap(); cache.put( - StdAddr::from_str(&x.address).trust_me(), + k, OwnerInfo { - owner_address: StdAddr::from_str(&format!( - "{}:{}", - x.owner_account_workchain_id, x.owner_account_hex - )) - .trust_me(), - root_address: StdAddr::from_str(&x.root_address).trust_me(), + owner_address, + root_address, code_hash: x.code_hash, version: x.version.into(), }, diff --git a/src/settings.rs b/src/settings.rs index cdd8b25..b62c398 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -71,14 +71,16 @@ fn default_key() -> Vec { let mut options = argon2::ParamsBuilder::default(); let options = options .output_len(32) //chacha key size - .and_then(|x| x.clone().params())?; + .and_then(|x| x.clone().params()) + .unwrap(); // Argon2 with default params (Argon2id v19) let argon2 = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, options); let key = argon2 - .hash_password(secret.as_bytes(), &salt)? + .hash_password(secret.as_bytes(), &salt) + .unwrap() .hash .context("No hash")? .as_bytes() diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index 917d4f8..ead3f70 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -14,6 +14,7 @@ use tycho_types::cell::HashBytes; use tycho_types::cell::Lazy; use tycho_types::cell::Load; use tycho_types::models::*; +use tycho_util::time::now_sec; use self::monitoring::*; use self::ton_subscriber::*; @@ -240,7 +241,7 @@ impl TonCoreContext { let config_cell = Boc::decode_base64("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").unwrap(); let config = config_cell.parse::()?; - let config = ParsedConfig::parse(config, SimpleClock.now_sec_u64() as u32)?; + let config = ParsedConfig::parse(config, now_sec() as u32)?; let cell = Boc::decode_base64(message_base64)?; let mut cs = cell.as_slice()?; diff --git a/src/ton_core/monitoring/token_transaction.rs b/src/ton_core/monitoring/token_transaction.rs index cf1e9b0..06e289d 100644 --- a/src/ton_core/monitoring/token_transaction.rs +++ b/src/ton_core/monitoring/token_transaction.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use anyhow::Result; use tokio::sync::mpsc; -use ton_types::HashBytes; use crate::ton_core::monitoring::*; use crate::ton_core::*; diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index 6bab8a4..ace4b9d 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -1,8 +1,7 @@ use anyhow::Result; use bigdecimal::BigDecimal; use num_bigint::BigUint; -use ton_block::{GetRepresentationHash, StdAddr}; -use ton_types::{AccountId, BuilderData}; +use tycho_types::cell::Cell; use uuid::Uuid; use crate::ton_core::*; @@ -55,48 +54,42 @@ pub async fn parse_token_transaction( async fn internal_transfer_send( token_transaction_ctx: TokenTransactionContext, tokens: BigUint, - payload_cell: Option, + payload_cell: Option, parse_ctx: ParseContext<'_>, ) -> Result { - let address = StdAddr::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(token_transaction_ctx.account), - )?; + let address = StdAddr::new(0, token_transaction_ctx.account); let owner_info = get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; let mut message_hash = Default::default(); - let _ = token_transaction_ctx - .transaction - .out_msgs - .iterate(|ton_block::InRefValue(message)| { - message_hash = message.hash().unwrap_or_default().to_hex_string(); - Ok(false) - }); + for message in token_transaction_ctx.transaction.iter_out_msgs() { + let message = message?; + let cell_builder = CellBuilder::build_from(&message)?; + message_hash = cell_builder.repr_hash().to_string(); + } let in_message_hash = token_transaction_ctx .transaction .in_msg - .clone() - .map(|message| message.hash().to_hex_string()) + .as_ref() + .map(|message| message.repr_hash().to_string()) .unwrap_or_default(); let transaction = CreateTokenTransaction { id: Uuid::new_v4(), - transaction_hash: Some(token_transaction_ctx.transaction_hash.to_hex_string()), + transaction_hash: Some(token_transaction_ctx.transaction_hash.to_string()), transaction_timestamp: token_transaction_ctx.block_utime, message_hash, owner_message_hash: None, - account_workchain_id: owner_info.owner_address.workchain_id(), - account_hex: owner_info.owner_address.address().to_hex_string(), + account_workchain_id: owner_info.owner_address.workchain as i32, + account_hex: owner_info.owner_address.address.to_string(), sender_workchain_id: None, sender_hex: None, root_address: owner_info.root_address.to_string(), value: -BigDecimal::new(tokens.into(), 0), - payload: payload_cell.map(|c| c.write_to_bytes()).transpose()?, - block_hash: token_transaction_ctx.block_hash.to_hex_string(), + payload: payload_cell.map(|c| Boc::encode(c)), + block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Send, status: TonTokenTransactionStatus::Done, @@ -112,11 +105,7 @@ async fn internal_transfer_receive( token_transfer: TokenIncomingTransfer, parse_ctx: ParseContext<'_>, ) -> Result { - let address = StdAddr::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(token_transaction_ctx.account), - )?; + let address = StdAddr::new(0, token_transaction_ctx.account); let owner_info = get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; @@ -125,36 +114,26 @@ async fn internal_transfer_receive( .transaction .in_msg .clone() - .map(|message| message.hash().to_hex_string()) + .map(|message| message.repr_hash().to_string()) .unwrap_or_default(); - let payload: Option = { - let mut bd = BuilderData::new(); - if token_transaction_ctx.in_msg.write_to(&mut bd).is_ok() { - Some(bd.into_cell()?) - } else { - None - } - }; + let payload = CellBuilder::build_from(token_transaction_ctx.in_msg)?; let transaction = CreateTokenTransaction { id: Uuid::new_v4(), - transaction_hash: Some(token_transaction_ctx.transaction_hash.to_hex_string()), + transaction_hash: Some(token_transaction_ctx.transaction_hash.to_string()), transaction_timestamp: token_transaction_ctx.block_utime, message_hash, owner_message_hash: None, account_workchain_id: owner_info.owner_address.get_workchain_id(), - account_hex: owner_info.owner_address.address().to_hex_string(), - sender_workchain_id: Some(token_transfer.sender_address.workchain_id()), - sender_hex: Some(token_transfer.sender_address.address().to_hex_string()), + account_hex: owner_info.owner_address.address().to_string(), + sender_workchain_id: Some(token_transfer.sender_address.workchain as i32), + sender_hex: Some(token_transfer.sender_address.address.to_string()), value: BigDecimal::new(token_transfer.tokens.into(), 0), root_address: owner_info.root_address.to_string(), - payload: payload - .map(|m| m.write_to_bytes()) - .transpose() - .unwrap_or(None), + payload: Some(Boc::encode(payload)), error: None, - block_hash: token_transaction_ctx.block_hash.to_hex_string(), + block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Receive, status: TonTokenTransactionStatus::Done, @@ -169,11 +148,7 @@ async fn internal_transfer_bounced( tokens: BigUint, parse_ctx: ParseContext<'_>, ) -> Result { - let address = StdAddr::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(token_transaction_ctx.account), - )?; + let address = StdAddr::new(0, token_transaction_ctx.account); let owner_info = get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; @@ -182,23 +157,23 @@ async fn internal_transfer_bounced( .transaction .in_msg .clone() - .map(|message| message.hash().to_hex_string()) + .map(|message| message.repr_hash().to_string()) .unwrap_or_default(); let transaction = CreateTokenTransaction { id: Uuid::new_v4(), - transaction_hash: Some(token_transaction_ctx.transaction_hash.to_hex_string()), + transaction_hash: Some(token_transaction_ctx.transaction_hash.to_string()), transaction_timestamp: token_transaction_ctx.block_utime, message_hash, owner_message_hash: None, - account_workchain_id: owner_info.owner_address.workchain_id(), - account_hex: owner_info.owner_address.address().to_hex_string(), + account_workchain_id: owner_info.owner_address.workchain as i32, + account_hex: owner_info.owner_address.address.to_string(), sender_workchain_id: None, sender_hex: None, root_address: owner_info.root_address.to_string(), value: BigDecimal::new(tokens.into(), 0), payload: None, - block_hash: token_transaction_ctx.block_hash.to_hex_string(), + block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Send, status: TonTokenTransactionStatus::Done, @@ -214,11 +189,7 @@ async fn internal_transfer_mint( tokens: BigUint, parse_ctx: ParseContext<'_>, ) -> Result { - let address = StdAddr::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(token_transaction_ctx.account), - )?; + let address = StdAddr::new(0, token_transaction_ctx.account); let owner_info = get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; @@ -227,24 +198,24 @@ async fn internal_transfer_mint( .transaction .in_msg .clone() - .map(|message| message.hash()) + .map(|message| *message.repr_hash()) .unwrap_or_default(); let transaction = CreateTokenTransaction { id: Uuid::new_v4(), - transaction_hash: Some(token_transaction_ctx.transaction_hash.to_hex_string()), + transaction_hash: Some(token_transaction_ctx.transaction_hash.to_string()), transaction_timestamp: token_transaction_ctx.block_utime, - message_hash: message_hash.to_hex_string(), + message_hash: message_hash.to_string(), owner_message_hash: None, account_workchain_id: owner_info.owner_address.get_workchain_id(), - account_hex: owner_info.owner_address.address().to_hex_string(), + account_hex: owner_info.owner_address.address.to_string(), sender_workchain_id: None, sender_hex: None, value: BigDecimal::new(tokens.into(), 0), root_address: owner_info.root_address.to_string(), payload: None, error: None, - block_hash: token_transaction_ctx.block_hash.to_hex_string(), + block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Receive, status: TonTokenTransactionStatus::Done, diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index 8049bff..8f9dcd2 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -5,8 +5,6 @@ use std::sync::{Arc, Weak}; use anyhow::Result; use futures::stream::FuturesUnordered; use futures::StreamExt; -use nekoton::core::models::TokenWalletVersion; -use nekoton_utils::TrustMe; use parking_lot::{Mutex, RwLock, RwLockReadGuard}; use rustc_hash::FxHashMap; use tycho_types::boc::Boc; diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs index a150f66..2ab027a 100644 --- a/src/utils/token_wallets/models.rs +++ b/src/utils/token_wallets/models.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use num_bigint::BigUint; use serde::{Deserialize, Serialize}; use tycho_types::{ @@ -71,6 +73,15 @@ pub enum TokenWalletVersion { Tip3, } +impl Display for TokenWalletVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + TokenWalletVersion::OldTip3v4 => "OldTip3v4", + TokenWalletVersion::Tip3 => "Tip3", + }) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct RootTokenContractDetails { /// Token ecosystem version From 99ae3565b7257181cfdd732634dca374373a6dc2 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Fri, 6 Feb 2026 16:15:10 +0300 Subject: [PATCH 22/59] fix token parsing --- src/api/controllers/misc.rs | 1 + src/api/requests/misc.rs | 7 +- src/models/transactions.rs | 7 +- src/services/ton.rs | 9 +- src/utils/existing_contract.rs | 3 - src/utils/mod.rs | 1 + src/utils/parsing.rs | 686 +++++++++++++++++++++++++++++++++ src/utils/token_wallet.rs | 3 - 8 files changed, 703 insertions(+), 14 deletions(-) create mode 100644 src/utils/parsing.rs diff --git a/src/api/controllers/misc.rs b/src/api/controllers/misc.rs index 234a25c..ceef4c7 100644 --- a/src/api/controllers/misc.rs +++ b/src/api/controllers/misc.rs @@ -2,6 +2,7 @@ use axum::extract::State; use axum::Json; use metrics::{histogram, increment_counter}; use tokio::time::Instant; +use tycho_types::abi::NamedAbiValue; use uuid::Uuid; use crate::api::controllers::*; diff --git a/src/api/requests/misc.rs b/src/api/requests/misc.rs index 5397185..f45e535 100644 --- a/src/api/requests/misc.rs +++ b/src/api/requests/misc.rs @@ -2,6 +2,7 @@ use bigdecimal::BigDecimal; use schemars::JsonSchema; use serde::Deserialize; +use tycho_types::abi::{AbiHeaderType, NamedAbiType}; use uuid::Uuid; use crate::api::any_schema; @@ -21,16 +22,16 @@ pub struct FunctionDetailsDTO { pub function_name: String, pub input_params: Vec, #[schemars(schema_with = "any_schema")] - pub output_params: Vec, + pub output_params: Vec, #[schemars(schema_with = "any_schema")] - pub headers: Vec, + pub headers: Vec, } #[derive(Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct InputParamDTO { #[schemars(schema_with = "any_schema")] - pub param: Param, + pub param: NamedAbiType, pub value: serde_json::Value, } diff --git a/src/models/transactions.rs b/src/models/transactions.rs index 66ab975..ccab5d0 100644 --- a/src/models/transactions.rs +++ b/src/models/transactions.rs @@ -1,7 +1,6 @@ use bigdecimal::BigDecimal; use serde::{Deserialize, Serialize}; -use ton_abi::Param; -use tycho_types::abi::{AbiHeaderType, NamedAbiType, NamedAbiValue}; +use tycho_types::abi::{AbiHeaderType, NamedAbiType}; use uuid::Uuid; use crate::models::*; @@ -182,14 +181,14 @@ pub struct SentTransaction { #[derive(Clone, Deserialize, Debug)] pub struct InputParam { - pub param: Param, + pub param: NamedAbiType, pub value: serde_json::Value, } #[derive(Clone, Debug, Deserialize)] pub struct FunctionDetails { pub function_name: String, - pub input_params: Vec, + pub input_params: Vec, pub output_params: Vec, pub headers: Vec, } diff --git a/src/services/ton.rs b/src/services/ton.rs index 48041c6..3f56a9d 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -878,13 +878,20 @@ impl TonService { self: &Arc, account_addr: &str, function_name: &str, - inputs: Vec, + inputs: Vec, outputs: Vec, headers: Vec, responsible: bool, ) -> Result { let account_addr = HashBytes::from_str(&account_addr).map_err(anyhow::Error::from)?; + let inputs = inputs.into_iter().map(|x| { + NamedAbiValue { + name: x.param.name, + value: x.param.ty, + } + }).collect(); + let input_params: Vec = inputs .iter() .map(|x| x.value.get_type().named(x.name.into())) diff --git a/src/utils/existing_contract.rs b/src/utils/existing_contract.rs index 68a1807..bd12cf7 100644 --- a/src/utils/existing_contract.rs +++ b/src/utils/existing_contract.rs @@ -1,7 +1,4 @@ use anyhow::Result; -use nekoton::transport::models::ExistingContract; -use nekoton_abi::{ExecutionOutput, FunctionExt, GenTimings, LastTransactionId, TransactionId}; -use ton_block::{Account, ShardAccount}; pub trait ExistingContractExt { fn from_shard_account(shard_account: &ShardAccount) -> Result>; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 4406397..9418c1d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -24,6 +24,7 @@ pub mod token_wallets; pub mod ton_wallet; mod tx_context; mod wallets; +pub mod parsing; pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; diff --git a/src/utils/parsing.rs b/src/utils/parsing.rs new file mode 100644 index 0000000..6dd8856 --- /dev/null +++ b/src/utils/parsing.rs @@ -0,0 +1,686 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use num_bigint::BigUint; +use tycho_types::{abi::{Function, NamedAbiValue}, cell::{Cell, CellSlice, HashBytes, Load}, models::{MsgInfo, OwnedMessage, Transaction}}; + +use crate::utils::{ton_wallet::MultisigType, wallets::multisig::MultisigTransaction}; + + +pub struct InputMessage(pub Vec); + +pub struct ContractCall { + pub inputs: Vec, + pub outputs: Vec, +} + +pub fn parse_multisig_transaction( + multisig_type: MultisigType, + tx: &Transaction, +) -> Option { + let in_msg_cell = tx.in_msg.as_ref()?; + + let Ok(mut slice) = in_msg_cell.as_slice() else { + return None; + }; + let Ok(in_msg) = OwnedMessage::load_from(&mut slice) else { + return None; + }; + + if !matches!(in_msg.info, MsgInfo::ExtIn(_)) { + return None; + } + parse_multisig_transaction_impl(multisig_type, in_msg, tx) +} + +fn parse_multisig_transaction_impl( + multisig_type: MultisigType, + in_msg: OwnedMessage, + tx: &Transaction, +) -> Option { + const PUBKEY_OFFSET: usize = 1 + ed25519_dalek::SIGNATURE_LENGTH * 8 + 1; + const PUBKEY_LENGTH: usize = 256; + const TIME_LENGTH: usize = 64; + const EXPIRE_LENGTH: usize = 32; + + let body = in_msg.body; + let Ok(mut body_slice) = body.clone().1.as_slice() else { + return None; + }; + + // Shift body by Maybe(signature), Maybe(pubkey), time and expire + body_slice.skip_first((PUBKEY_OFFSET + PUBKEY_LENGTH + TIME_LENGTH + EXPIRE_LENGTH) as u16, 0) + .ok()?; + + let Ok(function_id) = body_slice.get_u32(0) + else { + return None; + }; + + let parse_tx_input = |function: &Function, + mut slice: CellSlice<'_>| + -> Option<(HashBytes, InputMessage)> { + let inputs = function.decode_internal_input(slice).ok()?; + slice.skip_first(PUBKEY_OFFSET as u16, 0).ok()?; + let custodian = slice.load_u256().ok()?; + Some((custodian, InputMessage(inputs))) + }; + + let parse_tx_full = |function: &Function, + body: Cell| + -> Option<(HashBytes, ContractCall)> { + let Ok(mut slice) = body.as_slice() else { + return None; + }; + let (custodian, InputMessage(inputs)) = parse_tx_input(function, slice)?; + let outputs = function.parse(tx).ok()?; + Some((custodian, ContractCall { inputs, outputs })) + }; + + let functions = MultisigFunctions::instance(multisig_type); + + if function_id == functions.send_transaction.input_id { + let inputs = functions + .send_transaction + .decode_input(body, false, false) + .ok()?; + MultisigSendTransaction::try_from(InputMessage(inputs)) + .map(MultisigTransaction::Send) + .ok() + } else if function_id == functions.submit_transaction.input_id { + let (custodian, value) = parse_tx_full(functions.submit_transaction, body)?; + MultisigSubmitTransaction::try_from((custodian, value)) + .map(MultisigTransaction::Submit) + .ok() + } else if function_id == functions.confirm_transaction.input_id { + let (custodian, value) = parse_tx_input(functions.confirm_transaction, body)?; + MultisigConfirmTransaction::try_from((custodian, value)) + .map(MultisigTransaction::Confirm) + .ok() + } else if let Some(functions) = &functions.update_functions { + if function_id == functions.submit_update.input_id { + let (custodian, value) = parse_tx_full(functions.submit_update, body)?; + MultisigSubmitUpdate::try_from((custodian, value)) + .map(MultisigTransaction::SubmitUpdate) + .ok() + } else if function_id == functions.confirm_update.input_id { + let (custodian, value) = parse_tx_input(functions.confirm_update, body)?; + MultisigConfirmUpdate::try_from((custodian, value)) + .map(MultisigTransaction::ConfirmUpdate) + .ok() + } else if function_id == functions.execute_update.input_id { + let (custodian, value) = parse_tx_input(functions.execute_update, body)?; + MultisigExecuteUpdate::try_from((custodian, value)) + .map(MultisigTransaction::ExecuteUpdate) + .ok() + } else { + None + } + } else { + None + } +} + +struct MultisigFunctions { + send_transaction: &'static Function, + submit_transaction: &'static Function, + confirm_transaction: &'static Function, + update_functions: Option, +} + +struct UpdateFunctions { + submit_update: &'static Function, + confirm_update: &'static Function, + execute_update: &'static Function, +} + +impl MultisigFunctions { + fn instance(multisig_type: MultisigType) -> &'static Self { + use nekoton_contracts::wallets::{multisig, multisig2}; + + static OLD_FUNCTIONS: OnceBox = OnceBox::new(); + static NEW_FUNCTIONS: OnceBox = OnceBox::new(); + + match multisig_type { + ty if ty.is_multisig2() => NEW_FUNCTIONS.get_or_init(|| { + Box::new(MultisigFunctions { + send_transaction: multisig2::send_transaction(), + submit_transaction: multisig2::submit_transaction(), + confirm_transaction: multisig2::confirm_transaction(), + update_functions: Some(UpdateFunctions { + submit_update: multisig2::submit_update(), + confirm_update: multisig2::confirm_update(), + execute_update: multisig2::execute_update(), + }), + }) + }), + _ => OLD_FUNCTIONS.get_or_init(|| { + Box::new(MultisigFunctions { + send_transaction: multisig::send_transaction(), + submit_transaction: multisig::submit_transaction(), + confirm_transaction: multisig::confirm_transaction(), + update_functions: None, + }) + }), + } + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, InputMessage)) -> Result { + let output: MultisigConfirmTransaction = value.0.unpack()?; + Ok(Self { + custodian, + transaction_id: output.transaction_id, + }) + } +} + +#[derive(UnpackAbiPlain)] +struct MultisigSubmitTransactionInput { + #[abi(address)] + dest: MsgAddressInt, + #[abi(with = "uint128_number")] + value: BigUint, + #[abi(bool)] + bounce: bool, + #[abi(bool, name = "allBalance")] + all_balance: bool, + #[abi(cell)] + payload: ton_types::Cell, +} + +#[derive(UnpackAbiPlain)] +struct MultisigSubmitTransactionOutput { + #[abi(uint64, name = "transId")] + trans_id: u64, +} + +impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitTransaction { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { + let input: MultisigSubmitTransactionInput = value.inputs.unpack()?; + let output: MultisigSubmitTransactionOutput = value.outputs.unpack()?; + + Ok(Self { + custodian, + dest: input.dest, + value: input.value, + bounce: input.bounce, + all_balance: input.all_balance, + payload: input.payload, + trans_id: output.trans_id, + }) + } +} + +impl TryFrom for MultisigSendTransaction { + type Error = UnpackerError; + + fn try_from(value: InputMessage) -> Result { + let input: MultisigSendTransaction = value.0.unpack()?; + + Ok(Self { + dest: input.dest, + value: input.value, + bounce: input.bounce, + flags: input.flags, + payload: input.payload, + }) + } +} + +impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitUpdate { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { + use nekoton_contracts::wallets::multisig2; + + let input: multisig2::SubmitUpdateParams = value.inputs.unpack()?; + let output: multisig2::SubmitUpdateOutput = value.outputs.unpack()?; + + Ok(Self { + custodian, + new_code_hash: input.code_hash, + new_owners: input.owners.is_some(), + new_req_confirms: input.req_confirms.is_some(), + new_lifetime: input.lifetime.is_some(), + update_id: output.update_id, + }) + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmUpdate { + type Error = UnpackerError; + + fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { + use nekoton_contracts::wallets::multisig2; + + let input: multisig2::ConfirmUpdateParams = input.0.unpack()?; + Ok(Self { + custodian, + update_id: input.update_id, + }) + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigExecuteUpdate { + type Error = UnpackerError; + + fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { + use nekoton_contracts::wallets::multisig2; + + let input: multisig2::ExecuteUpdateParams = input.0.unpack()?; + Ok(Self { + custodian, + update_id: input.update_id, + }) + } +} + +pub fn parse_token_transaction( + tx: &Transaction, + description: &TransactionDescrOrdinary, + version: TokenWalletVersion, +) -> Option { + if description.aborted { + return None; + } + + let in_msg = tx.in_msg.as_ref()?.read_struct().ok()?; + + let mut body = in_msg.body()?; + let function_id = read_function_id(&body).ok()?; + + let header = in_msg.int_header()?; + + let functions = TokenWalletFunctions::for_version(version); + + if header.bounced { + body.move_by(32).ok()?; + let function_id = read_function_id(&body).ok()?; + body.move_by(32).ok()?; + + if function_id == functions.accept_transfer.input_id { + return Some(TokenWalletTransaction::TransferBounced( + body.get_next_u128().ok()?.into(), + )); + } + + if function_id == functions.accept_burn.input_id { + Some(TokenWalletTransaction::SwapBackBounced( + body.get_next_u128().ok()?.into(), + )) + } else { + None + } + } else if function_id == functions.accept_mint.input_id { + let inputs = functions.accept_mint.decode_input(body, true, false).ok()?; + + Accept::try_from((InputMessage(inputs), version)) + .map(|Accept { tokens }| TokenWalletTransaction::Accept(tokens)) + .ok() + } else if function_id == functions.transfer_to_wallet.input_id { + let inputs = functions + .transfer_to_wallet + .decode_input(body, true, false) + .ok()?; + + TokenOutgoingTransfer::try_from(( + InputMessage(inputs), + TransferType::ByTokenWalletAddress, + version, + )) + .map(TokenWalletTransaction::OutgoingTransfer) + .ok() + } else if function_id == functions.transfer.input_id { + let inputs = functions.transfer.decode_input(body, true, false).ok()?; + + TokenOutgoingTransfer::try_from(( + InputMessage(inputs), + TransferType::ByOwnerWalletAddress, + version, + )) + .map(TokenWalletTransaction::OutgoingTransfer) + .ok() + } else if function_id == functions.accept_transfer.input_id { + let inputs = functions + .accept_transfer + .decode_input(body, true, false) + .ok()?; + + TokenIncomingTransfer::try_from((InputMessage(inputs), version)) + .map(TokenWalletTransaction::IncomingTransfer) + .ok() + } else if function_id == functions.burn.input_id { + let inputs = functions.burn.decode_input(body, true, false).ok()?; + + TokenSwapBack::try_from((InputMessage(inputs), version)) + .map(TokenWalletTransaction::SwapBack) + .ok() + } else { + None + } +} + +struct TokenWalletFunctions { + // Incoming + accept_mint: &'static Function, + // Incoming + transfer: &'static Function, + // Incoming + transfer_to_wallet: &'static Function, + // Incoming + accept_transfer: &'static Function, + // Incoming + burn: &'static Function, + // Outgoing + accept_burn: &'static Function, +} + +impl TokenWalletFunctions { + pub fn for_version(version: TokenWalletVersion) -> &'static TokenWalletFunctions { + match version { + TokenWalletVersion::OldTip3v4 => { + static IDS: OnceBox = OnceBox::new(); + IDS.get_or_init(|| { + Box::new(Self { + accept_mint: old_tip3::token_wallet_contract::accept(), + transfer: old_tip3::token_wallet_contract::transfer_to_recipient(), + transfer_to_wallet: old_tip3::token_wallet_contract::transfer(), + accept_transfer: old_tip3::token_wallet_contract::internal_transfer(), + burn: old_tip3::token_wallet_contract::burn_by_owner(), + accept_burn: old_tip3::root_token_contract::tokens_burned(), + }) + }) + } + TokenWalletVersion::Tip3 => { + static IDS: OnceBox = OnceBox::new(); + IDS.get_or_init(|| { + Box::new(Self { + accept_mint: tip3_1::token_wallet_contract::accept_mint(), + transfer: tip3_1::token_wallet_contract::transfer(), + transfer_to_wallet: tip3_1::token_wallet_contract::transfer_to_wallet(), + accept_transfer: tip3_1::token_wallet_contract::accept_transfer(), + burn: tip3_1::token_wallet_contract::burnable::burn(), + accept_burn: tip3_1::root_token_contract::accept_burn(), + }) + }) + } + } + } +} + +impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenSwapBack { + type Error = UnpackerError; + + fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + let input: old_tip3::token_wallet_contract::BurnByOwnerInputs = value.0.unpack()?; + + Self { + tokens: input.tokens, + callback_address: input.callback_address, + callback_payload: input.callback_payload, + } + } + TokenWalletVersion::Tip3 => { + let input: tip3_1::token_wallet_contract::burnable::BurnInputs = + value.0.unpack()?; + + Self { + tokens: input.amount, + callback_address: input.callback_to, + callback_payload: input.payload, + } + } + }) + } +} + +struct Accept { + tokens: BigUint, +} + +impl TryFrom<(InputMessage, TokenWalletVersion)> for Accept { + type Error = UnpackerError; + + fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + let input: old_tip3::token_wallet_contract::AcceptInputs = value.0.unpack()?; + Self { + tokens: input.tokens, + } + } + TokenWalletVersion::Tip3 => { + let input: tip3_1::token_wallet_contract::AcceptMintInputs = value.0.unpack()?; + Self { + tokens: input.amount, + } + } + }) + } +} + +enum TransferType { + ByOwnerWalletAddress, + ByTokenWalletAddress, +} + +impl TryFrom<(InputMessage, TransferType, TokenWalletVersion)> for TokenOutgoingTransfer { + type Error = UnpackerError; + + fn try_from( + (value, transfer_type, version): (InputMessage, TransferType, TokenWalletVersion), + ) -> Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + match transfer_type { + // "transferToRecipient" + TransferType::ByOwnerWalletAddress => { + let input: old_tip3::token_wallet_contract::TransferToRecipientInputs = + value.0.unpack()?; + Self { + to: TransferRecipient::OwnerWallet(input.recipient_address), + tokens: input.tokens, + payload: input.payload, + } + } + // "transfer + TransferType::ByTokenWalletAddress => { + let input: old_tip3::token_wallet_contract::TransferInputs = + value.0.unpack()?; + Self { + to: TransferRecipient::TokenWallet(input.to), + tokens: input.tokens, + payload: input.payload, + } + } + } + } + TokenWalletVersion::Tip3 => { + match transfer_type { + // "transfer" + TransferType::ByOwnerWalletAddress => { + let input: tip3_1::token_wallet_contract::TransferInputs = + value.0.unpack()?; + Self { + to: TransferRecipient::OwnerWallet(input.recipient), + tokens: input.amount, + payload: input.payload, + } + } + // "transferToWallet" + TransferType::ByTokenWalletAddress => { + let input: tip3_1::token_wallet_contract::TransferToWalletInputs = + value.0.unpack()?; + Self { + to: TransferRecipient::TokenWallet(input.recipient_token_wallet), + tokens: input.amount, + payload: input.payload, + } + } + } + } + }) + } +} + +impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenIncomingTransfer { + type Error = UnpackerError; + + fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + let input: old_tip3::token_wallet_contract::InternalTransferInputs = + value.0.unpack()?; + + Self { + tokens: input.tokens, + sender_address: input.sender_address, + } + } + TokenWalletVersion::Tip3 => { + let input: tip3_1::token_wallet_contract::AcceptTransferInputs = + value.0.unpack()?; + + Self { + tokens: input.amount, + sender_address: input.sender, + } + } + }) + } +} + + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::*; + + fn parse_transaction(data: &str) -> (Transaction, TransactionDescrOrdinary) { + let tx = Transaction::construct_from_base64(data).unwrap(); + let description = match tx.description.read_struct().unwrap() { + TransactionDescr::Ordinary(description) => description, + _ => panic!(), + }; + (tx, description) + } + + #[test] + fn test_parse_wallet_v3_token_transfer_with_payload() { + let tx = Transaction::construct_from_base64("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").unwrap(); + println!("tx: {tx:#?}"); + + let description = match tx.description.read_struct() { + Ok(TransactionDescr::Ordinary(description)) => description, + _ => panic!(), + }; + println!("description: {description:#?}"); + + let parsed = parse_token_transaction(&tx, &description, TokenWalletVersion::Tip3); + println!("parsed tx: {parsed:#?}"); + } + + #[test] + fn test_parse_multisig_submit() { + let tx = Transaction::construct_from_base64("te6ccgECDAEAAkMAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAEv38uN8H+CfBrFklcU0i9Vs4RZzxi5vtTa9PqJ/LpPctz/rat2wAABIjJ0UsBX2sytAADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAOylU78GhKKYOUuj1Rh3dLpOOzgJUEyoySchhaM60lDREBQDowAnUfXAxOIAAAAAAAAAABtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnJ5QDnTzA46E1KOsPz7QLrshaiw53aaaTNY7TZfFM9uf9wCstMqmz8MmfSmYLSpRuMah9ruqiOVsRPjzhTEdu9aAgHgCAYBAd8HAHXn+7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u4AAAJfv5cb4S+1mVoSY7BZq+1mVo/lxvgwAFFif7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7gwJAeGUlZeW3g4p7fOroeyZUZdj1hWrKWusR/Na6V9uRhKJvV3dgWDQ1/YR5hQfYLaM861DgLJMku/LPDKMt43TyJUH+ToLdTA3yCwRnsc9IMg9JIXlsbI92/1mZ+RrZF1GGY1AAABdLq+AHhfazLsEx2CzYAoBY4AVKmRhQN1a9YbnwdGmdH0KtPv2SINcG4FpEDjh70ON2qAAAAAAAAAAAAAdjv+NHoAUCwAA").unwrap(); + + let custodian = + HashBytes::from_str("e4e82dd4c0df20b0467b1cf48320f4921796c6c8f76ff5999f91ad9175186635") + .unwrap(); + + assert!(matches!( + parse_transaction_additional_info( + &tx, + WalletType::Multisig(MultisigType::SafeMultisigWallet) + ) + .unwrap(), + TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + recipient: Some(_), + known_payload: None, + method: WalletInteractionMethod::Multisig(data) + }) if matches!(&*data, MultisigTransaction::Submit(submit) if submit.custodian == custodian) + )); + } + + #[test] + fn test_parse_multisig_confirm() { + let tx = Transaction::construct_from_base64("te6ccgECCgEAAjAAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAJcbrc/8GSsRcwsaEKUmFwdbT9tmaf3vKqKpeWIR9/9GyMA8r2+gAACXGutDTBYBvSYwADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAI1K3sqU+I63UTJ+xkdHcyrkM2hxcBJu//z7hF+/hEtukBQFcwAnUYtYxOIAAAAAAAAAABSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnITMJnhiVklA89yLWhQU+4BB1tJ3iPLRRZoWlPVKSkbvYENWnQphG03/JbEJJWwJbdhZCl+oH7UI7ARqCUcU6H/AgHgCAYBAd8HAK9J/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7vACa4ZyAEEjOHCY7aEkcDRTMruTfdNxrg9GyWxKU18Pes2WvMQekAAAAAABLjdbn/hMA3pMZAAUWJ/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7uDAkA8c+cpxQ8FYd2C/XWiibmIX4wPfvHIultapCNOhW5dJ5hl2YD+PHO24RUXdbY669yR8BUfGNuxVTwVkV1K0HA7QByTARuQhGj9eozhRteIImtsExhdcFckfL9FqBq5uNuaoAAAF3bK3Ps2Ab0p4ap0DtYBvF9mf0BgGA=").unwrap(); + + let custodian = + HashBytes::from_str("c93011b908468fd7a8ce146d788226b6c13185d7057247cbf45a81ab9b8db9aa") + .unwrap(); + + assert!(matches!( + parse_transaction_additional_info( + &tx, + WalletType::Multisig(MultisigType::SafeMultisigWallet) + ) + .unwrap(), + TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + recipient: None, + known_payload: None, + method: WalletInteractionMethod::Multisig(data) + }) if matches!(&*data, MultisigTransaction::Confirm(confirm) if confirm.custodian == custodian) + )) + } + + #[test] + fn test_parse_bounced_tokens_transfer() { + let (tx, description) = parse_transaction("te6ccgECCQEAAiEAA7V9jKvgMYxeLukedeW/PRr7QyRzEpkal33nb9KfgpelA3AAAO1mmxCMEy4UbEGiIQKVpE2nzO2Ar32k7H36ni1NMpxrcPorUNuwAADtZo+e3BYO9BHwADRwGMkIBQQBAhcMSgkCmI36GG92AhEDAgBvyYehIEwUWEAAAAAAAAQAAgAAAAKLF5Ge7DorMQ9dbEzZTgWK7Jiugap8s4dRpkiQl7CNEEBQFgwAnkP1TAqiBAAAAAAAAAAAtgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgnIBZa/nTbAD2Vcr8A6p+uT7XD4tLowmBLZEuIHLxU1zbeHGgHFi5dfeWnrNgtL3FHE6zw6ysjTJJI3LFFDAgPi3AgHgCAYBAd8HALFoAbGVfAYxi8XdI868t+ejX2hkjmJTI1LvvO36U/BS9KBvABgzjiRJUfoXsV99CuD/WnKK4QN5mlferMiVbk0Y3Jc3ECddFmAGFFhgAAAdrNNiEYTB3oI+QAD5WAHF6/YBDYNj7TABzedO3/4+ENpaE0PhwRx5NFYisFNfpQA2Mq+AxjF4u6R515b89GvtDJHMSmRqXfedv0p+Cl6UDdApiN+gBhRYYAAAHazSjHIEwd6CFH////+MaQuBAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEA="); + + assert!(matches!( + parse_token_transaction(&tx, &description, TokenWalletVersion::OldTip3v4).unwrap(), + TokenWalletTransaction::TransferBounced(_) + )); + } + + #[test] + fn test_parse_wallet_v5r1_transfer() { + let (tx, description) = parse_transaction("te6ccgECDgEAAroAA7V6PzxB5ur5JLcojkw57D91dcch0SdJBkRg11onChvcQxAAAuqQo7KQGfOICr+MryG/HTeCGLoHvR2QzQp8l/VW7Jy5KteDKoNgAALqkJdMvBZ0b1RwADRmUxQIBQQBAg8MQoYY8SmEQAMCAG/JhfBQTA/WGAAAAAAAAgAAAAAAAxZIaTNMW1cxmByM5WsWV9cxExzB5+1s+b7Uz5613xWmQNAtXACdQmljE4gAAAAAAAAAACPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIACCcp/sV0iKg0YadasmKflOuBQl9+BT1AMGK8jDUAHzabPWEAUiOhXPDSZMLX8X/WQ0jZRy1Ef+OW9TZFgZ7OoiSM4CAeAIBgEB3wcBsWgBR+eIPN1fJJblEcmHPYfurrjkOiTpIMiMGutE4UN7iGMABaelQoOWDtcjd5wKID6i0sUbGwEsUr2tFotsGs7AiEdQUQ/0AAYP1jQAAF1SFHZSBM6N6o7ACwHliAFH54g83V8kluURyYc9h+6uuOQ6JOkgyIwa60ThQ3uIYgObSztz///4izo3vDAAACqUsU/LVK+ma1KSpaW5p+h9917oIw6a7Txpn/VJg/WB7C5dJQYSdVOvNFZvNMz1vvv5wMwo33jnWdrh1jaHQJXGBQkCCg7DyG0DDQoBaGIAC09KhQcsHa5G7zgUQH1FpYo2NgJYpXtaLRbYNZ2BEI6goh/oAAAAAAAAAAAAAAAAAAELAbIPin6lAAAAAAAAAABUUC0RQAgAcgwTrCsIXFRhmQTVWMIpgapb1R1i6mXzRjfAhiAa+x8AKPzxB5ur5JLcojkw57D91dcch0SdJBkRg11onChvcQxIHJw4AQwACW7J3GUgAAA="); + assert!(!description.aborted); + + let wallet_transaction = parse_transaction_additional_info(&tx, WalletType::WalletV5R1); + assert!(wallet_transaction.is_some()); + + if let Some(TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + recipient, + known_payload, + .. + })) = wallet_transaction + { + assert_eq!( + recipient.unwrap(), + MsgAddressInt::from_str( + "0:169e950a0e583b5c8dde702880fa8b4b146c6c04b14af6b45a2db06b3b02211d" + ) + .unwrap() + ); + + assert!(known_payload.is_some()); + + let payload = known_payload.unwrap(); + if let KnownPayload::JettonOutgoingTransfer(JettonOutgoingTransfer { to, tokens }) = + payload + { + assert_eq!( + to, + MsgAddressInt::from_str( + "0:390609d615842e2a30cc826aac6114c0d52dea8eb17532f9a31be043100d7d8f" + ) + .unwrap() + ); + + assert_eq!(tokens.to_u128().unwrap(), 296400000000); + } + } + } +} diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index 800d823..82515c9 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -1,8 +1,5 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton_abi::ExecutionContext; -use nekoton_contracts::tip3_any::{RootTokenContractState, TokenWalletContractState}; -use nekoton_utils::SimpleClock; use num_bigint::BigUint; use num_traits::ToPrimitive; use tycho_types::abi::AbiValue; From 9324a1dcaf84932d93c12d416dbd27d270bbb43e Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Sat, 7 Feb 2026 10:49:08 +0300 Subject: [PATCH 23/59] fix parsing multisig and token --- src/api/controllers/misc.rs | 1 - src/services/ton.rs | 9 +- .../monitoring/token_transaction_parser.rs | 6 +- .../monitoring/ton_transaction_parser.rs | 26 +- src/ton_core/ton_subscriber/mod.rs | 12 +- src/utils/existing_contract.rs | 19 +- src/utils/mod.rs | 21 +- src/utils/multisig/mod.rs | 2 + src/utils/multisig/models.rs | 156 ++++++++ src/utils/multisig/parsing.rs | 344 +++++++++++++++++ src/utils/token_wallets/mod.rs | 1 + src/utils/token_wallets/models.rs | 85 ----- src/utils/{ => token_wallets}/parsing.rs | 346 ++---------------- 13 files changed, 575 insertions(+), 453 deletions(-) create mode 100644 src/utils/multisig/mod.rs create mode 100644 src/utils/multisig/models.rs create mode 100644 src/utils/multisig/parsing.rs rename src/utils/{ => token_wallets}/parsing.rs (62%) diff --git a/src/api/controllers/misc.rs b/src/api/controllers/misc.rs index ceef4c7..234a25c 100644 --- a/src/api/controllers/misc.rs +++ b/src/api/controllers/misc.rs @@ -2,7 +2,6 @@ use axum::extract::State; use axum::Json; use metrics::{histogram, increment_counter}; use tokio::time::Instant; -use tycho_types::abi::NamedAbiValue; use uuid::Uuid; use crate::api::controllers::*; diff --git a/src/services/ton.rs b/src/services/ton.rs index 3f56a9d..25f05a1 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -885,12 +885,13 @@ impl TonService { ) -> Result { let account_addr = HashBytes::from_str(&account_addr).map_err(anyhow::Error::from)?; - let inputs = inputs.into_iter().map(|x| { - NamedAbiValue { + let inputs = inputs + .into_iter() + .map(|x| NamedAbiValue { name: x.param.name, value: x.param.ty, - } - }).collect(); + }) + .collect(); let input_params: Vec = inputs .iter() diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index ace4b9d..f174207 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -125,8 +125,8 @@ async fn internal_transfer_receive( transaction_timestamp: token_transaction_ctx.block_utime, message_hash, owner_message_hash: None, - account_workchain_id: owner_info.owner_address.get_workchain_id(), - account_hex: owner_info.owner_address.address().to_string(), + account_workchain_id: owner_info.owner_address.workchain as i32, + account_hex: owner_info.owner_address.address.to_string(), sender_workchain_id: Some(token_transfer.sender_address.workchain as i32), sender_hex: Some(token_transfer.sender_address.address.to_string()), value: BigDecimal::new(token_transfer.tokens.into(), 0), @@ -207,7 +207,7 @@ async fn internal_transfer_mint( transaction_timestamp: token_transaction_ctx.block_utime, message_hash: message_hash.to_string(), owner_message_hash: None, - account_workchain_id: owner_info.owner_address.get_workchain_id(), + account_workchain_id: owner_info.owner_address.workchain as i32, account_hex: owner_info.owner_address.address.to_string(), sender_workchain_id: None, sender_hex: None, diff --git a/src/ton_core/monitoring/ton_transaction_parser.rs b/src/ton_core/monitoring/ton_transaction_parser.rs index 60058a4..0379ffa 100644 --- a/src/ton_core/monitoring/ton_transaction_parser.rs +++ b/src/ton_core/monitoring/ton_transaction_parser.rs @@ -4,7 +4,13 @@ use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ton_core::*, utils::ton_wallet::MultisigType}; +use crate::{ + ton_core::*, + utils::{ + multisig::{models::MultisigTransaction, parsing}, + ton_wallet::MultisigType, + }, +}; #[derive(thiserror::Error, Debug, Copy, Clone)] pub enum TransactionError { @@ -47,15 +53,15 @@ pub async fn parse_ton_transaction( let fee = BigDecimal::from_u128(compute_fees(&transaction)); let value = BigDecimal::from_u128(compute_value(&transaction)); let balance_change = BigDecimal::from_i128(compute_balance_change(&transaction)); - let multisig_transaction_id = nekoton::core::parsing::parse_multisig_transaction( - MultisigType::SafeMultisigWallet, - &transaction, - ) - .and_then(|transaction| match transaction { - MultisigTransaction::Confirm(transaction) => Some(transaction.transaction_id as i64), - MultisigTransaction::Submit(transaction) => Some(transaction.trans_id as i64), - _ => None, - }); + let multisig_transaction_id = + parsing::parse_multisig_transaction(MultisigType::SafeMultisigWallet, &transaction) + .and_then(|transaction| match transaction { + MultisigTransaction::Confirm(transaction) => { + Some(transaction.transaction_id as i64) + } + MultisigTransaction::Submit(transaction) => Some(transaction.trans_id as i64), + _ => None, + }); let parsed = match in_msg.info { MsgInfo::Int(header) => { diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index 8f9dcd2..041bb90 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -18,6 +18,8 @@ use tycho_vm::StackValue; use crate::models::ExistingContract; use crate::ton_core::*; +use crate::utils::token_wallets::models::TokenWalletVersion; +use crate::utils::token_wallets::parsing; pub struct TonSubscriber { // tip block timestamp @@ -252,7 +254,9 @@ impl TonSubscriber { }; } None => { - let token_subscription = token_subscription.as_ref().trust_me(); + let Some(token_subscription) = token_subscription.as_ref() else { + continue; + }; match token_subscription.handle_block( &state_subscriptions, @@ -450,13 +454,13 @@ impl TokenSubscription { _ => continue, }; - let parsed_token_transaction = match nekoton::core::parsing::parse_token_transaction( + let parsed_token_transaction = match parsing::parse_token_transaction( &transaction, &transaction_info, TokenWalletVersion::Tip3, ) { Some(parsed_token_transaction) => Some(parsed_token_transaction), - None => nekoton::core::parsing::parse_token_transaction( + None => parsing::parse_token_transaction( &transaction, &transaction_info, TokenWalletVersion::OldTip3v4, @@ -469,7 +473,7 @@ impl TokenSubscription { .ok_or_else(|| TonCoreError::AccountNotExist(account.to_string()))?; let (token_wallet_details, ..) = get_token_wallet_details(&token_contract)?; - let owner_account = &token_wallet_details.owner_address; + let owner_account = &token_wallet_details.owner_address.address; if state_subscriptions.get(owner_account).is_some() { let in_msg = match transaction.in_msg.as_ref() { diff --git a/src/utils/existing_contract.rs b/src/utils/existing_contract.rs index bd12cf7..9ab41a1 100644 --- a/src/utils/existing_contract.rs +++ b/src/utils/existing_contract.rs @@ -1,4 +1,10 @@ use anyhow::Result; +use tycho_types::{ + abi::{Function, NamedAbiValue}, + models::ShardAccount, +}; + +use crate::models::ExistingContract; pub trait ExistingContractExt { fn from_shard_account(shard_account: &ShardAccount) -> Result>; @@ -6,11 +12,8 @@ pub trait ExistingContractExt { shard_account: &Option, ) -> Result>; - fn run_local( - &self, - function: &ton_abi::Function, - input: &[ton_abi::Token], - ) -> Result>; + fn run_local(&self, function: &Function, input: &[NamedAbiValue]) + -> Result>; } impl ExistingContractExt for ExistingContract { @@ -37,9 +40,9 @@ impl ExistingContractExt for ExistingContract { fn run_local( &self, - function: &ton_abi::Function, - input: &[ton_abi::Token], - ) -> Result> { + function: &Function, + input: &[NamedAbiValue], + ) -> Result> { let ExecutionOutput { tokens, result_code, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9418c1d..6d68115 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,11 +1,6 @@ use std::hash::BuildHasherDefault; -use anyhow::Result; use rustc_hash::FxHasher; -use ton_block::Deserializable; -use tycho_types::boc::Boc; -use tycho_types::cell::CellBuilder; -use tycho_types::models::Transaction; pub use self::encoding::*; pub use self::existing_contract::*; @@ -17,6 +12,7 @@ pub use self::tx_context::*; mod encoding; mod existing_contract; pub mod mnemonic; +pub mod multisig; mod pending_messages_queue; mod shard_utils; mod token_wallet; @@ -24,18 +20,10 @@ pub mod token_wallets; pub mod ton_wallet; mod tx_context; mod wallets; -pub mod parsing; pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; -pub fn conver_to_old_transaction(transaction: &Transaction) -> Result { - let cell = CellBuilder::build_from(transaction)?; - let bytes = Boc::encode(cell); - let cell = ton_types::deserialize_tree_of_cells(&mut &*bytes)?; - Transaction::construct_from_cell(cell) -} - macro_rules! declare_function { ( $(abi: $abi:ident,)? @@ -156,3 +144,10 @@ pub mod serde_cell { Ok(cell) } } + +pub struct InputMessage(pub Vec); + +pub struct ContractCall { + pub inputs: Vec, + pub outputs: Vec, +} diff --git a/src/utils/multisig/mod.rs b/src/utils/multisig/mod.rs new file mode 100644 index 0000000..3efaa2d --- /dev/null +++ b/src/utils/multisig/mod.rs @@ -0,0 +1,2 @@ +pub mod models; +pub mod parsing; diff --git a/src/utils/multisig/models.rs b/src/utils/multisig/models.rs new file mode 100644 index 0000000..e7e1295 --- /dev/null +++ b/src/utils/multisig/models.rs @@ -0,0 +1,156 @@ +use serde::{Deserialize, Serialize}; +use tycho_types::{ + cell::{Cell, HashBytes}, + models::StdAddr, +}; + +use crate::utils::{serde_address, serde_cell, serde_string}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type", content = "data")] +pub enum MultisigTransaction { + Send(MultisigSendTransaction), + Submit(MultisigSubmitTransaction), + Confirm(MultisigConfirmTransaction), + SubmitUpdate(MultisigSubmitUpdate), + ConfirmUpdate(MultisigConfirmUpdate), + ExecuteUpdate(MultisigExecuteUpdate), +} + +#[derive(UnpackAbiPlain, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy)] +#[serde(rename_all = "camelCase")] +pub struct MultisigConfirmTransaction { + #[abi(skip)] + #[serde(with = "serde_HashBytes")] + pub custodian: HashBytes, + + #[abi(uint64, name = "transactionId")] + #[serde(with = "serde_string")] + pub transaction_id: u64, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MultisigSubmitTransaction { + #[serde(with = "serde_HashBytes")] + pub custodian: HashBytes, + + #[serde(with = "serde_address")] + pub dest: StdAddr, + + #[serde(with = "serde_string")] + pub value: u128, + + pub bounce: bool, + + pub all_balance: bool, + + #[serde(with = "serde_cell")] + pub payload: Cell, + + #[serde(with = "serde_string")] + pub trans_id: u64, +} + +#[derive(UnpackAbiPlain, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigSendTransaction { + #[abi(address)] + #[serde(with = "serde_address")] + pub dest: StdAddr, + + #[abi(with = "nekoton_abi::uint128_number")] + #[serde(with = "serde_string")] + pub value: u128, + + #[abi(bool)] + pub bounce: bool, + + #[abi(uint8)] + pub flags: u8, + + #[abi(cell)] + #[serde(with = "serde_cell")] + pub payload: Cell, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigSubmitUpdate { + #[serde(with = "serde_HashBytes")] + pub custodian: HashBytes, + #[serde(with = "serde_optional_HashBytes")] + pub new_code_hash: Option, + pub new_owners: bool, + pub new_req_confirms: bool, + pub new_lifetime: bool, + #[serde(with = "serde_string")] + pub update_id: u64, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigConfirmUpdate { + #[serde(with = "serde_HashBytes")] + pub custodian: HashBytes, + #[serde(with = "serde_string")] + pub update_id: u64, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigExecuteUpdate { + #[serde(with = "serde_HashBytes")] + pub custodian: HashBytes, + #[serde(with = "serde_string")] + pub update_id: u64, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigPendingTransaction { + #[serde(with = "serde_string")] + pub id: u64, + + #[serde(with = "serde_vec_HashBytes")] + pub confirmations: Vec, + + pub signs_required: u8, + pub signs_received: u8, + + #[serde(with = "serde_HashBytes")] + pub creator: HashBytes, + + pub index: u8, + + #[serde(with = "serde_address")] + pub dest: StdAddr, + + #[serde(with = "serde_string")] + pub value: u128, + + pub send_flags: u16, + + #[serde(with = "serde_cell")] + pub payload: Cell, + + pub bounce: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigPendingUpdate { + #[serde(with = "serde_string")] + pub id: u64, + + #[serde(with = "serde_vec_HashBytes")] + pub confirmations: Vec, + + pub signs_received: u8, + + #[serde(with = "serde_HashBytes")] + pub creator: HashBytes, + + pub index: u8, + + #[serde(with = "serde_optional_HashBytes")] + pub new_code_hash: Option, + #[serde(with = "serde_optional_vec_HashBytes")] + pub new_custodians: Option>, + pub new_req_confirms: Option, + pub new_lifetime: Option, +} diff --git a/src/utils/multisig/parsing.rs b/src/utils/multisig/parsing.rs new file mode 100644 index 0000000..a92066d --- /dev/null +++ b/src/utils/multisig/parsing.rs @@ -0,0 +1,344 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use num_bigint::BigUint; +use tycho_types::{ + abi::Function, + cell::{Cell, CellSlice, HashBytes, Load}, + models::{MsgInfo, OwnedMessage, Transaction}, +}; + +use crate::utils::{ + multisig::models::{ + MultisigConfirmTransaction, MultisigConfirmUpdate, MultisigExecuteUpdate, + MultisigSendTransaction, MultisigSubmitTransaction, MultisigSubmitUpdate, + MultisigTransaction, + }, + ton_wallet::{multisig::UnpackerError, MultisigType}, + wallets::{ + multisig::{self}, + multisig2, + }, + ContractCall, InputMessage, +}; + +pub fn parse_multisig_transaction( + multisig_type: MultisigType, + tx: &Transaction, +) -> Option { + let in_msg_cell = tx.in_msg.as_ref()?; + + let Ok(mut slice) = in_msg_cell.as_slice() else { + return None; + }; + let Ok(in_msg) = OwnedMessage::load_from(&mut slice) else { + return None; + }; + + if !matches!(in_msg.info, MsgInfo::ExtIn(_)) { + return None; + } + parse_multisig_transaction_impl(multisig_type, in_msg, tx) +} + +fn parse_multisig_transaction_impl( + multisig_type: MultisigType, + in_msg: OwnedMessage, + tx: &Transaction, +) -> Option { + const PUBKEY_OFFSET: usize = 1 + ed25519_dalek::SIGNATURE_LENGTH * 8 + 1; + const PUBKEY_LENGTH: usize = 256; + const TIME_LENGTH: usize = 64; + const EXPIRE_LENGTH: usize = 32; + + let body = in_msg.body; + let Ok(mut body_slice) = body.clone().1.as_slice() else { + return None; + }; + + // Shift body by Maybe(signature), Maybe(pubkey), time and expire + body_slice + .skip_first( + (PUBKEY_OFFSET + PUBKEY_LENGTH + TIME_LENGTH + EXPIRE_LENGTH) as u16, + 0, + ) + .ok()?; + + let Ok(function_id) = body_slice.get_u32(0) else { + return None; + }; + + let parse_tx_input = + |function: &Function, mut slice: CellSlice<'_>| -> Option<(HashBytes, InputMessage)> { + let inputs = function.decode_internal_input(slice).ok()?; + slice.skip_first(PUBKEY_OFFSET as u16, 0).ok()?; + let custodian = slice.load_u256().ok()?; + Some((custodian, InputMessage(inputs))) + }; + + let parse_tx_full = |function: &Function, body: Cell| -> Option<(HashBytes, ContractCall)> { + let Ok(mut slice) = body.as_slice() else { + return None; + }; + let (custodian, InputMessage(inputs)) = parse_tx_input(function, slice)?; + let outputs = function.parse(tx).ok()?; + Some((custodian, ContractCall { inputs, outputs })) + }; + + let functions = MultisigFunctions::instance(multisig_type); + + if function_id == functions.send_transaction.input_id { + let inputs = functions + .send_transaction + .decode_input(body, false, false) + .ok()?; + MultisigSendTransaction::try_from(InputMessage(inputs)) + .map(MultisigTransaction::Send) + .ok() + } else if function_id == functions.submit_transaction.input_id { + let (custodian, value) = parse_tx_full(functions.submit_transaction, body)?; + MultisigSubmitTransaction::try_from((custodian, value)) + .map(MultisigTransaction::Submit) + .ok() + } else if function_id == functions.confirm_transaction.input_id { + let (custodian, value) = parse_tx_input(functions.confirm_transaction, body)?; + MultisigConfirmTransaction::try_from((custodian, value)) + .map(MultisigTransaction::Confirm) + .ok() + } else if let Some(functions) = &functions.update_functions { + if function_id == functions.submit_update.input_id { + let (custodian, value) = parse_tx_full(functions.submit_update, body)?; + MultisigSubmitUpdate::try_from((custodian, value)) + .map(MultisigTransaction::SubmitUpdate) + .ok() + } else if function_id == functions.confirm_update.input_id { + let (custodian, value) = parse_tx_input(functions.confirm_update, body)?; + MultisigConfirmUpdate::try_from((custodian, value)) + .map(MultisigTransaction::ConfirmUpdate) + .ok() + } else if function_id == functions.execute_update.input_id { + let (custodian, value) = parse_tx_input(functions.execute_update, body)?; + MultisigExecuteUpdate::try_from((custodian, value)) + .map(MultisigTransaction::ExecuteUpdate) + .ok() + } else { + None + } + } else { + None + } +} + +struct MultisigFunctions { + send_transaction: &'static Function, + submit_transaction: &'static Function, + confirm_transaction: &'static Function, + update_functions: Option, +} + +struct UpdateFunctions { + submit_update: &'static Function, + confirm_update: &'static Function, + execute_update: &'static Function, +} + +impl MultisigFunctions { + fn instance(multisig_type: MultisigType) -> &'static Self { + static OLD_FUNCTIONS: std::sync::OnceLock = std::sync::OnceLock::new(); + static NEW_FUNCTIONS: std::sync::OnceLock = std::sync::OnceLock::new(); + + match multisig_type { + ty if ty.is_multisig2() => NEW_FUNCTIONS.get_or_init(|| MultisigFunctions { + send_transaction: multisig2::send_transaction(), + submit_transaction: multisig2::submit_transaction(), + confirm_transaction: multisig2::confirm_transaction(), + update_functions: Some(UpdateFunctions { + submit_update: multisig2::submit_update(), + confirm_update: multisig2::confirm_update(), + execute_update: multisig2::execute_update(), + }), + }), + _ => OLD_FUNCTIONS.get_or_init(|| MultisigFunctions { + send_transaction: multisig::send_transaction(), + submit_transaction: multisig::submit_transaction(), + confirm_transaction: multisig::confirm_transaction(), + update_functions: None, + }), + } + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, InputMessage)) -> Result { + let output: MultisigConfirmTransaction = value.0.unpack()?; + Ok(Self { + custodian, + transaction_id: output.transaction_id, + }) + } +} + +#[derive(UnpackAbiPlain)] +struct MultisigSubmitTransactionInput { + #[abi(address)] + dest: MsgAddressInt, + #[abi(with = "uint128_number")] + value: BigUint, + #[abi(bool)] + bounce: bool, + #[abi(bool, name = "allBalance")] + all_balance: bool, + #[abi(cell)] + payload: ton_types::Cell, +} + +#[derive(UnpackAbiPlain)] +struct MultisigSubmitTransactionOutput { + #[abi(uint64, name = "transId")] + trans_id: u64, +} + +impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitTransaction { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { + let input: MultisigSubmitTransactionInput = value.inputs.unpack()?; + let output: MultisigSubmitTransactionOutput = value.outputs.unpack()?; + + Ok(Self { + custodian, + dest: input.dest, + value: input.value, + bounce: input.bounce, + all_balance: input.all_balance, + payload: input.payload, + trans_id: output.trans_id, + }) + } +} + +impl TryFrom for MultisigSendTransaction { + type Error = UnpackerError; + + fn try_from(value: InputMessage) -> Result { + let input: MultisigSendTransaction = value.0.unpack()?; + + Ok(Self { + dest: input.dest, + value: input.value, + bounce: input.bounce, + flags: input.flags, + payload: input.payload, + }) + } +} + +impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitUpdate { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { + use nekoton_contracts::wallets::multisig2; + + let input: multisig2::SubmitUpdateParams = value.inputs.unpack()?; + let output: multisig2::SubmitUpdateOutput = value.outputs.unpack()?; + + Ok(Self { + custodian, + new_code_hash: input.code_hash, + new_owners: input.owners.is_some(), + new_req_confirms: input.req_confirms.is_some(), + new_lifetime: input.lifetime.is_some(), + update_id: output.update_id, + }) + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmUpdate { + type Error = UnpackerError; + + fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { + use nekoton_contracts::wallets::multisig2; + + let input: multisig2::ConfirmUpdateParams = input.0.unpack()?; + Ok(Self { + custodian, + update_id: input.update_id, + }) + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigExecuteUpdate { + type Error = UnpackerError; + + fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { + use nekoton_contracts::wallets::multisig2; + + let input: multisig2::ExecuteUpdateParams = input.0.unpack()?; + Ok(Self { + custodian, + update_id: input.update_id, + }) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::*; + + fn parse_transaction(data: &str) -> (Transaction, TransactionDescrOrdinary) { + let tx = Transaction::construct_from_base64(data).unwrap(); + let description = match tx.description.read_struct().unwrap() { + TransactionDescr::Ordinary(description) => description, + _ => panic!(), + }; + (tx, description) + } + + #[test] + fn test_parse_multisig_submit() { + let tx = Transaction::construct_from_base64("te6ccgECDAEAAkMAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAEv38uN8H+CfBrFklcU0i9Vs4RZzxi5vtTa9PqJ/LpPctz/rat2wAABIjJ0UsBX2sytAADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAOylU78GhKKYOUuj1Rh3dLpOOzgJUEyoySchhaM60lDREBQDowAnUfXAxOIAAAAAAAAAABtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnJ5QDnTzA46E1KOsPz7QLrshaiw53aaaTNY7TZfFM9uf9wCstMqmz8MmfSmYLSpRuMah9ruqiOVsRPjzhTEdu9aAgHgCAYBAd8HAHXn+7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u4AAAJfv5cb4S+1mVoSY7BZq+1mVo/lxvgwAFFif7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7gwJAeGUlZeW3g4p7fOroeyZUZdj1hWrKWusR/Na6V9uRhKJvV3dgWDQ1/YR5hQfYLaM861DgLJMku/LPDKMt43TyJUH+ToLdTA3yCwRnsc9IMg9JIXlsbI92/1mZ+RrZF1GGY1AAABdLq+AHhfazLsEx2CzYAoBY4AVKmRhQN1a9YbnwdGmdH0KtPv2SINcG4FpEDjh70ON2qAAAAAAAAAAAAAdjv+NHoAUCwAA").unwrap(); + + let custodian = + HashBytes::from_str("e4e82dd4c0df20b0467b1cf48320f4921796c6c8f76ff5999f91ad9175186635") + .unwrap(); + + assert!(matches!( + parse_transaction_additional_info( + &tx, + WalletType::Multisig(MultisigType::SafeMultisigWallet) + ) + .unwrap(), + TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + recipient: Some(_), + known_payload: None, + method: WalletInteractionMethod::Multisig(data) + }) if matches!(&*data, MultisigTransaction::Submit(submit) if submit.custodian == custodian) + )); + } + + #[test] + fn test_parse_multisig_confirm() { + let tx = Transaction::construct_from_base64("te6ccgECCgEAAjAAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAJcbrc/8GSsRcwsaEKUmFwdbT9tmaf3vKqKpeWIR9/9GyMA8r2+gAACXGutDTBYBvSYwADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAI1K3sqU+I63UTJ+xkdHcyrkM2hxcBJu//z7hF+/hEtukBQFcwAnUYtYxOIAAAAAAAAAABSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnITMJnhiVklA89yLWhQU+4BB1tJ3iPLRRZoWlPVKSkbvYENWnQphG03/JbEJJWwJbdhZCl+oH7UI7ARqCUcU6H/AgHgCAYBAd8HAK9J/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7vACa4ZyAEEjOHCY7aEkcDRTMruTfdNxrg9GyWxKU18Pes2WvMQekAAAAAABLjdbn/hMA3pMZAAUWJ/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7uDAkA8c+cpxQ8FYd2C/XWiibmIX4wPfvHIultapCNOhW5dJ5hl2YD+PHO24RUXdbY669yR8BUfGNuxVTwVkV1K0HA7QByTARuQhGj9eozhRteIImtsExhdcFckfL9FqBq5uNuaoAAAF3bK3Ps2Ab0p4ap0DtYBvF9mf0BgGA=").unwrap(); + + let custodian = + HashBytes::from_str("c93011b908468fd7a8ce146d788226b6c13185d7057247cbf45a81ab9b8db9aa") + .unwrap(); + + assert!(matches!( + parse_transaction_additional_info( + &tx, + WalletType::Multisig(MultisigType::SafeMultisigWallet) + ) + .unwrap(), + TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + recipient: None, + known_payload: None, + method: WalletInteractionMethod::Multisig(data) + }) if matches!(&*data, MultisigTransaction::Confirm(confirm) if confirm.custodian == custodian) + )) + } +} diff --git a/src/utils/token_wallets/mod.rs b/src/utils/token_wallets/mod.rs index 29e89c9..c73b9dc 100644 --- a/src/utils/token_wallets/mod.rs +++ b/src/utils/token_wallets/mod.rs @@ -9,6 +9,7 @@ use crate::utils::declare_function; pub const INTERFACE_ID: u32 = 0x2a4ac43e; pub mod models; +pub mod parsing; /// Get token wallet owner address /// diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs index 2ab027a..44bf214 100644 --- a/src/utils/token_wallets/models.rs +++ b/src/utils/token_wallets/models.rs @@ -187,88 +187,3 @@ impl TokenWalletContractState<'_> { } } } - -pub fn parse_token_transaction( - tx: &Transaction, - info: &OrdinaryTxInfo, - version: TokenWalletVersion, -) -> Option { - if info.aborted { - return None; - } - - let in_msg = tx.in_msg.as_ref()?.read_struct().ok()?; - - let mut body = in_msg.body()?; - let function_id = read_function_id(&body).ok()?; - - let header = in_msg.int_header()?; - - let functions = TokenWalletFunctions::for_version(version); - - if header.bounced { - body.move_by(32).ok()?; - let function_id = read_function_id(&body).ok()?; - body.move_by(32).ok()?; - - if function_id == functions.accept_transfer.input_id { - return Some(TokenWalletTransaction::TransferBounced( - body.get_next_u128().ok()?.into(), - )); - } - - if function_id == functions.accept_burn.input_id { - Some(TokenWalletTransaction::SwapBackBounced( - body.get_next_u128().ok()?.into(), - )) - } else { - None - } - } else if function_id == functions.accept_mint.input_id { - let inputs = functions.accept_mint.decode_input(body, true, false).ok()?; - - Accept::try_from((InputMessage(inputs), version)) - .map(|Accept { tokens }| TokenWalletTransaction::Accept(tokens)) - .ok() - } else if function_id == functions.transfer_to_wallet.input_id { - let inputs = functions - .transfer_to_wallet - .decode_input(body, true, false) - .ok()?; - - TokenOutgoingTransfer::try_from(( - InputMessage(inputs), - TransferType::ByTokenWalletAddress, - version, - )) - .map(TokenWalletTransaction::OutgoingTransfer) - .ok() - } else if function_id == functions.transfer.input_id { - let inputs = functions.transfer.decode_input(body, true, false).ok()?; - - TokenOutgoingTransfer::try_from(( - InputMessage(inputs), - TransferType::ByOwnerWalletAddress, - version, - )) - .map(TokenWalletTransaction::OutgoingTransfer) - .ok() - } else if function_id == functions.accept_transfer.input_id { - let inputs = functions - .accept_transfer - .decode_input(body, true, false) - .ok()?; - - TokenIncomingTransfer::try_from((InputMessage(inputs), version)) - .map(TokenWalletTransaction::IncomingTransfer) - .ok() - } else if function_id == functions.burn.input_id { - let inputs = functions.burn.decode_input(body, true, false).ok()?; - - TokenSwapBack::try_from((InputMessage(inputs), version)) - .map(TokenWalletTransaction::SwapBack) - .ok() - } else { - None - } -} diff --git a/src/utils/parsing.rs b/src/utils/token_wallets/parsing.rs similarity index 62% rename from src/utils/parsing.rs rename to src/utils/token_wallets/parsing.rs index 6dd8856..5c6458f 100644 --- a/src/utils/parsing.rs +++ b/src/utils/token_wallets/parsing.rs @@ -2,291 +2,30 @@ use std::convert::TryFrom; use anyhow::Result; use num_bigint::BigUint; -use tycho_types::{abi::{Function, NamedAbiValue}, cell::{Cell, CellSlice, HashBytes, Load}, models::{MsgInfo, OwnedMessage, Transaction}}; - -use crate::utils::{ton_wallet::MultisigType, wallets::multisig::MultisigTransaction}; - - -pub struct InputMessage(pub Vec); - -pub struct ContractCall { - pub inputs: Vec, - pub outputs: Vec, -} - -pub fn parse_multisig_transaction( - multisig_type: MultisigType, - tx: &Transaction, -) -> Option { - let in_msg_cell = tx.in_msg.as_ref()?; - - let Ok(mut slice) = in_msg_cell.as_slice() else { - return None; - }; - let Ok(in_msg) = OwnedMessage::load_from(&mut slice) else { - return None; - }; - - if !matches!(in_msg.info, MsgInfo::ExtIn(_)) { - return None; - } - parse_multisig_transaction_impl(multisig_type, in_msg, tx) -} - -fn parse_multisig_transaction_impl( - multisig_type: MultisigType, - in_msg: OwnedMessage, - tx: &Transaction, -) -> Option { - const PUBKEY_OFFSET: usize = 1 + ed25519_dalek::SIGNATURE_LENGTH * 8 + 1; - const PUBKEY_LENGTH: usize = 256; - const TIME_LENGTH: usize = 64; - const EXPIRE_LENGTH: usize = 32; - - let body = in_msg.body; - let Ok(mut body_slice) = body.clone().1.as_slice() else { - return None; - }; - - // Shift body by Maybe(signature), Maybe(pubkey), time and expire - body_slice.skip_first((PUBKEY_OFFSET + PUBKEY_LENGTH + TIME_LENGTH + EXPIRE_LENGTH) as u16, 0) - .ok()?; - - let Ok(function_id) = body_slice.get_u32(0) - else { - return None; - }; - - let parse_tx_input = |function: &Function, - mut slice: CellSlice<'_>| - -> Option<(HashBytes, InputMessage)> { - let inputs = function.decode_internal_input(slice).ok()?; - slice.skip_first(PUBKEY_OFFSET as u16, 0).ok()?; - let custodian = slice.load_u256().ok()?; - Some((custodian, InputMessage(inputs))) - }; - - let parse_tx_full = |function: &Function, - body: Cell| - -> Option<(HashBytes, ContractCall)> { - let Ok(mut slice) = body.as_slice() else { - return None; - }; - let (custodian, InputMessage(inputs)) = parse_tx_input(function, slice)?; - let outputs = function.parse(tx).ok()?; - Some((custodian, ContractCall { inputs, outputs })) - }; - - let functions = MultisigFunctions::instance(multisig_type); - - if function_id == functions.send_transaction.input_id { - let inputs = functions - .send_transaction - .decode_input(body, false, false) - .ok()?; - MultisigSendTransaction::try_from(InputMessage(inputs)) - .map(MultisigTransaction::Send) - .ok() - } else if function_id == functions.submit_transaction.input_id { - let (custodian, value) = parse_tx_full(functions.submit_transaction, body)?; - MultisigSubmitTransaction::try_from((custodian, value)) - .map(MultisigTransaction::Submit) - .ok() - } else if function_id == functions.confirm_transaction.input_id { - let (custodian, value) = parse_tx_input(functions.confirm_transaction, body)?; - MultisigConfirmTransaction::try_from((custodian, value)) - .map(MultisigTransaction::Confirm) - .ok() - } else if let Some(functions) = &functions.update_functions { - if function_id == functions.submit_update.input_id { - let (custodian, value) = parse_tx_full(functions.submit_update, body)?; - MultisigSubmitUpdate::try_from((custodian, value)) - .map(MultisigTransaction::SubmitUpdate) - .ok() - } else if function_id == functions.confirm_update.input_id { - let (custodian, value) = parse_tx_input(functions.confirm_update, body)?; - MultisigConfirmUpdate::try_from((custodian, value)) - .map(MultisigTransaction::ConfirmUpdate) - .ok() - } else if function_id == functions.execute_update.input_id { - let (custodian, value) = parse_tx_input(functions.execute_update, body)?; - MultisigExecuteUpdate::try_from((custodian, value)) - .map(MultisigTransaction::ExecuteUpdate) - .ok() - } else { - None - } - } else { - None - } -} - -struct MultisigFunctions { - send_transaction: &'static Function, - submit_transaction: &'static Function, - confirm_transaction: &'static Function, - update_functions: Option, -} - -struct UpdateFunctions { - submit_update: &'static Function, - confirm_update: &'static Function, - execute_update: &'static Function, -} - -impl MultisigFunctions { - fn instance(multisig_type: MultisigType) -> &'static Self { - use nekoton_contracts::wallets::{multisig, multisig2}; - - static OLD_FUNCTIONS: OnceBox = OnceBox::new(); - static NEW_FUNCTIONS: OnceBox = OnceBox::new(); - - match multisig_type { - ty if ty.is_multisig2() => NEW_FUNCTIONS.get_or_init(|| { - Box::new(MultisigFunctions { - send_transaction: multisig2::send_transaction(), - submit_transaction: multisig2::submit_transaction(), - confirm_transaction: multisig2::confirm_transaction(), - update_functions: Some(UpdateFunctions { - submit_update: multisig2::submit_update(), - confirm_update: multisig2::confirm_update(), - execute_update: multisig2::execute_update(), - }), - }) - }), - _ => OLD_FUNCTIONS.get_or_init(|| { - Box::new(MultisigFunctions { - send_transaction: multisig::send_transaction(), - submit_transaction: multisig::submit_transaction(), - confirm_transaction: multisig::confirm_transaction(), - update_functions: None, - }) - }), - } - } -} - -impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { - type Error = UnpackerError; - - fn try_from((custodian, value): (HashBytes, InputMessage)) -> Result { - let output: MultisigConfirmTransaction = value.0.unpack()?; - Ok(Self { - custodian, - transaction_id: output.transaction_id, - }) - } -} - -#[derive(UnpackAbiPlain)] -struct MultisigSubmitTransactionInput { - #[abi(address)] - dest: MsgAddressInt, - #[abi(with = "uint128_number")] - value: BigUint, - #[abi(bool)] - bounce: bool, - #[abi(bool, name = "allBalance")] - all_balance: bool, - #[abi(cell)] - payload: ton_types::Cell, -} - -#[derive(UnpackAbiPlain)] -struct MultisigSubmitTransactionOutput { - #[abi(uint64, name = "transId")] - trans_id: u64, -} - -impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitTransaction { - type Error = UnpackerError; - - fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { - let input: MultisigSubmitTransactionInput = value.inputs.unpack()?; - let output: MultisigSubmitTransactionOutput = value.outputs.unpack()?; - - Ok(Self { - custodian, - dest: input.dest, - value: input.value, - bounce: input.bounce, - all_balance: input.all_balance, - payload: input.payload, - trans_id: output.trans_id, - }) - } -} - -impl TryFrom for MultisigSendTransaction { - type Error = UnpackerError; - - fn try_from(value: InputMessage) -> Result { - let input: MultisigSendTransaction = value.0.unpack()?; - - Ok(Self { - dest: input.dest, - value: input.value, - bounce: input.bounce, - flags: input.flags, - payload: input.payload, - }) - } -} - -impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitUpdate { - type Error = UnpackerError; - - fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { - use nekoton_contracts::wallets::multisig2; - - let input: multisig2::SubmitUpdateParams = value.inputs.unpack()?; - let output: multisig2::SubmitUpdateOutput = value.outputs.unpack()?; - - Ok(Self { - custodian, - new_code_hash: input.code_hash, - new_owners: input.owners.is_some(), - new_req_confirms: input.req_confirms.is_some(), - new_lifetime: input.lifetime.is_some(), - update_id: output.update_id, - }) - } -} - -impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmUpdate { - type Error = UnpackerError; - - fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { - use nekoton_contracts::wallets::multisig2; - - let input: multisig2::ConfirmUpdateParams = input.0.unpack()?; - Ok(Self { - custodian, - update_id: input.update_id, - }) - } -} - -impl TryFrom<(HashBytes, InputMessage)> for MultisigExecuteUpdate { - type Error = UnpackerError; - - fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { - use nekoton_contracts::wallets::multisig2; - - let input: multisig2::ExecuteUpdateParams = input.0.unpack()?; - Ok(Self { - custodian, - update_id: input.update_id, - }) - } -} +use tycho_types::{ + abi::{Function, NamedAbiValue}, + cell::{Cell, CellSlice, HashBytes, Load}, + models::{MsgInfo, OrdinaryTxInfo, OwnedMessage, Transaction}, +}; + +use crate::utils::{ + token_wallets::models::{ + TokenIncomingTransfer, TokenOutgoingTransfer, TokenWalletTransaction, TokenWalletVersion, + }, + ton_wallet::{multisig::UnpackerError, MultisigType}, + wallets::{ + multisig::{self, MultisigTransaction}, + multisig2, + }, + InputMessage, +}; pub fn parse_token_transaction( tx: &Transaction, - description: &TransactionDescrOrdinary, + info: &OrdinaryTxInfo, version: TokenWalletVersion, ) -> Option { - if description.aborted { + if info.aborted { return None; } @@ -558,11 +297,12 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenIncomingTransfer { } } - #[cfg(test)] mod tests { use std::str::FromStr; + use crate::utils::token_wallets::models::TokenWalletVersion; + use super::*; fn parse_transaction(data: &str) -> (Transaction, TransactionDescrOrdinary) { @@ -589,50 +329,6 @@ mod tests { println!("parsed tx: {parsed:#?}"); } - #[test] - fn test_parse_multisig_submit() { - let tx = Transaction::construct_from_base64("te6ccgECDAEAAkMAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAEv38uN8H+CfBrFklcU0i9Vs4RZzxi5vtTa9PqJ/LpPctz/rat2wAABIjJ0UsBX2sytAADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAOylU78GhKKYOUuj1Rh3dLpOOzgJUEyoySchhaM60lDREBQDowAnUfXAxOIAAAAAAAAAABtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnJ5QDnTzA46E1KOsPz7QLrshaiw53aaaTNY7TZfFM9uf9wCstMqmz8MmfSmYLSpRuMah9ruqiOVsRPjzhTEdu9aAgHgCAYBAd8HAHXn+7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u4AAAJfv5cb4S+1mVoSY7BZq+1mVo/lxvgwAFFif7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7gwJAeGUlZeW3g4p7fOroeyZUZdj1hWrKWusR/Na6V9uRhKJvV3dgWDQ1/YR5hQfYLaM861DgLJMku/LPDKMt43TyJUH+ToLdTA3yCwRnsc9IMg9JIXlsbI92/1mZ+RrZF1GGY1AAABdLq+AHhfazLsEx2CzYAoBY4AVKmRhQN1a9YbnwdGmdH0KtPv2SINcG4FpEDjh70ON2qAAAAAAAAAAAAAdjv+NHoAUCwAA").unwrap(); - - let custodian = - HashBytes::from_str("e4e82dd4c0df20b0467b1cf48320f4921796c6c8f76ff5999f91ad9175186635") - .unwrap(); - - assert!(matches!( - parse_transaction_additional_info( - &tx, - WalletType::Multisig(MultisigType::SafeMultisigWallet) - ) - .unwrap(), - TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { - recipient: Some(_), - known_payload: None, - method: WalletInteractionMethod::Multisig(data) - }) if matches!(&*data, MultisigTransaction::Submit(submit) if submit.custodian == custodian) - )); - } - - #[test] - fn test_parse_multisig_confirm() { - let tx = Transaction::construct_from_base64("te6ccgECCgEAAjAAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAJcbrc/8GSsRcwsaEKUmFwdbT9tmaf3vKqKpeWIR9/9GyMA8r2+gAACXGutDTBYBvSYwADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAI1K3sqU+I63UTJ+xkdHcyrkM2hxcBJu//z7hF+/hEtukBQFcwAnUYtYxOIAAAAAAAAAABSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnITMJnhiVklA89yLWhQU+4BB1tJ3iPLRRZoWlPVKSkbvYENWnQphG03/JbEJJWwJbdhZCl+oH7UI7ARqCUcU6H/AgHgCAYBAd8HAK9J/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7vACa4ZyAEEjOHCY7aEkcDRTMruTfdNxrg9GyWxKU18Pes2WvMQekAAAAAABLjdbn/hMA3pMZAAUWJ/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7uDAkA8c+cpxQ8FYd2C/XWiibmIX4wPfvHIultapCNOhW5dJ5hl2YD+PHO24RUXdbY669yR8BUfGNuxVTwVkV1K0HA7QByTARuQhGj9eozhRteIImtsExhdcFckfL9FqBq5uNuaoAAAF3bK3Ps2Ab0p4ap0DtYBvF9mf0BgGA=").unwrap(); - - let custodian = - HashBytes::from_str("c93011b908468fd7a8ce146d788226b6c13185d7057247cbf45a81ab9b8db9aa") - .unwrap(); - - assert!(matches!( - parse_transaction_additional_info( - &tx, - WalletType::Multisig(MultisigType::SafeMultisigWallet) - ) - .unwrap(), - TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { - recipient: None, - known_payload: None, - method: WalletInteractionMethod::Multisig(data) - }) if matches!(&*data, MultisigTransaction::Confirm(confirm) if confirm.custodian == custodian) - )) - } - #[test] fn test_parse_bounced_tokens_transfer() { let (tx, description) = parse_transaction("te6ccgECCQEAAiEAA7V9jKvgMYxeLukedeW/PRr7QyRzEpkal33nb9KfgpelA3AAAO1mmxCMEy4UbEGiIQKVpE2nzO2Ar32k7H36ni1NMpxrcPorUNuwAADtZo+e3BYO9BHwADRwGMkIBQQBAhcMSgkCmI36GG92AhEDAgBvyYehIEwUWEAAAAAAAAQAAgAAAAKLF5Ge7DorMQ9dbEzZTgWK7Jiugap8s4dRpkiQl7CNEEBQFgwAnkP1TAqiBAAAAAAAAAAAtgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgnIBZa/nTbAD2Vcr8A6p+uT7XD4tLowmBLZEuIHLxU1zbeHGgHFi5dfeWnrNgtL3FHE6zw6ysjTJJI3LFFDAgPi3AgHgCAYBAd8HALFoAbGVfAYxi8XdI868t+ejX2hkjmJTI1LvvO36U/BS9KBvABgzjiRJUfoXsV99CuD/WnKK4QN5mlferMiVbk0Y3Jc3ECddFmAGFFhgAAAdrNNiEYTB3oI+QAD5WAHF6/YBDYNj7TABzedO3/4+ENpaE0PhwRx5NFYisFNfpQA2Mq+AxjF4u6R515b89GvtDJHMSmRqXfedv0p+Cl6UDdApiN+gBhRYYAAAHazSjHIEwd6CFH////+MaQuBAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEA="); From 8bd06b3a2c32b4810e45f546c2d6dc64666f95f1 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Sun, 8 Feb 2026 11:11:59 +0300 Subject: [PATCH 24/59] fix parsing --- src/utils/mod.rs | 1 + src/utils/multisig/parsing.rs | 11 +- src/utils/token_wallets/parsing.rs | 176 +++++++---------------------- 3 files changed, 46 insertions(+), 142 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6d68115..a8c2604 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,7 @@ use std::hash::BuildHasherDefault; use rustc_hash::FxHasher; +use tycho_types::abi::NamedAbiValue; pub use self::encoding::*; pub use self::existing_contract::*; diff --git a/src/utils/multisig/parsing.rs b/src/utils/multisig/parsing.rs index a92066d..ef68637 100644 --- a/src/utils/multisig/parsing.rs +++ b/src/utils/multisig/parsing.rs @@ -1,11 +1,10 @@ use std::convert::TryFrom; use anyhow::Result; -use num_bigint::BigUint; use tycho_types::{ abi::Function, cell::{Cell, CellSlice, HashBytes, Load}, - models::{MsgInfo, OwnedMessage, Transaction}, + models::{MsgInfo, OwnedMessage, StdAddr, Transaction}, }; use crate::utils::{ @@ -183,15 +182,15 @@ impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { #[derive(UnpackAbiPlain)] struct MultisigSubmitTransactionInput { #[abi(address)] - dest: MsgAddressInt, + dest: StdAddr, #[abi(with = "uint128_number")] - value: BigUint, + value: u128, #[abi(bool)] bounce: bool, #[abi(bool, name = "allBalance")] all_balance: bool, #[abi(cell)] - payload: ton_types::Cell, + payload: Cell, } #[derive(UnpackAbiPlain)] @@ -239,8 +238,6 @@ impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitUpdate { type Error = UnpackerError; fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { - use nekoton_contracts::wallets::multisig2; - let input: multisig2::SubmitUpdateParams = value.inputs.unpack()?; let output: multisig2::SubmitUpdateOutput = value.outputs.unpack()?; diff --git a/src/utils/token_wallets/parsing.rs b/src/utils/token_wallets/parsing.rs index 5c6458f..be40fb9 100644 --- a/src/utils/token_wallets/parsing.rs +++ b/src/utils/token_wallets/parsing.rs @@ -1,22 +1,21 @@ -use std::convert::TryFrom; +use std::{convert::TryFrom, sync::OnceLock}; use anyhow::Result; use num_bigint::BigUint; use tycho_types::{ - abi::{Function, NamedAbiValue}, - cell::{Cell, CellSlice, HashBytes, Load}, - models::{MsgInfo, OrdinaryTxInfo, OwnedMessage, Transaction}, + abi::Function, + models::{OrdinaryTxInfo, Transaction}, }; use crate::utils::{ - token_wallets::models::{ - TokenIncomingTransfer, TokenOutgoingTransfer, TokenWalletTransaction, TokenWalletVersion, - }, - ton_wallet::{multisig::UnpackerError, MultisigType}, - wallets::{ - multisig::{self, MultisigTransaction}, - multisig2, + token_wallets::{ + self, + models::{ + TokenIncomingTransfer, TokenOutgoingTransfer, TokenSwapBack, TokenWalletTransaction, + TokenWalletVersion, TransferRecipient, + }, }, + ton_wallet::multisig::UnpackerError, InputMessage, }; @@ -124,29 +123,17 @@ impl TokenWalletFunctions { pub fn for_version(version: TokenWalletVersion) -> &'static TokenWalletFunctions { match version { TokenWalletVersion::OldTip3v4 => { - static IDS: OnceBox = OnceBox::new(); - IDS.get_or_init(|| { - Box::new(Self { - accept_mint: old_tip3::token_wallet_contract::accept(), - transfer: old_tip3::token_wallet_contract::transfer_to_recipient(), - transfer_to_wallet: old_tip3::token_wallet_contract::transfer(), - accept_transfer: old_tip3::token_wallet_contract::internal_transfer(), - burn: old_tip3::token_wallet_contract::burn_by_owner(), - accept_burn: old_tip3::root_token_contract::tokens_burned(), - }) - }) + unimplemented!() } TokenWalletVersion::Tip3 => { - static IDS: OnceBox = OnceBox::new(); - IDS.get_or_init(|| { - Box::new(Self { - accept_mint: tip3_1::token_wallet_contract::accept_mint(), - transfer: tip3_1::token_wallet_contract::transfer(), - transfer_to_wallet: tip3_1::token_wallet_contract::transfer_to_wallet(), - accept_transfer: tip3_1::token_wallet_contract::accept_transfer(), - burn: tip3_1::token_wallet_contract::burnable::burn(), - accept_burn: tip3_1::root_token_contract::accept_burn(), - }) + static IDS: OnceLock = OnceLock::new(); + IDS.get_or_init(|| Self { + accept_mint: token_wallets::accept_mint(), + transfer: token_wallets::transfer(), + transfer_to_wallet: token_wallets::transfer_to_wallet(), + accept_transfer: token_wallets::accept_transfer(), + burn: token_wallets::burnable::burn(), + accept_burn: root_token_contract::accept_burn(), }) } } @@ -159,17 +146,10 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenSwapBack { fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { Ok(match version { TokenWalletVersion::OldTip3v4 => { - let input: old_tip3::token_wallet_contract::BurnByOwnerInputs = value.0.unpack()?; - - Self { - tokens: input.tokens, - callback_address: input.callback_address, - callback_payload: input.callback_payload, - } + return Err(UnpackerError::InvalidAbi); } TokenWalletVersion::Tip3 => { - let input: tip3_1::token_wallet_contract::burnable::BurnInputs = - value.0.unpack()?; + let input: token_wallets::burnable::BurnInputs = value.0.unpack()?; Self { tokens: input.amount, @@ -191,13 +171,10 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for Accept { fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { Ok(match version { TokenWalletVersion::OldTip3v4 => { - let input: old_tip3::token_wallet_contract::AcceptInputs = value.0.unpack()?; - Self { - tokens: input.tokens, - } + return Err(UnpackerError::InvalidAbi); } TokenWalletVersion::Tip3 => { - let input: tip3_1::token_wallet_contract::AcceptMintInputs = value.0.unpack()?; + let input: token_wallets::AcceptMintInputs = value.0.unpack()?; Self { tokens: input.amount, } @@ -219,35 +196,13 @@ impl TryFrom<(InputMessage, TransferType, TokenWalletVersion)> for TokenOutgoing ) -> Result { Ok(match version { TokenWalletVersion::OldTip3v4 => { - match transfer_type { - // "transferToRecipient" - TransferType::ByOwnerWalletAddress => { - let input: old_tip3::token_wallet_contract::TransferToRecipientInputs = - value.0.unpack()?; - Self { - to: TransferRecipient::OwnerWallet(input.recipient_address), - tokens: input.tokens, - payload: input.payload, - } - } - // "transfer - TransferType::ByTokenWalletAddress => { - let input: old_tip3::token_wallet_contract::TransferInputs = - value.0.unpack()?; - Self { - to: TransferRecipient::TokenWallet(input.to), - tokens: input.tokens, - payload: input.payload, - } - } - } + return Err(UnpackerError::InvalidAbi); } TokenWalletVersion::Tip3 => { match transfer_type { // "transfer" TransferType::ByOwnerWalletAddress => { - let input: tip3_1::token_wallet_contract::TransferInputs = - value.0.unpack()?; + let input: token_wallets::TransferInputs = value.0.unpack()?; Self { to: TransferRecipient::OwnerWallet(input.recipient), tokens: input.amount, @@ -256,8 +211,7 @@ impl TryFrom<(InputMessage, TransferType, TokenWalletVersion)> for TokenOutgoing } // "transferToWallet" TransferType::ByTokenWalletAddress => { - let input: tip3_1::token_wallet_contract::TransferToWalletInputs = - value.0.unpack()?; + let input: token_wallets::TransferToWalletInputs = value.0.unpack()?; Self { to: TransferRecipient::TokenWallet(input.recipient_token_wallet), tokens: input.amount, @@ -276,17 +230,10 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenIncomingTransfer { fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { Ok(match version { TokenWalletVersion::OldTip3v4 => { - let input: old_tip3::token_wallet_contract::InternalTransferInputs = - value.0.unpack()?; - - Self { - tokens: input.tokens, - sender_address: input.sender_address, - } + return Err(UnpackerError::InvalidAbi); } TokenWalletVersion::Tip3 => { - let input: tip3_1::token_wallet_contract::AcceptTransferInputs = - value.0.unpack()?; + let input: token_wallets::AcceptTransferInputs = value.0.unpack()?; Self { tokens: input.amount, @@ -299,33 +246,33 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenIncomingTransfer { #[cfg(test)] mod tests { - use std::str::FromStr; + + use tycho_types::{boc::Boc, cell::Load, models::TxInfo}; use crate::utils::token_wallets::models::TokenWalletVersion; use super::*; - fn parse_transaction(data: &str) -> (Transaction, TransactionDescrOrdinary) { - let tx = Transaction::construct_from_base64(data).unwrap(); - let description = match tx.description.read_struct().unwrap() { - TransactionDescr::Ordinary(description) => description, + fn parse_transaction(data: &str) -> (Transaction, OrdinaryTxInfo) { + let binding = Boc::decode_base64(data).unwrap(); + let mut cell = binding.as_slice().unwrap(); + let transaction = Transaction::load_from(&mut cell).unwrap(); + let info = match transaction.load_info().unwrap() { + TxInfo::Ordinary(info) => info, _ => panic!(), }; - (tx, description) + (transaction, info) } #[test] fn test_parse_wallet_v3_token_transfer_with_payload() { - let tx = Transaction::construct_from_base64("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").unwrap(); - println!("tx: {tx:#?}"); + let (transaction, description) = parse_transaction("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"); + + println!("tx: {transaction:#?}"); - let description = match tx.description.read_struct() { - Ok(TransactionDescr::Ordinary(description)) => description, - _ => panic!(), - }; println!("description: {description:#?}"); - let parsed = parse_token_transaction(&tx, &description, TokenWalletVersion::Tip3); + let parsed = parse_token_transaction(&transaction, &description, TokenWalletVersion::Tip3); println!("parsed tx: {parsed:#?}"); } @@ -338,45 +285,4 @@ mod tests { TokenWalletTransaction::TransferBounced(_) )); } - - #[test] - fn test_parse_wallet_v5r1_transfer() { - let (tx, description) = parse_transaction("te6ccgECDgEAAroAA7V6PzxB5ur5JLcojkw57D91dcch0SdJBkRg11onChvcQxAAAuqQo7KQGfOICr+MryG/HTeCGLoHvR2QzQp8l/VW7Jy5KteDKoNgAALqkJdMvBZ0b1RwADRmUxQIBQQBAg8MQoYY8SmEQAMCAG/JhfBQTA/WGAAAAAAAAgAAAAAAAxZIaTNMW1cxmByM5WsWV9cxExzB5+1s+b7Uz5613xWmQNAtXACdQmljE4gAAAAAAAAAACPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIACCcp/sV0iKg0YadasmKflOuBQl9+BT1AMGK8jDUAHzabPWEAUiOhXPDSZMLX8X/WQ0jZRy1Ef+OW9TZFgZ7OoiSM4CAeAIBgEB3wcBsWgBR+eIPN1fJJblEcmHPYfurrjkOiTpIMiMGutE4UN7iGMABaelQoOWDtcjd5wKID6i0sUbGwEsUr2tFotsGs7AiEdQUQ/0AAYP1jQAAF1SFHZSBM6N6o7ACwHliAFH54g83V8kluURyYc9h+6uuOQ6JOkgyIwa60ThQ3uIYgObSztz///4izo3vDAAACqUsU/LVK+ma1KSpaW5p+h9917oIw6a7Txpn/VJg/WB7C5dJQYSdVOvNFZvNMz1vvv5wMwo33jnWdrh1jaHQJXGBQkCCg7DyG0DDQoBaGIAC09KhQcsHa5G7zgUQH1FpYo2NgJYpXtaLRbYNZ2BEI6goh/oAAAAAAAAAAAAAAAAAAELAbIPin6lAAAAAAAAAABUUC0RQAgAcgwTrCsIXFRhmQTVWMIpgapb1R1i6mXzRjfAhiAa+x8AKPzxB5ur5JLcojkw57D91dcch0SdJBkRg11onChvcQxIHJw4AQwACW7J3GUgAAA="); - assert!(!description.aborted); - - let wallet_transaction = parse_transaction_additional_info(&tx, WalletType::WalletV5R1); - assert!(wallet_transaction.is_some()); - - if let Some(TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { - recipient, - known_payload, - .. - })) = wallet_transaction - { - assert_eq!( - recipient.unwrap(), - MsgAddressInt::from_str( - "0:169e950a0e583b5c8dde702880fa8b4b146c6c04b14af6b45a2db06b3b02211d" - ) - .unwrap() - ); - - assert!(known_payload.is_some()); - - let payload = known_payload.unwrap(); - if let KnownPayload::JettonOutgoingTransfer(JettonOutgoingTransfer { to, tokens }) = - payload - { - assert_eq!( - to, - MsgAddressInt::from_str( - "0:390609d615842e2a30cc826aac6114c0d52dea8eb17532f9a31be043100d7d8f" - ) - .unwrap() - ); - - assert_eq!(tokens.to_u128().unwrap(), 296400000000); - } - } - } } From 770e2cb79a8db4ec8b8840439a1cd8e79c47a2cb Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 9 Feb 2026 10:53:51 +0300 Subject: [PATCH 25/59] fix parsing token --- src/utils/token_wallets/mod.rs | 396 ++++++++++++++++++++++++++++- src/utils/token_wallets/models.rs | 16 +- src/utils/token_wallets/parsing.rs | 74 ++++-- 3 files changed, 451 insertions(+), 35 deletions(-) diff --git a/src/utils/token_wallets/mod.rs b/src/utils/token_wallets/mod.rs index c73b9dc..f02814e 100644 --- a/src/utils/token_wallets/mod.rs +++ b/src/utils/token_wallets/mod.rs @@ -1,7 +1,9 @@ +use anyhow::{anyhow, Result}; +use num_traits::ToPrimitive; use tycho_types::{ - abi::{AbiType, Function, NamedAbiType}, + abi::{AbiType, AbiValue, Function, NamedAbiType, NamedAbiValue}, cell::Cell, - models::StdAddr, + models::{AnyAddr, StdAddr}, }; use crate::utils::declare_function; @@ -51,6 +53,88 @@ impl TransferInputs { AbiType::Cell.named("payload"), ] } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 6 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let recipient_abi_value = &values[1]; + let deploy_wallet_value_abi_value = &values[2]; + let remaining_gas_to_abi_value = &values[3]; + let notify_abi_value = &values[4]; + let payload_abi_value = &values[5]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*recipient_abi_value.name != "recipient" + && recipient_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid recipient")); + } + let AbiValue::Address(recipient) = &recipient_abi_value.value else { + return Err(anyhow!("Invalid recipient")); + }; + + let AnyAddr::Std(recipient) = *recipient.clone() else { + return Err(anyhow!("Invalid recipient")); + }; + + if &*deploy_wallet_value_abi_value.name != "deployWalletValue" + && deploy_wallet_value_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid deployWalletValue")); + } + let AbiValue::Uint(_, deploy_wallet_value) = &deploy_wallet_value_abi_value.value else { + return Err(anyhow!("Invalid deployWalletValue")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*notify_abi_value.name != "notify" && notify_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid notify")); + } + let AbiValue::Bool(notify) = ¬ify_abi_value.value else { + return Err(anyhow!("Invalid notify")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + recipient: recipient, + deploy_wallet_value: deploy_wallet_value.to_u128().unwrap(), + remaining_gas_to: remaining_gas_to, + notify: *notify, + payload: payload.clone(), + }) + } } /// Transfer tokens and optionally deploy token wallet for the recipient @@ -93,6 +177,78 @@ impl TransferToWalletInputs { AbiType::Cell.named("payload"), ] } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 5 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let recipient_token_wallet_abi_value = &values[1]; + let remaining_gas_to_abi_value = &values[2]; + let notify_abi_value = &values[3]; + let payload_abi_value = &values[4]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*recipient_token_wallet_abi_value.name != "recipientTokenWallet" + && recipient_token_wallet_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid recipientTokenWallet")); + } + let AbiValue::Address(recipient_token_wallet) = &recipient_token_wallet_abi_value.value + else { + return Err(anyhow!("Invalid recipientTokenWallet")); + }; + + let AnyAddr::Std(recipient_token_wallet) = *recipient_token_wallet.clone() else { + return Err(anyhow!("Invalid recipientTokenWallet")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*notify_abi_value.name != "notify" && notify_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid notify")); + } + let AbiValue::Bool(notify) = ¬ify_abi_value.value else { + return Err(anyhow!("Invalid notify")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + recipient_token_wallet: recipient_token_wallet, + remaining_gas_to: remaining_gas_to, + notify: *notify, + payload: payload.clone(), + }) + } } /// Transfer tokens using token wallet address @@ -134,6 +290,77 @@ impl AcceptTransferInputs { AbiType::Cell.named("payload"), ] } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 5 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let sender_abi_value = &values[1]; + let remaining_gas_to_abi_value = &values[2]; + let notify_abi_value = &values[3]; + let payload_abi_value = &values[4]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*sender_abi_value.name != "sender" + && sender_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid sender")); + } + let AbiValue::Address(sender) = &sender_abi_value.value else { + return Err(anyhow!("Invalid sender")); + }; + + let AnyAddr::Std(sender) = *sender.clone() else { + return Err(anyhow!("Invalid sender")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*notify_abi_value.name != "notify" && notify_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid notify")); + } + let AbiValue::Bool(notify) = ¬ify_abi_value.value else { + return Err(anyhow!("Invalid notify")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + sender: sender, + remaining_gas_to: remaining_gas_to, + notify: *notify, + payload: payload.clone(), + }) + } } /// Callback for transfer operation @@ -176,6 +403,62 @@ impl AcceptMintInputs { AbiType::Cell.named("payload"), ] } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 4 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let remaining_gas_to_abi_value = &values[1]; + let notify_abi_value = &values[2]; + let payload_abi_value = &values[3]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*notify_abi_value.name != "notify" && notify_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid notify")); + } + let AbiValue::Bool(notify) = ¬ify_abi_value.value else { + return Err(anyhow!("Invalid notify")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + remaining_gas_to: remaining_gas_to, + notify: *notify, + payload: payload.clone(), + }) + } } /// Accept minted tokens from root @@ -199,6 +482,12 @@ pub fn accept_mint() -> &'static Function { } pub mod burnable { + use num_traits::ToPrimitive; + use tycho_types::{ + abi::{AbiValue, NamedAbiValue}, + models::AnyAddr, + }; + use super::*; #[derive(Debug, Clone)] @@ -218,6 +507,67 @@ pub mod burnable { AbiType::Cell.named("payload"), ] } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 4 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let remaining_gas_to_abi_value = &values[1]; + let callback_to_abi_value = &values[2]; + let payload_abi_value = &values[3]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*callback_to_abi_value.name != "callbackTo" + && callback_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid callbackTo")); + } + let AbiValue::Address(callback_to) = &callback_to_abi_value.value else { + return Err(anyhow!("Invalid callbackTo")); + }; + + let AnyAddr::Std(callback_to) = *callback_to.clone() else { + return Err(anyhow!("Invalid callback_to")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + remaining_gas_to: remaining_gas_to, + callback_to: callback_to, + payload: payload.clone(), + }) + } } /// TODO: fill docs @@ -257,3 +607,45 @@ pub fn mint() -> &'static Function { outputs: Vec::new(), } } + +#[derive(Debug, Clone)] +pub struct AcceptBurnInputs { + pub amount: u128, + pub wallet_owner: StdAddr, + pub remaining_gas_to: StdAddr, + pub callback_to: StdAddr, + pub payload: ton_types::Cell, +} + +impl AcceptBurnInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("walletOwner"), + AbiType::Address.named("remainingGasTo"), + AbiType::Address.named("callbackTo"), + AbiType::Cell.named("payload"), + ] + } +} + +/// Called by token wallet on burn +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - amount of tokens +/// * `walletOwner: address` - token wallet owner +/// * `remainingGasTo: address` - address where to send excess gas +/// * `callbackTo: address` - address where to send callback +/// * `payload: cell` - arbitrary payload +/// +pub fn accept_burn() -> &'static Function { + declare_function! { + function_id: 0x192B51B1, + name: "acceptBurn", + inputs: AcceptBurnInputs::abi_type(), + outputs: Vec::new(), + } +} diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs index 44bf214..562c332 100644 --- a/src/utils/token_wallets/models.rs +++ b/src/utils/token_wallets/models.rs @@ -4,7 +4,7 @@ use num_bigint::BigUint; use serde::{Deserialize, Serialize}; use tycho_types::{ cell::{Cell, HashBytes}, - models::{OrdinaryTxInfo, StdAddr, Transaction}, + models::StdAddr, }; use crate::utils::{serde_address, serde_cell, serde_string}; @@ -16,18 +16,18 @@ pub enum TokenWalletTransaction { OutgoingTransfer(TokenOutgoingTransfer), SwapBack(TokenSwapBack), #[serde(with = "serde_string")] - Accept(BigUint), + Accept(u128), #[serde(with = "serde_string")] - TransferBounced(BigUint), + TransferBounced(u128), #[serde(with = "serde_string")] - SwapBackBounced(BigUint), + SwapBackBounced(u128), } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TokenIncomingTransfer { #[serde(with = "serde_string")] - pub tokens: BigUint, + pub tokens: u128, /// Not the address of the token wallet, but the address of its owner #[serde(with = "serde_address")] pub sender_address: StdAddr, @@ -37,7 +37,7 @@ pub struct TokenIncomingTransfer { pub struct TokenOutgoingTransfer { pub to: TransferRecipient, #[serde(with = "serde_string")] - pub tokens: BigUint, + pub tokens: u128, /// token transfer payload #[serde(with = "serde_cell")] pub payload: Cell, @@ -55,7 +55,7 @@ pub enum TransferRecipient { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct TokenSwapBack { #[serde(with = "serde_string")] - pub tokens: BigUint, + pub tokens: u128, #[serde(with = "serde_address")] pub callback_address: StdAddr, /// ETH address or something else @@ -96,7 +96,7 @@ pub struct RootTokenContractDetails { #[serde(with = "serde_address")] pub owner_address: StdAddr, #[serde(with = "serde_string")] - pub total_supply: BigUint, + pub total_supply: u128, } #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] diff --git a/src/utils/token_wallets/parsing.rs b/src/utils/token_wallets/parsing.rs index be40fb9..afc6221 100644 --- a/src/utils/token_wallets/parsing.rs +++ b/src/utils/token_wallets/parsing.rs @@ -1,10 +1,10 @@ use std::{convert::TryFrom, sync::OnceLock}; use anyhow::Result; -use num_bigint::BigUint; use tycho_types::{ abi::Function, - models::{OrdinaryTxInfo, Transaction}, + cell::Load, + models::{MsgInfo, OrdinaryTxInfo, OwnedMessage, Transaction}, }; use crate::utils::{ @@ -28,35 +28,55 @@ pub fn parse_token_transaction( return None; } - let in_msg = tx.in_msg.as_ref()?.read_struct().ok()?; + let in_msg_cell = tx.in_msg.as_ref()?; - let mut body = in_msg.body()?; - let function_id = read_function_id(&body).ok()?; + let Ok(mut slice) = in_msg_cell.as_slice() else { + return None; + }; + let Ok(in_msg) = OwnedMessage::load_from(&mut slice) else { + return None; + }; + + let body = in_msg.body; + let Ok(mut body_slice) = body.1.as_slice() else { + return None; + }; - let header = in_msg.int_header()?; + let Ok(function_id) = body_slice.get_u32(0) else { + return None; + }; + + let MsgInfo::Int(info) = in_msg.info else { + return None; + }; let functions = TokenWalletFunctions::for_version(version); - if header.bounced { - body.move_by(32).ok()?; - let function_id = read_function_id(&body).ok()?; - body.move_by(32).ok()?; + if info.bounced { + body_slice.skip_first(32, 0).ok()?; + let Ok(function_id) = body_slice.get_u32(0) else { + return None; + }; + body_slice.skip_first(32, 0).ok()?; if function_id == functions.accept_transfer.input_id { return Some(TokenWalletTransaction::TransferBounced( - body.get_next_u128().ok()?.into(), + body_slice.load_u128().ok()?.into(), )); } if function_id == functions.accept_burn.input_id { Some(TokenWalletTransaction::SwapBackBounced( - body.get_next_u128().ok()?.into(), + body_slice.load_u128().ok()?.into(), )) } else { None } } else if function_id == functions.accept_mint.input_id { - let inputs = functions.accept_mint.decode_input(body, true, false).ok()?; + let inputs = functions + .accept_mint + .decode_internal_input(body_slice) + .ok()?; Accept::try_from((InputMessage(inputs), version)) .map(|Accept { tokens }| TokenWalletTransaction::Accept(tokens)) @@ -64,7 +84,7 @@ pub fn parse_token_transaction( } else if function_id == functions.transfer_to_wallet.input_id { let inputs = functions .transfer_to_wallet - .decode_input(body, true, false) + .decode_internal_input(body_slice) .ok()?; TokenOutgoingTransfer::try_from(( @@ -75,7 +95,7 @@ pub fn parse_token_transaction( .map(TokenWalletTransaction::OutgoingTransfer) .ok() } else if function_id == functions.transfer.input_id { - let inputs = functions.transfer.decode_input(body, true, false).ok()?; + let inputs = functions.transfer.decode_internal_input(body_slice).ok()?; TokenOutgoingTransfer::try_from(( InputMessage(inputs), @@ -87,14 +107,14 @@ pub fn parse_token_transaction( } else if function_id == functions.accept_transfer.input_id { let inputs = functions .accept_transfer - .decode_input(body, true, false) + .decode_internal_input(body_slice) .ok()?; TokenIncomingTransfer::try_from((InputMessage(inputs), version)) .map(TokenWalletTransaction::IncomingTransfer) .ok() } else if function_id == functions.burn.input_id { - let inputs = functions.burn.decode_input(body, true, false).ok()?; + let inputs = functions.burn.decode_internal_input(body_slice).ok()?; TokenSwapBack::try_from((InputMessage(inputs), version)) .map(TokenWalletTransaction::SwapBack) @@ -133,7 +153,7 @@ impl TokenWalletFunctions { transfer_to_wallet: token_wallets::transfer_to_wallet(), accept_transfer: token_wallets::accept_transfer(), burn: token_wallets::burnable::burn(), - accept_burn: root_token_contract::accept_burn(), + accept_burn: token_wallets::accept_burn(), }) } } @@ -149,7 +169,8 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenSwapBack { return Err(UnpackerError::InvalidAbi); } TokenWalletVersion::Tip3 => { - let input: token_wallets::burnable::BurnInputs = value.0.unpack()?; + let input = token_wallets::burnable::BurnInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; Self { tokens: input.amount, @@ -162,7 +183,7 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenSwapBack { } struct Accept { - tokens: BigUint, + tokens: u128, } impl TryFrom<(InputMessage, TokenWalletVersion)> for Accept { @@ -174,7 +195,8 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for Accept { return Err(UnpackerError::InvalidAbi); } TokenWalletVersion::Tip3 => { - let input: token_wallets::AcceptMintInputs = value.0.unpack()?; + let input = token_wallets::AcceptMintInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; Self { tokens: input.amount, } @@ -202,7 +224,8 @@ impl TryFrom<(InputMessage, TransferType, TokenWalletVersion)> for TokenOutgoing match transfer_type { // "transfer" TransferType::ByOwnerWalletAddress => { - let input: token_wallets::TransferInputs = value.0.unpack()?; + let input = token_wallets::TransferInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; Self { to: TransferRecipient::OwnerWallet(input.recipient), tokens: input.amount, @@ -211,7 +234,8 @@ impl TryFrom<(InputMessage, TransferType, TokenWalletVersion)> for TokenOutgoing } // "transferToWallet" TransferType::ByTokenWalletAddress => { - let input: token_wallets::TransferToWalletInputs = value.0.unpack()?; + let input = token_wallets::TransferToWalletInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; Self { to: TransferRecipient::TokenWallet(input.recipient_token_wallet), tokens: input.amount, @@ -233,8 +257,8 @@ impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenIncomingTransfer { return Err(UnpackerError::InvalidAbi); } TokenWalletVersion::Tip3 => { - let input: token_wallets::AcceptTransferInputs = value.0.unpack()?; - + let input = token_wallets::AcceptTransferInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; Self { tokens: input.amount, sender_address: input.sender, From 77697d872b7619a41ca01e9ebd0461462ab654aa Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 9 Feb 2026 12:23:17 +0300 Subject: [PATCH 26/59] fix multisig models --- src/utils/mod.rs | 28 +++ src/utils/multisig/models.rs | 325 +++++++++++++++++++++++++++--- src/utils/multisig/parsing.rs | 282 ++++++++++---------------- src/utils/token_wallets/models.rs | 27 +-- src/utils/wallets/multisig2.rs | 187 ++++++++++++++++- 5 files changed, 627 insertions(+), 222 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a8c2604..272b778 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -122,6 +122,34 @@ pub mod serde_address { } } +pub mod serde_optional_string { + use std::fmt; + use std::str::FromStr; + + use serde::de::Error; + use serde::{Deserialize, Serialize}; + + pub fn serialize(data: &Option, serializer: S) -> Result + where + S: serde::Serializer, + T: fmt::Display, + { + data.as_ref().map(ToString::to_string).serialize(serializer) + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + T: FromStr, + T::Err: fmt::Display, + { + Option::::deserialize(deserializer).and_then(|data| { + data.map(|data| T::from_str(&data).map_err(Error::custom)) + .transpose() + }) + } +} + pub mod serde_cell { use serde::de::Error; use serde::Deserialize; diff --git a/src/utils/multisig/models.rs b/src/utils/multisig/models.rs index e7e1295..b01f034 100644 --- a/src/utils/multisig/models.rs +++ b/src/utils/multisig/models.rs @@ -1,10 +1,16 @@ +use anyhow::{anyhow, Result}; +use num_traits::ToPrimitive; use serde::{Deserialize, Serialize}; use tycho_types::{ + abi::{AbiType, AbiValue, NamedAbiValue}, cell::{Cell, HashBytes}, - models::StdAddr, + models::{AnyAddr, StdAddr}, }; -use crate::utils::{serde_address, serde_cell, serde_string}; +use crate::utils::{ + serde_address, serde_cell, serde_string, ton_wallet::multisig::UnpackerError, + wallets::multisig2, ContractCall, InputMessage, +}; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "type", content = "data")] @@ -17,22 +23,42 @@ pub enum MultisigTransaction { ExecuteUpdate(MultisigExecuteUpdate), } -#[derive(UnpackAbiPlain, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy)] #[serde(rename_all = "camelCase")] pub struct MultisigConfirmTransaction { - #[abi(skip)] - #[serde(with = "serde_HashBytes")] pub custodian: HashBytes, - #[abi(uint64, name = "transactionId")] #[serde(with = "serde_string")] pub transaction_id: u64, } +impl MultisigConfirmTransaction { + pub fn unpack(values: Vec) -> Result { + if values.len() != 1 { + return Err(anyhow!("Invalid number of arguments")); + } + + let transaction_id_abi_value = &values[0]; + if &*transaction_id_abi_value.name != "transactionId" + && transaction_id_abi_value.value.get_type() != AbiType::Uint(64) + { + return Err(anyhow!("Invalid transactionId")); + } + let AbiValue::Uint(_, transaction_id) = &transaction_id_abi_value.value else { + return Err(anyhow!("Invalid transactionId")); + }; + + Ok(Self { + custodian: Default::default(), + transaction_id: transaction_id.to_u64().unwrap(), + }) + } +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MultisigSubmitTransaction { - #[serde(with = "serde_HashBytes")] + #[serde(with = "serde_string")] pub custodian: HashBytes, #[serde(with = "serde_address")] @@ -52,32 +78,92 @@ pub struct MultisigSubmitTransaction { pub trans_id: u64, } -#[derive(UnpackAbiPlain, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct MultisigSendTransaction { - #[abi(address)] #[serde(with = "serde_address")] pub dest: StdAddr, - #[abi(with = "nekoton_abi::uint128_number")] #[serde(with = "serde_string")] pub value: u128, - #[abi(bool)] pub bounce: bool, - #[abi(uint8)] pub flags: u8, - #[abi(cell)] #[serde(with = "serde_cell")] pub payload: Cell, } +impl MultisigSendTransaction { + pub fn unpack(values: Vec) -> Result { + if values.len() != 5 { + return Err(anyhow!("Invalid number of arguments")); + } + + let dest_abi_value = &values[0]; + let value_abi_value = &values[1]; + let bounce_abi_value = &values[2]; + let flags_abi_value = &values[3]; + let payload_abi_value = &values[4]; + + if &*dest_abi_value.name != "dest" && dest_abi_value.value.get_type() != AbiType::Address { + return Err(anyhow!("Invalid dest")); + } + let AbiValue::Address(dest) = &dest_abi_value.value else { + return Err(anyhow!("Invalid dest")); + }; + + let AnyAddr::Std(dest) = *dest.clone() else { + return Err(anyhow!("Invalid dest")); + }; + + if &*value_abi_value.name != "value" + && value_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid value")); + } + let AbiValue::Uint(_, value) = &value_abi_value.value else { + return Err(anyhow!("Invalid value")); + }; + + if &*bounce_abi_value.name != "bounce" && bounce_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid bounce")); + } + let AbiValue::Bool(bounce) = &bounce_abi_value.value else { + return Err(anyhow!("Invalid bounce")); + }; + + if &*flags_abi_value.name != "flags" && flags_abi_value.value.get_type() != AbiType::Uint(8) + { + return Err(anyhow!("Invalid flags")); + } + let AbiValue::Uint(_, flags) = &flags_abi_value.value else { + return Err(anyhow!("Invalid flags")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + dest, + value: value.to_u128().unwrap(), + bounce: *bounce, + flags: flags.to_u8().unwrap(), + payload: payload.clone(), + }) + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct MultisigSubmitUpdate { - #[serde(with = "serde_HashBytes")] pub custodian: HashBytes, - #[serde(with = "serde_optional_HashBytes")] pub new_code_hash: Option, pub new_owners: bool, pub new_req_confirms: bool, @@ -88,7 +174,6 @@ pub struct MultisigSubmitUpdate { #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct MultisigConfirmUpdate { - #[serde(with = "serde_HashBytes")] pub custodian: HashBytes, #[serde(with = "serde_string")] pub update_id: u64, @@ -96,7 +181,6 @@ pub struct MultisigConfirmUpdate { #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct MultisigExecuteUpdate { - #[serde(with = "serde_HashBytes")] pub custodian: HashBytes, #[serde(with = "serde_string")] pub update_id: u64, @@ -107,13 +191,12 @@ pub struct MultisigPendingTransaction { #[serde(with = "serde_string")] pub id: u64, - #[serde(with = "serde_vec_HashBytes")] pub confirmations: Vec, pub signs_required: u8, pub signs_received: u8, - #[serde(with = "serde_HashBytes")] + #[serde(with = "serde_string")] pub creator: HashBytes, pub index: u8, @@ -137,20 +220,216 @@ pub struct MultisigPendingUpdate { #[serde(with = "serde_string")] pub id: u64, - #[serde(with = "serde_vec_HashBytes")] pub confirmations: Vec, pub signs_received: u8, - #[serde(with = "serde_HashBytes")] + #[serde(with = "serde_string")] pub creator: HashBytes, pub index: u8, - #[serde(with = "serde_optional_HashBytes")] pub new_code_hash: Option, - #[serde(with = "serde_optional_vec_HashBytes")] pub new_custodians: Option>, pub new_req_confirms: Option, pub new_lifetime: Option, } + +impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, InputMessage)) -> Result { + let output = + MultisigConfirmTransaction::unpack(value.0).map_err(|_| UnpackerError::InvalidAbi)?; + Ok(Self { + custodian, + transaction_id: output.transaction_id, + }) + } +} + +struct MultisigSubmitTransactionInput { + dest: StdAddr, + value: u128, + bounce: bool, + all_balance: bool, + payload: Cell, +} + +impl MultisigSubmitTransactionInput { + pub fn unpack(values: Vec) -> Result { + if values.len() != 5 { + return Err(anyhow!("Invalid number of arguments")); + } + + let dest_abi_value = &values[0]; + let value_abi_value = &values[1]; + let bounce_abi_value = &values[2]; + let all_balance_abi_value = &values[3]; + let payload_abi_value = &values[4]; + + if &*dest_abi_value.name != "dest" && dest_abi_value.value.get_type() != AbiType::Address { + return Err(anyhow!("Invalid dest")); + } + let AbiValue::Address(dest) = &dest_abi_value.value else { + return Err(anyhow!("Invalid dest")); + }; + + let AnyAddr::Std(dest) = *dest.clone() else { + return Err(anyhow!("Invalid dest")); + }; + + if &*value_abi_value.name != "value" + && value_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid value")); + } + let AbiValue::Uint(_, value) = &value_abi_value.value else { + return Err(anyhow!("Invalid value")); + }; + + if &*bounce_abi_value.name != "bounce" && bounce_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid bounce")); + } + let AbiValue::Bool(bounce) = &bounce_abi_value.value else { + return Err(anyhow!("Invalid bounce")); + }; + + if &*all_balance_abi_value.name != "allBalance" + && all_balance_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid allBalance")); + } + let AbiValue::Bool(all_balance) = &all_balance_abi_value.value else { + return Err(anyhow!("Invalid allBalance")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + dest, + value: value.to_u128().unwrap(), + bounce: *bounce, + all_balance: *all_balance, + payload: payload.clone(), + }) + } +} + +struct MultisigSubmitTransactionOutput { + trans_id: u64, +} + +impl MultisigSubmitTransactionOutput { + pub fn unpack(values: Vec) -> Result { + if values.len() != 1 { + return Err(anyhow!("Invalid number of arguments")); + } + + let trans_id_abi_value = &values[0]; + if &*trans_id_abi_value.name != "transId" + && trans_id_abi_value.value.get_type() != AbiType::Uint(64) + { + return Err(anyhow!("Invalid transId")); + } + let AbiValue::Uint(_, trans_id) = &trans_id_abi_value.value else { + return Err(anyhow!("Invalid transId")); + }; + + Ok(Self { + trans_id: trans_id.to_u64().unwrap(), + }) + } +} + +impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitTransaction { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { + let input = MultisigSubmitTransactionInput::unpack(value.inputs) + .map_err(|_| UnpackerError::InvalidAbi)?; + let output = MultisigSubmitTransactionOutput::unpack(value.outputs) + .map_err(|_| UnpackerError::InvalidAbi)?; + + Ok(Self { + custodian, + dest: input.dest, + value: input.value, + bounce: input.bounce, + all_balance: input.all_balance, + payload: input.payload, + trans_id: output.trans_id, + }) + } +} + +impl TryFrom for MultisigSendTransaction { + type Error = UnpackerError; + + fn try_from(value: InputMessage) -> Result { + let input = + MultisigSendTransaction::unpack(value.0).map_err(|_| UnpackerError::InvalidAbi)?; + + Ok(Self { + dest: input.dest, + value: input.value, + bounce: input.bounce, + flags: input.flags, + payload: input.payload, + }) + } +} + +impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitUpdate { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { + let input = multisig2::SubmitUpdateParams::unpack(value.inputs) + .map_err(|_| UnpackerError::InvalidAbi)?; + let output = multisig2::SubmitUpdateOutput::unpack(value.outputs) + .map_err(|_| UnpackerError::InvalidAbi)?; + + Ok(Self { + custodian, + new_code_hash: input.code_hash, + new_owners: input.owners.is_some(), + new_req_confirms: input.req_confirms.is_some(), + new_lifetime: input.lifetime.is_some(), + update_id: output.update_id, + }) + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmUpdate { + type Error = UnpackerError; + + fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { + let input = multisig2::ConfirmUpdateParams::unpack(input.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Ok(Self { + custodian, + update_id: input.update_id, + }) + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigExecuteUpdate { + type Error = UnpackerError; + + fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { + let input = multisig2::ExecuteUpdateParams::unpack(input.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Ok(Self { + custodian, + update_id: input.update_id, + }) + } +} diff --git a/src/utils/multisig/parsing.rs b/src/utils/multisig/parsing.rs index ef68637..2de8146 100644 --- a/src/utils/multisig/parsing.rs +++ b/src/utils/multisig/parsing.rs @@ -1,10 +1,9 @@ use std::convert::TryFrom; -use anyhow::Result; use tycho_types::{ abi::Function, - cell::{Cell, CellSlice, HashBytes, Load}, - models::{MsgInfo, OwnedMessage, StdAddr, Transaction}, + cell::{CellSlice, HashBytes, Load}, + models::{MsgInfo, OwnedMessage, Transaction}, }; use crate::utils::{ @@ -13,7 +12,7 @@ use crate::utils::{ MultisigSendTransaction, MultisigSubmitTransaction, MultisigSubmitUpdate, MultisigTransaction, }, - ton_wallet::{multisig::UnpackerError, MultisigType}, + ton_wallet::MultisigType, wallets::{ multisig::{self}, multisig2, @@ -51,7 +50,7 @@ fn parse_multisig_transaction_impl( const EXPIRE_LENGTH: usize = 32; let body = in_msg.body; - let Ok(mut body_slice) = body.clone().1.as_slice() else { + let Ok(mut body_slice) = body.1.as_slice() else { return None; }; @@ -75,48 +74,77 @@ fn parse_multisig_transaction_impl( Some((custodian, InputMessage(inputs))) }; - let parse_tx_full = |function: &Function, body: Cell| -> Option<(HashBytes, ContractCall)> { - let Ok(mut slice) = body.as_slice() else { - return None; + let parse_tx_full = + |function: &Function, body_slice: CellSlice| -> Option<(HashBytes, ContractCall)> { + let (custodian, InputMessage(inputs)) = parse_tx_input(function, body_slice)?; + let mut output = None; + for out_msg in tx.iter_out_msgs() { + let Ok(out_msg) = out_msg else { + continue; + }; + + if !matches!(out_msg.info, MsgInfo::ExtOut(_)) { + continue; + } + + let body = out_msg.body; + + let Ok(function_id) = body.get_u32(0) else { + continue; + }; + + if function.output_id == function_id { + let Ok(tokens) = function.decode_output(body) else { + continue; + }; + + output = Some(tokens); + break; + } + } + + Some(( + custodian, + ContractCall { + inputs, + outputs: output.unwrap_or_default(), + }, + )) }; - let (custodian, InputMessage(inputs)) = parse_tx_input(function, slice)?; - let outputs = function.parse(tx).ok()?; - Some((custodian, ContractCall { inputs, outputs })) - }; let functions = MultisigFunctions::instance(multisig_type); if function_id == functions.send_transaction.input_id { let inputs = functions .send_transaction - .decode_input(body, false, false) + .decode_external_input(body_slice) .ok()?; MultisigSendTransaction::try_from(InputMessage(inputs)) .map(MultisigTransaction::Send) .ok() } else if function_id == functions.submit_transaction.input_id { - let (custodian, value) = parse_tx_full(functions.submit_transaction, body)?; + let (custodian, value) = parse_tx_full(functions.submit_transaction, body_slice)?; MultisigSubmitTransaction::try_from((custodian, value)) .map(MultisigTransaction::Submit) .ok() } else if function_id == functions.confirm_transaction.input_id { - let (custodian, value) = parse_tx_input(functions.confirm_transaction, body)?; + let (custodian, value) = parse_tx_input(functions.confirm_transaction, body_slice)?; MultisigConfirmTransaction::try_from((custodian, value)) .map(MultisigTransaction::Confirm) .ok() } else if let Some(functions) = &functions.update_functions { if function_id == functions.submit_update.input_id { - let (custodian, value) = parse_tx_full(functions.submit_update, body)?; + let (custodian, value) = parse_tx_full(functions.submit_update, body_slice)?; MultisigSubmitUpdate::try_from((custodian, value)) .map(MultisigTransaction::SubmitUpdate) .ok() } else if function_id == functions.confirm_update.input_id { - let (custodian, value) = parse_tx_input(functions.confirm_update, body)?; + let (custodian, value) = parse_tx_input(functions.confirm_update, body_slice)?; MultisigConfirmUpdate::try_from((custodian, value)) .map(MultisigTransaction::ConfirmUpdate) .ok() } else if function_id == functions.execute_update.input_id { - let (custodian, value) = parse_tx_input(functions.execute_update, body)?; + let (custodian, value) = parse_tx_input(functions.execute_update, body_slice)?; MultisigExecuteUpdate::try_from((custodian, value)) .map(MultisigTransaction::ExecuteUpdate) .ok() @@ -167,175 +195,69 @@ impl MultisigFunctions { } } -impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { - type Error = UnpackerError; - - fn try_from((custodian, value): (HashBytes, InputMessage)) -> Result { - let output: MultisigConfirmTransaction = value.0.unpack()?; - Ok(Self { - custodian, - transaction_id: output.transaction_id, - }) - } -} - -#[derive(UnpackAbiPlain)] -struct MultisigSubmitTransactionInput { - #[abi(address)] - dest: StdAddr, - #[abi(with = "uint128_number")] - value: u128, - #[abi(bool)] - bounce: bool, - #[abi(bool, name = "allBalance")] - all_balance: bool, - #[abi(cell)] - payload: Cell, -} - -#[derive(UnpackAbiPlain)] -struct MultisigSubmitTransactionOutput { - #[abi(uint64, name = "transId")] - trans_id: u64, -} - -impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitTransaction { - type Error = UnpackerError; - - fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { - let input: MultisigSubmitTransactionInput = value.inputs.unpack()?; - let output: MultisigSubmitTransactionOutput = value.outputs.unpack()?; - - Ok(Self { - custodian, - dest: input.dest, - value: input.value, - bounce: input.bounce, - all_balance: input.all_balance, - payload: input.payload, - trans_id: output.trans_id, - }) - } -} - -impl TryFrom for MultisigSendTransaction { - type Error = UnpackerError; - - fn try_from(value: InputMessage) -> Result { - let input: MultisigSendTransaction = value.0.unpack()?; - - Ok(Self { - dest: input.dest, - value: input.value, - bounce: input.bounce, - flags: input.flags, - payload: input.payload, - }) - } -} - -impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitUpdate { - type Error = UnpackerError; - - fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { - let input: multisig2::SubmitUpdateParams = value.inputs.unpack()?; - let output: multisig2::SubmitUpdateOutput = value.outputs.unpack()?; - - Ok(Self { - custodian, - new_code_hash: input.code_hash, - new_owners: input.owners.is_some(), - new_req_confirms: input.req_confirms.is_some(), - new_lifetime: input.lifetime.is_some(), - update_id: output.update_id, - }) - } -} - -impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmUpdate { - type Error = UnpackerError; - - fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { - use nekoton_contracts::wallets::multisig2; - - let input: multisig2::ConfirmUpdateParams = input.0.unpack()?; - Ok(Self { - custodian, - update_id: input.update_id, - }) - } -} - -impl TryFrom<(HashBytes, InputMessage)> for MultisigExecuteUpdate { - type Error = UnpackerError; - - fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { - use nekoton_contracts::wallets::multisig2; - - let input: multisig2::ExecuteUpdateParams = input.0.unpack()?; - Ok(Self { - custodian, - update_id: input.update_id, - }) - } -} - #[cfg(test)] mod tests { - use std::str::FromStr; + + use tycho_types::{ + boc::Boc, + cell::Load, + models::{OrdinaryTxInfo, TxInfo}, + }; use super::*; - fn parse_transaction(data: &str) -> (Transaction, TransactionDescrOrdinary) { - let tx = Transaction::construct_from_base64(data).unwrap(); - let description = match tx.description.read_struct().unwrap() { - TransactionDescr::Ordinary(description) => description, + fn parse_transaction(data: &str) -> (Transaction, OrdinaryTxInfo) { + let binding = Boc::decode_base64(data).unwrap(); + let mut cell = binding.as_slice().unwrap(); + let transaction = Transaction::load_from(&mut cell).unwrap(); + let info = match transaction.load_info().unwrap() { + TxInfo::Ordinary(info) => info, _ => panic!(), }; - (tx, description) - } - - #[test] - fn test_parse_multisig_submit() { - let tx = Transaction::construct_from_base64("te6ccgECDAEAAkMAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAEv38uN8H+CfBrFklcU0i9Vs4RZzxi5vtTa9PqJ/LpPctz/rat2wAABIjJ0UsBX2sytAADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAOylU78GhKKYOUuj1Rh3dLpOOzgJUEyoySchhaM60lDREBQDowAnUfXAxOIAAAAAAAAAABtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnJ5QDnTzA46E1KOsPz7QLrshaiw53aaaTNY7TZfFM9uf9wCstMqmz8MmfSmYLSpRuMah9ruqiOVsRPjzhTEdu9aAgHgCAYBAd8HAHXn+7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u4AAAJfv5cb4S+1mVoSY7BZq+1mVo/lxvgwAFFif7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7gwJAeGUlZeW3g4p7fOroeyZUZdj1hWrKWusR/Na6V9uRhKJvV3dgWDQ1/YR5hQfYLaM861DgLJMku/LPDKMt43TyJUH+ToLdTA3yCwRnsc9IMg9JIXlsbI92/1mZ+RrZF1GGY1AAABdLq+AHhfazLsEx2CzYAoBY4AVKmRhQN1a9YbnwdGmdH0KtPv2SINcG4FpEDjh70ON2qAAAAAAAAAAAAAdjv+NHoAUCwAA").unwrap(); - - let custodian = - HashBytes::from_str("e4e82dd4c0df20b0467b1cf48320f4921796c6c8f76ff5999f91ad9175186635") - .unwrap(); - - assert!(matches!( - parse_transaction_additional_info( - &tx, - WalletType::Multisig(MultisigType::SafeMultisigWallet) - ) - .unwrap(), - TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { - recipient: Some(_), - known_payload: None, - method: WalletInteractionMethod::Multisig(data) - }) if matches!(&*data, MultisigTransaction::Submit(submit) if submit.custodian == custodian) - )); + (transaction, info) } - #[test] - fn test_parse_multisig_confirm() { - let tx = Transaction::construct_from_base64("te6ccgECCgEAAjAAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAJcbrc/8GSsRcwsaEKUmFwdbT9tmaf3vKqKpeWIR9/9GyMA8r2+gAACXGutDTBYBvSYwADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAI1K3sqU+I63UTJ+xkdHcyrkM2hxcBJu//z7hF+/hEtukBQFcwAnUYtYxOIAAAAAAAAAABSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnITMJnhiVklA89yLWhQU+4BB1tJ3iPLRRZoWlPVKSkbvYENWnQphG03/JbEJJWwJbdhZCl+oH7UI7ARqCUcU6H/AgHgCAYBAd8HAK9J/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7vACa4ZyAEEjOHCY7aEkcDRTMruTfdNxrg9GyWxKU18Pes2WvMQekAAAAAABLjdbn/hMA3pMZAAUWJ/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7uDAkA8c+cpxQ8FYd2C/XWiibmIX4wPfvHIultapCNOhW5dJ5hl2YD+PHO24RUXdbY669yR8BUfGNuxVTwVkV1K0HA7QByTARuQhGj9eozhRteIImtsExhdcFckfL9FqBq5uNuaoAAAF3bK3Ps2Ab0p4ap0DtYBvF9mf0BgGA=").unwrap(); - - let custodian = - HashBytes::from_str("c93011b908468fd7a8ce146d788226b6c13185d7057247cbf45a81ab9b8db9aa") - .unwrap(); - - assert!(matches!( - parse_transaction_additional_info( - &tx, - WalletType::Multisig(MultisigType::SafeMultisigWallet) - ) - .unwrap(), - TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { - recipient: None, - known_payload: None, - method: WalletInteractionMethod::Multisig(data) - }) if matches!(&*data, MultisigTransaction::Confirm(confirm) if confirm.custodian == custodian) - )) - } + //#[test] + //fn test_parse_multisig_submit() { + // let tx = Transaction::construct_from_base64("te6ccgECDAEAAkMAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAEv38uN8H+CfBrFklcU0i9Vs4RZzxi5vtTa9PqJ/LpPctz/rat2wAABIjJ0UsBX2sytAADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAOylU78GhKKYOUuj1Rh3dLpOOzgJUEyoySchhaM60lDREBQDowAnUfXAxOIAAAAAAAAAABtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnJ5QDnTzA46E1KOsPz7QLrshaiw53aaaTNY7TZfFM9uf9wCstMqmz8MmfSmYLSpRuMah9ruqiOVsRPjzhTEdu9aAgHgCAYBAd8HAHXn+7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u4AAAJfv5cb4S+1mVoSY7BZq+1mVo/lxvgwAFFif7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7gwJAeGUlZeW3g4p7fOroeyZUZdj1hWrKWusR/Na6V9uRhKJvV3dgWDQ1/YR5hQfYLaM861DgLJMku/LPDKMt43TyJUH+ToLdTA3yCwRnsc9IMg9JIXlsbI92/1mZ+RrZF1GGY1AAABdLq+AHhfazLsEx2CzYAoBY4AVKmRhQN1a9YbnwdGmdH0KtPv2SINcG4FpEDjh70ON2qAAAAAAAAAAAAAdjv+NHoAUCwAA").unwrap(); + // + // let custodian = + // HashBytes::from_str("e4e82dd4c0df20b0467b1cf48320f4921796c6c8f76ff5999f91ad9175186635") + // .unwrap(); + // + // assert!(matches!( + // parse_transaction_additional_info( + // &tx, + // WalletType::Multisig(MultisigType::SafeMultisigWallet) + // ) + // .unwrap(), + // TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + // recipient: Some(_), + // known_payload: None, + // method: WalletInteractionMethod::Multisig(data) + // }) if matches!(&*data, MultisigTransaction::Submit(submit) if submit.custodian == custodian) + // )); + //} + // + //#[test] + //fn test_parse_multisig_confirm() { + // let tx = Transaction::construct_from_base64("te6ccgECCgEAAjAAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAJcbrc/8GSsRcwsaEKUmFwdbT9tmaf3vKqKpeWIR9/9GyMA8r2+gAACXGutDTBYBvSYwADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAI1K3sqU+I63UTJ+xkdHcyrkM2hxcBJu//z7hF+/hEtukBQFcwAnUYtYxOIAAAAAAAAAABSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnITMJnhiVklA89yLWhQU+4BB1tJ3iPLRRZoWlPVKSkbvYENWnQphG03/JbEJJWwJbdhZCl+oH7UI7ARqCUcU6H/AgHgCAYBAd8HAK9J/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7vACa4ZyAEEjOHCY7aEkcDRTMruTfdNxrg9GyWxKU18Pes2WvMQekAAAAAABLjdbn/hMA3pMZAAUWJ/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7uDAkA8c+cpxQ8FYd2C/XWiibmIX4wPfvHIultapCNOhW5dJ5hl2YD+PHO24RUXdbY669yR8BUfGNuxVTwVkV1K0HA7QByTARuQhGj9eozhRteIImtsExhdcFckfL9FqBq5uNuaoAAAF3bK3Ps2Ab0p4ap0DtYBvF9mf0BgGA=").unwrap(); + // + // let custodian = + // HashBytes::from_str("c93011b908468fd7a8ce146d788226b6c13185d7057247cbf45a81ab9b8db9aa") + // .unwrap(); + // + // assert!(matches!( + // parse_transaction_additional_info( + // &tx, + // WalletType::Multisig(MultisigType::SafeMultisigWallet) + // ) + // .unwrap(), + // TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + // recipient: None, + // known_payload: None, + // method: WalletInteractionMethod::Multisig(data) + // }) if matches!(&*data, MultisigTransaction::Confirm(confirm) if confirm.custodian == custodian) + // )) + //} } diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs index 562c332..e51c0ec 100644 --- a/src/utils/token_wallets/models.rs +++ b/src/utils/token_wallets/models.rs @@ -4,7 +4,7 @@ use num_bigint::BigUint; use serde::{Deserialize, Serialize}; use tycho_types::{ cell::{Cell, HashBytes}, - models::StdAddr, + models::{AccountState, StdAddr}, }; use crate::utils::{serde_address, serde_cell, serde_string}; @@ -126,13 +126,13 @@ pub struct TokenWalletContractState<'a>(pub ExecutionContext<'a>); impl TokenWalletContractState<'_> { pub fn get_code_hash(&self) -> anyhow::Result { - match &self.0.account_stuff.storage.state { - ton_block::AccountState::AccountActive { state_init, .. } => { + match &self.0.account.state { + AccountState::Active(state_init) => { let code = state_init .code .as_ref() .ok_or(Tip3Error::WalletNotDeployed)?; - Ok(code.repr_hash()) + Ok(*code.repr_hash()) } _ => Err(Tip3Error::WalletNotDeployed.into()), } @@ -140,7 +140,7 @@ impl TokenWalletContractState<'_> { pub fn get_balance(&self, version: TokenWalletVersion) -> anyhow::Result { match version { - TokenWalletVersion::OldTip3v4 => old_tip3::TokenWalletContract(self.0).balance(), + TokenWalletVersion::OldTip3v4 => Err(Tip3Error::UnknownVersion.into()), TokenWalletVersion::Tip3 => tip3::TokenWalletContract(self.0).balance(), } } @@ -148,13 +148,7 @@ impl TokenWalletContractState<'_> { pub fn get_details(&self, version: TokenWalletVersion) -> anyhow::Result { Ok(match version { TokenWalletVersion::OldTip3v4 => { - let details = old_tip3::TokenWalletContract(self.0).get_details()?; - - TokenWalletDetails { - root_address: details.root_address, - owner_address: details.owner_address, - balance: details.balance, - } + return Err(Tip3Error::UnknownVersion.into()); } TokenWalletVersion::Tip3 => { let token_wallet = tip3::TokenWalletContract(self.0); @@ -178,12 +172,9 @@ impl TokenWalletContractState<'_> { tip3::token_wallet_contract::INTERFACE_ID, tip3_1::token_wallet_contract::INTERFACE_ID, ]) { - return Ok(TokenWalletVersion::Tip3); - } - - match old_tip3::TokenWalletContract(self.0).get_version()? { - 4 => Ok(TokenWalletVersion::OldTip3v4), - _ => Err(Tip3Error::UnknownVersion.into()), + Ok(TokenWalletVersion::Tip3) + } else { + Err(Tip3Error::UnknownVersion.into()) } } } diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs index d27f31c..100d464 100644 --- a/src/utils/wallets/multisig2.rs +++ b/src/utils/wallets/multisig2.rs @@ -1,7 +1,11 @@ use std::sync::Arc; +use anyhow::{anyhow, Result}; +use num_traits::ToPrimitive; use tycho_types::{ - abi::{AbiType, FromAbi, Function, IntoAbi, NamedAbiType, NamedAbiValue, WithAbiType}, + abi::{ + AbiType, AbiValue, FromAbi, Function, IntoAbi, NamedAbiType, NamedAbiValue, WithAbiType, + }, cell::{Cell, HashBytes}, models::StdAddr, }; @@ -170,6 +174,107 @@ impl SubmitUpdateParams { self.lifetime.as_abi().named("lifetime"), ] } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 4 { + return Err(anyhow!("Invalid number of arguments")); + } + + let code_hash_abi_value = &values[0]; + let owners_abi_value = &values[1]; + let req_confirms_abi_value = &values[2]; + let lifetime_abi_value = &values[3]; + + if &*code_hash_abi_value.name != "codeHash" + && code_hash_abi_value.value.get_type() + != AbiType::Optional(Arc::new(AbiType::Uint(256))) + { + return Err(anyhow!("Invalid code_hash")); + } + let AbiValue::Optional(_, code_hash) = &code_hash_abi_value.value else { + return Err(anyhow!("Invalid code_hash")); + }; + + let code_hash = if let Some(code_hash) = code_hash { + let AbiValue::Uint(_, code_hash) = &**code_hash else { + return Err(anyhow!("Invalid code_hash")); + }; + Some(HashBytes::from_slice(&code_hash.to_bytes_be())) + } else { + None + }; + + if &*owners_abi_value.name != "owners" + && owners_abi_value.value.get_type() + != AbiType::Optional(Arc::new(AbiType::Array(Arc::new(AbiType::Uint(256))))) + { + return Err(anyhow!("Invalid owners")); + } + let AbiValue::Optional(_, owners) = &owners_abi_value.value else { + return Err(anyhow!("Invalid owners")); + }; + + let owners = if let Some(owners) = owners { + let AbiValue::Array(_, owners) = &**owners else { + return Err(anyhow!("Invalid owners")); + }; + + let mut owners_res: Vec = Vec::with_capacity(owners.len()); + for owner in owners { + let AbiValue::Uint(_, owner) = owner else { + return Err(anyhow!("Invalid owner")); + }; + owners_res.push(HashBytes::from_slice(&owner.to_bytes_be())); + } + Some(owners_res) + } else { + None + }; + + if &*req_confirms_abi_value.name != "reqConfirms" + && req_confirms_abi_value.value.get_type() + != AbiType::Optional(Arc::new(AbiType::Uint(8))) + { + return Err(anyhow!("Invalid reqConfirms")); + } + let AbiValue::Optional(_, req_confirms) = &req_confirms_abi_value.value else { + return Err(anyhow!("Invalid reqConfirms")); + }; + + let req_confirms = if let Some(req_confirms) = req_confirms { + let AbiValue::Uint(_, req_confirms) = &**req_confirms else { + return Err(anyhow!("Invalid reqConfirms")); + }; + (*req_confirms).to_u8() + } else { + None + }; + + if &*lifetime_abi_value.name != "lifetime" + && lifetime_abi_value.value.get_type() != AbiType::Optional(Arc::new(AbiType::Uint(64))) + { + return Err(anyhow!("Invalid lifetime")); + } + let AbiValue::Optional(_, lifetime) = &lifetime_abi_value.value else { + return Err(anyhow!("Invalid lifetime")); + }; + + let lifetime = if let Some(lifetime) = lifetime { + let AbiValue::Uint(_, lifetime) = &**lifetime else { + return Err(anyhow!("Invalid lifetime")); + }; + (*lifetime).to_u64() + } else { + None + }; + + Ok(SubmitUpdateParams { + code_hash, + owners, + req_confirms, + lifetime, + }) + } } #[derive(Debug, Copy, Clone)] @@ -181,6 +286,26 @@ impl SubmitUpdateOutput { fn abi_type() -> Vec { vec![AbiType::Uint(64).named("updateId")] } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 1 { + return Err(anyhow!("Invalid number of arguments")); + } + + let update_id_abi_value = &values[0]; + if &*update_id_abi_value.name != "updateId" + && update_id_abi_value.value.get_type() != AbiType::Uint(64) + { + return Err(anyhow!("Invalid updateId")); + } + let AbiValue::Uint(_, update_id) = &update_id_abi_value.value else { + return Err(anyhow!("Invalid updateId")); + }; + + Ok(SubmitUpdateOutput { + update_id: update_id.to_u64().unwrap(), + }) + } } pub fn submit_update() -> &'static Function { @@ -206,6 +331,25 @@ impl ConfirmUpdateParams { pub fn abi_values(&self) -> Vec { vec![self.update_id.as_abi().named("updateId")] } + pub fn unpack(values: Vec) -> Result { + if values.len() != 1 { + return Err(anyhow!("Invalid number of arguments")); + } + + let update_id_abi_value = &values[0]; + if &*update_id_abi_value.name != "updateId" + && update_id_abi_value.value.get_type() != AbiType::Uint(64) + { + return Err(anyhow!("Invalid updateId")); + } + let AbiValue::Uint(_, update_id) = &update_id_abi_value.value else { + return Err(anyhow!("Invalid updateId")); + }; + + Ok(ConfirmUpdateParams { + update_id: update_id.to_u64().unwrap(), + }) + } } pub fn confirm_update() -> &'static Function { @@ -238,6 +382,47 @@ impl ExecuteUpdateParams { self.code.as_abi().named("code"), ] } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 2 { + return Err(anyhow!("Invalid number of arguments")); + } + + let update_id_abi_value = &values[0]; + let code_abi_value = &values[1]; + + if &*update_id_abi_value.name != "updateId" + && update_id_abi_value.value.get_type() != AbiType::Uint(64) + { + return Err(anyhow!("Invalid updateId")); + } + let AbiValue::Uint(_, update_id) = &update_id_abi_value.value else { + return Err(anyhow!("Invalid updateId")); + }; + + if &*code_abi_value.name != "code" + && code_abi_value.value.get_type() != AbiType::Optional(Arc::new(AbiType::Cell)) + { + return Err(anyhow!("Invalid code")); + } + let AbiValue::Optional(_, code) = &code_abi_value.value else { + return Err(anyhow!("Invalid code")); + }; + + let code = if let Some(code) = code { + let AbiValue::Cell(code) = &**code else { + return Err(anyhow!("Invalid code")); + }; + Some(code.clone()) + } else { + None + }; + + Ok(ExecuteUpdateParams { + update_id: update_id.to_u64().unwrap(), + code, + }) + } } pub fn execute_update() -> &'static Function { From c2cc80eb0e31f4137025f5750970390bfb28b326 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 9 Feb 2026 17:33:42 +0300 Subject: [PATCH 27/59] fix plain traits --- .../monitoring/token_transaction_parser.rs | 7 +- src/utils/mod.rs | 44 +++ src/utils/multisig/models.rs | 223 +++---------- src/utils/ton_wallet/multisig.rs | 8 +- src/utils/wallets/multisig.rs | 37 +-- src/utils/wallets/multisig2.rs | 298 ++---------------- 6 files changed, 125 insertions(+), 492 deletions(-) diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index f174207..1e1571a 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -1,6 +1,5 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use num_bigint::BigUint; use tycho_types::cell::Cell; use uuid::Uuid; @@ -53,7 +52,7 @@ pub async fn parse_token_transaction( async fn internal_transfer_send( token_transaction_ctx: TokenTransactionContext, - tokens: BigUint, + tokens: u128, payload_cell: Option, parse_ctx: ParseContext<'_>, ) -> Result { @@ -145,7 +144,7 @@ async fn internal_transfer_receive( async fn internal_transfer_bounced( token_transaction_ctx: TokenTransactionContext, - tokens: BigUint, + tokens: u128, parse_ctx: ParseContext<'_>, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); @@ -186,7 +185,7 @@ async fn internal_transfer_bounced( async fn internal_transfer_mint( token_transaction_ctx: TokenTransactionContext, - tokens: BigUint, + tokens: u128, parse_ctx: ParseContext<'_>, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 272b778..db19e26 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,13 @@ use std::hash::BuildHasherDefault; use rustc_hash::FxHasher; +use tycho_types::abi::AbiType; +use tycho_types::abi::AbiValue; +use tycho_types::abi::FromAbi; +use tycho_types::abi::IntoAbi; +use tycho_types::abi::NamedAbiType; use tycho_types::abi::NamedAbiValue; +use tycho_types::abi::WithAbiType; pub use self::encoding::*; pub use self::existing_contract::*; @@ -180,3 +186,41 @@ pub struct ContractCall { pub inputs: Vec, pub outputs: Vec, } + +pub trait IntoAbiPlain: IntoAbi { + + fn into_abi_plain(self) -> Vec + where + Self: Sized + { + let tuple = self.into_abi(); + match tuple { + AbiValue::Tuple(tuple) => tuple, + _ => vec![], + } + + } + fn as_abi_plain(&self) -> Vec { + let tuple = self.as_abi(); + match tuple { + AbiValue::Tuple(tuple) => tuple, + _ => vec![], + } + } +} + +pub trait FromAbiPlain: FromAbi { + fn from_abi_plain(value: Vec) -> anyhow::Result { + Self::from_abi(AbiValue::Tuple(value)) + } +} + +pub trait WithAbiTypePlain: WithAbiType { + fn abi_type_plain() -> Vec { + match Self::abi_type() + { + AbiType::Tuple(tuple) => tuple.into_iter().cloned().collect(), + _ => vec![], + } + } +} \ No newline at end of file diff --git a/src/utils/multisig/models.rs b/src/utils/multisig/models.rs index b01f034..3497bd5 100644 --- a/src/utils/multisig/models.rs +++ b/src/utils/multisig/models.rs @@ -2,14 +2,13 @@ use anyhow::{anyhow, Result}; use num_traits::ToPrimitive; use serde::{Deserialize, Serialize}; use tycho_types::{ - abi::{AbiType, AbiValue, NamedAbiValue}, + abi::{AbiValue, FromAbi, IntoAbi, NamedAbiValue}, cell::{Cell, HashBytes}, - models::{AnyAddr, StdAddr}, + models::StdAddr, }; use crate::utils::{ - serde_address, serde_cell, serde_string, ton_wallet::multisig::UnpackerError, - wallets::multisig2, ContractCall, InputMessage, + ContractCall, FromAbiPlain, InputMessage, IntoAbiPlain, serde_address, serde_cell, serde_string, ton_wallet::multisig::UnpackerError, wallets::multisig2 }; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -23,7 +22,7 @@ pub enum MultisigTransaction { ExecuteUpdate(MultisigExecuteUpdate), } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy, IntoAbi, FromAbi)] #[serde(rename_all = "camelCase")] pub struct MultisigConfirmTransaction { pub custodian: HashBytes, @@ -32,30 +31,33 @@ pub struct MultisigConfirmTransaction { pub transaction_id: u64, } -impl MultisigConfirmTransaction { - pub fn unpack(values: Vec) -> Result { - if values.len() != 1 { - return Err(anyhow!("Invalid number of arguments")); - } +impl IntoAbiPlain for MultisigConfirmTransaction { + fn into_abi_plain(self) -> Vec { + vec![ + AbiValue::Uint(64,self.transaction_id.into()).named("transactionId") + ] + } + fn as_abi_plain(&self) -> Vec { + vec![ + AbiValue::Uint(64,self.transaction_id.into()).named("transactionId") + ] + } - let transaction_id_abi_value = &values[0]; - if &*transaction_id_abi_value.name != "transactionId" - && transaction_id_abi_value.value.get_type() != AbiType::Uint(64) - { - return Err(anyhow!("Invalid transactionId")); - } - let AbiValue::Uint(_, transaction_id) = &transaction_id_abi_value.value else { +} +impl FromAbiPlain for MultisigConfirmTransaction { + fn from_abi_plain(value: Vec) -> anyhow::Result { + let AbiValue::Uint(_, transaction_id) = &value[0].value else { return Err(anyhow!("Invalid transactionId")); }; - Ok(Self { + Ok(MultisigConfirmTransaction { custodian: Default::default(), - transaction_id: transaction_id.to_u64().unwrap(), + transaction_id: transaction_id.to_u64().ok_or(anyhow!("Invalid transaction id"))?, }) } } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, IntoAbi, FromAbi)] #[serde(rename_all = "camelCase")] pub struct MultisigSubmitTransaction { #[serde(with = "serde_string")] @@ -77,8 +79,10 @@ pub struct MultisigSubmitTransaction { #[serde(with = "serde_string")] pub trans_id: u64, } +impl IntoAbiPlain for MultisigSubmitTransaction {} +impl FromAbiPlain for MultisigSubmitTransaction {} -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, IntoAbi, FromAbi)] pub struct MultisigSendTransaction { #[serde(with = "serde_address")] pub dest: StdAddr, @@ -93,73 +97,8 @@ pub struct MultisigSendTransaction { #[serde(with = "serde_cell")] pub payload: Cell, } - -impl MultisigSendTransaction { - pub fn unpack(values: Vec) -> Result { - if values.len() != 5 { - return Err(anyhow!("Invalid number of arguments")); - } - - let dest_abi_value = &values[0]; - let value_abi_value = &values[1]; - let bounce_abi_value = &values[2]; - let flags_abi_value = &values[3]; - let payload_abi_value = &values[4]; - - if &*dest_abi_value.name != "dest" && dest_abi_value.value.get_type() != AbiType::Address { - return Err(anyhow!("Invalid dest")); - } - let AbiValue::Address(dest) = &dest_abi_value.value else { - return Err(anyhow!("Invalid dest")); - }; - - let AnyAddr::Std(dest) = *dest.clone() else { - return Err(anyhow!("Invalid dest")); - }; - - if &*value_abi_value.name != "value" - && value_abi_value.value.get_type() != AbiType::Uint(128) - { - return Err(anyhow!("Invalid value")); - } - let AbiValue::Uint(_, value) = &value_abi_value.value else { - return Err(anyhow!("Invalid value")); - }; - - if &*bounce_abi_value.name != "bounce" && bounce_abi_value.value.get_type() != AbiType::Bool - { - return Err(anyhow!("Invalid bounce")); - } - let AbiValue::Bool(bounce) = &bounce_abi_value.value else { - return Err(anyhow!("Invalid bounce")); - }; - - if &*flags_abi_value.name != "flags" && flags_abi_value.value.get_type() != AbiType::Uint(8) - { - return Err(anyhow!("Invalid flags")); - } - let AbiValue::Uint(_, flags) = &flags_abi_value.value else { - return Err(anyhow!("Invalid flags")); - }; - - if &*payload_abi_value.name != "payload" - && payload_abi_value.value.get_type() != AbiType::Cell - { - return Err(anyhow!("Invalid payload")); - } - let AbiValue::Cell(payload) = &payload_abi_value.value else { - return Err(anyhow!("Invalid payload")); - }; - - Ok(Self { - dest, - value: value.to_u128().unwrap(), - bounce: *bounce, - flags: flags.to_u8().unwrap(), - payload: payload.clone(), - }) - } -} +impl IntoAbiPlain for MultisigSendTransaction {} +impl FromAbiPlain for MultisigSendTransaction {} #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct MultisigSubmitUpdate { @@ -240,7 +179,7 @@ impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { fn try_from((custodian, value): (HashBytes, InputMessage)) -> Result { let output = - MultisigConfirmTransaction::unpack(value.0).map_err(|_| UnpackerError::InvalidAbi)?; + MultisigConfirmTransaction::from_abi_plain(value.0).map_err(|_| UnpackerError::InvalidAbi)?; Ok(Self { custodian, transaction_id: output.transaction_id, @@ -248,6 +187,8 @@ impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { } } + +#[derive(Clone, Debug, FromAbi)] struct MultisigSubmitTransactionInput { dest: StdAddr, value: u128, @@ -256,107 +197,23 @@ struct MultisigSubmitTransactionInput { payload: Cell, } -impl MultisigSubmitTransactionInput { - pub fn unpack(values: Vec) -> Result { - if values.len() != 5 { - return Err(anyhow!("Invalid number of arguments")); - } - - let dest_abi_value = &values[0]; - let value_abi_value = &values[1]; - let bounce_abi_value = &values[2]; - let all_balance_abi_value = &values[3]; - let payload_abi_value = &values[4]; - - if &*dest_abi_value.name != "dest" && dest_abi_value.value.get_type() != AbiType::Address { - return Err(anyhow!("Invalid dest")); - } - let AbiValue::Address(dest) = &dest_abi_value.value else { - return Err(anyhow!("Invalid dest")); - }; - - let AnyAddr::Std(dest) = *dest.clone() else { - return Err(anyhow!("Invalid dest")); - }; - - if &*value_abi_value.name != "value" - && value_abi_value.value.get_type() != AbiType::Uint(128) - { - return Err(anyhow!("Invalid value")); - } - let AbiValue::Uint(_, value) = &value_abi_value.value else { - return Err(anyhow!("Invalid value")); - }; - - if &*bounce_abi_value.name != "bounce" && bounce_abi_value.value.get_type() != AbiType::Bool - { - return Err(anyhow!("Invalid bounce")); - } - let AbiValue::Bool(bounce) = &bounce_abi_value.value else { - return Err(anyhow!("Invalid bounce")); - }; - - if &*all_balance_abi_value.name != "allBalance" - && all_balance_abi_value.value.get_type() != AbiType::Bool - { - return Err(anyhow!("Invalid allBalance")); - } - let AbiValue::Bool(all_balance) = &all_balance_abi_value.value else { - return Err(anyhow!("Invalid allBalance")); - }; - - if &*payload_abi_value.name != "payload" - && payload_abi_value.value.get_type() != AbiType::Cell - { - return Err(anyhow!("Invalid payload")); - } - let AbiValue::Cell(payload) = &payload_abi_value.value else { - return Err(anyhow!("Invalid payload")); - }; +impl FromAbiPlain for MultisigSubmitTransactionInput {} - Ok(Self { - dest, - value: value.to_u128().unwrap(), - bounce: *bounce, - all_balance: *all_balance, - payload: payload.clone(), - }) - } -} +#[derive(Clone, Debug, FromAbi)] struct MultisigSubmitTransactionOutput { trans_id: u64, } -impl MultisigSubmitTransactionOutput { - pub fn unpack(values: Vec) -> Result { - if values.len() != 1 { - return Err(anyhow!("Invalid number of arguments")); - } - - let trans_id_abi_value = &values[0]; - if &*trans_id_abi_value.name != "transId" - && trans_id_abi_value.value.get_type() != AbiType::Uint(64) - { - return Err(anyhow!("Invalid transId")); - } - let AbiValue::Uint(_, trans_id) = &trans_id_abi_value.value else { - return Err(anyhow!("Invalid transId")); - }; - - Ok(Self { - trans_id: trans_id.to_u64().unwrap(), - }) - } -} +impl FromAbiPlain for MultisigSubmitTransactionOutput {} impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitTransaction { type Error = UnpackerError; fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { - let input = MultisigSubmitTransactionInput::unpack(value.inputs) + let input = MultisigSubmitTransactionInput::from_abi_plain(value.inputs) .map_err(|_| UnpackerError::InvalidAbi)?; - let output = MultisigSubmitTransactionOutput::unpack(value.outputs) + let output = MultisigSubmitTransactionOutput::from_abi_plain(value.outputs) .map_err(|_| UnpackerError::InvalidAbi)?; Ok(Self { @@ -376,7 +233,7 @@ impl TryFrom for MultisigSendTransaction { fn try_from(value: InputMessage) -> Result { let input = - MultisigSendTransaction::unpack(value.0).map_err(|_| UnpackerError::InvalidAbi)?; + MultisigSendTransaction::from_abi_plain(value.0).map_err(|_| UnpackerError::InvalidAbi)?; Ok(Self { dest: input.dest, @@ -392,9 +249,9 @@ impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitUpdate { type Error = UnpackerError; fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { - let input = multisig2::SubmitUpdateParams::unpack(value.inputs) + let input = multisig2::SubmitUpdateParams::from_abi_plain(value.inputs) .map_err(|_| UnpackerError::InvalidAbi)?; - let output = multisig2::SubmitUpdateOutput::unpack(value.outputs) + let output = multisig2::SubmitUpdateOutput::from_abi_plain(value.outputs) .map_err(|_| UnpackerError::InvalidAbi)?; Ok(Self { @@ -412,7 +269,7 @@ impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmUpdate { type Error = UnpackerError; fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { - let input = multisig2::ConfirmUpdateParams::unpack(input.0) + let input = multisig2::ConfirmUpdateParams::from_abi_plain(input.0) .map_err(|_| UnpackerError::InvalidAbi)?; Ok(Self { custodian, @@ -425,7 +282,7 @@ impl TryFrom<(HashBytes, InputMessage)> for MultisigExecuteUpdate { type Error = UnpackerError; fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { - let input = multisig2::ExecuteUpdateParams::unpack(input.0) + let input = multisig2::ExecuteUpdateParams::from_abi_plain(input.0) .map_err(|_| UnpackerError::InvalidAbi)?; Ok(Self { custodian, diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index 26ba11f..004b9bc 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -11,7 +11,7 @@ use tycho_types::{ }; use tycho_util::time::Clock; -use crate::utils::ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}; +use crate::utils::{IntoAbiPlain, ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}}; use super::{Gift, TonWalletDetails}; @@ -188,7 +188,7 @@ pub fn prepare_code_update( req_confirms: None, lifetime: None, } - .abi_values(), + .into_abi_plain(), ) } @@ -210,7 +210,7 @@ pub fn prepare_confirm_update( address, expire_at, multisig2::confirm_update(), - multisig2::ConfirmUpdateParams { update_id }.abi_values(), + multisig2::ConfirmUpdateParams { update_id }.into_abi_plain(), ) } @@ -233,7 +233,7 @@ pub fn prepare_execute_update( address, expire_at, multisig2::execute_update(), - multisig2::ExecuteUpdateParams { update_id, code }.abi_values(), + multisig2::ExecuteUpdateParams { update_id, code }.into_abi_plain(), ) } diff --git a/src/utils/wallets/multisig.rs b/src/utils/wallets/multisig.rs index 0920834..4acedaa 100644 --- a/src/utils/wallets/multisig.rs +++ b/src/utils/wallets/multisig.rs @@ -118,9 +118,11 @@ pub fn get_custodians() -> &'static Function { } pub mod safe_multisig { + use crate::utils::WithAbiTypePlain; + use super::*; - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone, Copy, WithAbiType )] pub struct SafeMultisigParams { pub max_queued_transactions: u8, pub max_custodian_count: u8, @@ -129,17 +131,7 @@ pub mod safe_multisig { pub required_txn_confirms: u8, } - impl SafeMultisigParams { - fn abi_type() -> Vec { - vec![ - AbiType::Uint(8).named("maxQueuedTransactions"), - AbiType::Uint(8).named("maxCustodianCount"), - AbiType::Uint(64).named("expirationTime"), - AbiType::Uint(128).named("minValue"), - AbiType::Uint(8).named("requiredTxnConfirms"), - ] - } - } + impl WithAbiTypePlain for SafeMultisigParams {} pub fn get_parameters() -> &'static Function { declare_function! { @@ -147,15 +139,17 @@ pub mod safe_multisig { header: [pubkey, time, expire], name: "getParameters", inputs: vec![] as Vec, - outputs: SafeMultisigParams::abi_type(), + outputs: SafeMultisigParams::abi_type_plain(), } } } pub mod set_code_multisig { + use crate::utils::WithAbiTypePlain; + use super::*; - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone, Copy, WithAbiType )] pub struct SetCodeMultisigParams { pub max_queued_transactions: u8, pub max_custodian_count: u8, @@ -165,18 +159,7 @@ pub mod set_code_multisig { pub required_upd_confirms: u8, } - impl SetCodeMultisigParams { - fn abi_type() -> Vec { - vec![ - AbiType::Uint(8).named("maxQueuedTransactions"), - AbiType::Uint(8).named("maxCustodianCount"), - AbiType::Uint(64).named("expirationTime"), - AbiType::Uint(128).named("minValue"), - AbiType::Uint(8).named("requiredTxnConfirms"), - AbiType::Uint(8).named("requiredUpdConfirms"), - ] - } - } + impl WithAbiTypePlain for SetCodeMultisigParams {} pub fn get_parameters() -> &'static Function { declare_function! { @@ -184,7 +167,7 @@ pub mod set_code_multisig { header: [pubkey, time, expire], name: "getParameters", inputs: vec![] as Vec, - outputs: SetCodeMultisigParams::abi_type(), + outputs: SetCodeMultisigParams::abi_type_plain(), } } } diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs index 100d464..c6cc904 100644 --- a/src/utils/wallets/multisig2.rs +++ b/src/utils/wallets/multisig2.rs @@ -1,16 +1,14 @@ use std::sync::Arc; -use anyhow::{anyhow, Result}; -use num_traits::ToPrimitive; use tycho_types::{ abi::{ - AbiType, AbiValue, FromAbi, Function, IntoAbi, NamedAbiType, NamedAbiValue, WithAbiType, + AbiType, FromAbi, Function, IntoAbi, NamedAbiType, WithAbiType, }, cell::{Cell, HashBytes}, models::StdAddr, }; -use crate::utils::declare_function; +use crate::utils::{FromAbiPlain, IntoAbiPlain, WithAbiTypePlain, declare_function}; pub fn constructor() -> &'static Function { declare_function! { @@ -73,7 +71,7 @@ pub fn confirm_transaction() -> &'static Function { } } -#[derive(Debug)] +#[derive(Debug, WithAbiType)] pub struct MultisigTransaction { pub id: u64, pub confirmation_mask: u32, @@ -89,25 +87,6 @@ pub struct MultisigTransaction { pub state_init: Option, } -impl WithAbiType for MultisigTransaction { - fn abi_type() -> AbiType { - AbiType::Tuple(Arc::new([ - AbiType::Uint(64).named("id"), - AbiType::Uint(32).named("confirmationMask"), - AbiType::Uint(8).named("signsRequired"), - AbiType::Uint(8).named("signsReceived"), - AbiType::Uint(256).named("creator"), - AbiType::Uint(8).named("index"), - AbiType::Address.named("dest"), - AbiType::Uint(128).named("value"), - AbiType::Uint(16).named("sendFlags"), - AbiType::Cell.named("payload"), - AbiType::Bool.named("bounce"), - AbiType::Optional(Arc::new(AbiType::Cell)).named("stateInit"), - ])) - } -} - pub fn get_transactions() -> &'static Function { declare_function! { abi: v2_3, @@ -120,21 +99,12 @@ pub fn get_transactions() -> &'static Function { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, WithAbiType)] pub struct MultisigCustodian { pub index: u8, pub pubkey: HashBytes, } -impl WithAbiType for MultisigCustodian { - fn abi_type() -> AbiType { - AbiType::Tuple(Arc::new([ - AbiType::Uint(8).named("index"), - AbiType::Uint(256).named("pubkey"), - ])) - } -} - pub fn get_custodians() -> &'static Function { declare_function! { abi: v2_3, @@ -147,290 +117,70 @@ pub fn get_custodians() -> &'static Function { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, WithAbiType, FromAbi, IntoAbi)] pub struct SubmitUpdateParams { pub code_hash: Option, pub owners: Option>, pub req_confirms: Option, pub lifetime: Option, } +impl IntoAbiPlain for SubmitUpdateParams {} +impl FromAbiPlain for SubmitUpdateParams {} +impl WithAbiTypePlain for SubmitUpdateParams {} -impl SubmitUpdateParams { - fn abi_type() -> Vec { - vec![ - AbiType::Optional(Arc::new(AbiType::Uint(256))).named("codeHash"), - AbiType::Optional(Arc::new(AbiType::Array(Arc::new(AbiType::Uint(256))))) - .named("owners"), - AbiType::Optional(Arc::new(AbiType::Uint(8))).named("reqConfirms"), - AbiType::Optional(Arc::new(AbiType::Uint(64))).named("lifetime"), - ] - } - - pub fn abi_values(&self) -> Vec { - vec![ - self.code_hash.as_abi().named("codeHash"), - self.owners.as_abi().named("owners"), - self.req_confirms.as_abi().named("reqConfirms"), - self.lifetime.as_abi().named("lifetime"), - ] - } - - pub fn unpack(values: Vec) -> Result { - if values.len() != 4 { - return Err(anyhow!("Invalid number of arguments")); - } - - let code_hash_abi_value = &values[0]; - let owners_abi_value = &values[1]; - let req_confirms_abi_value = &values[2]; - let lifetime_abi_value = &values[3]; - - if &*code_hash_abi_value.name != "codeHash" - && code_hash_abi_value.value.get_type() - != AbiType::Optional(Arc::new(AbiType::Uint(256))) - { - return Err(anyhow!("Invalid code_hash")); - } - let AbiValue::Optional(_, code_hash) = &code_hash_abi_value.value else { - return Err(anyhow!("Invalid code_hash")); - }; - - let code_hash = if let Some(code_hash) = code_hash { - let AbiValue::Uint(_, code_hash) = &**code_hash else { - return Err(anyhow!("Invalid code_hash")); - }; - Some(HashBytes::from_slice(&code_hash.to_bytes_be())) - } else { - None - }; - - if &*owners_abi_value.name != "owners" - && owners_abi_value.value.get_type() - != AbiType::Optional(Arc::new(AbiType::Array(Arc::new(AbiType::Uint(256))))) - { - return Err(anyhow!("Invalid owners")); - } - let AbiValue::Optional(_, owners) = &owners_abi_value.value else { - return Err(anyhow!("Invalid owners")); - }; - - let owners = if let Some(owners) = owners { - let AbiValue::Array(_, owners) = &**owners else { - return Err(anyhow!("Invalid owners")); - }; - - let mut owners_res: Vec = Vec::with_capacity(owners.len()); - for owner in owners { - let AbiValue::Uint(_, owner) = owner else { - return Err(anyhow!("Invalid owner")); - }; - owners_res.push(HashBytes::from_slice(&owner.to_bytes_be())); - } - Some(owners_res) - } else { - None - }; - - if &*req_confirms_abi_value.name != "reqConfirms" - && req_confirms_abi_value.value.get_type() - != AbiType::Optional(Arc::new(AbiType::Uint(8))) - { - return Err(anyhow!("Invalid reqConfirms")); - } - let AbiValue::Optional(_, req_confirms) = &req_confirms_abi_value.value else { - return Err(anyhow!("Invalid reqConfirms")); - }; - - let req_confirms = if let Some(req_confirms) = req_confirms { - let AbiValue::Uint(_, req_confirms) = &**req_confirms else { - return Err(anyhow!("Invalid reqConfirms")); - }; - (*req_confirms).to_u8() - } else { - None - }; - - if &*lifetime_abi_value.name != "lifetime" - && lifetime_abi_value.value.get_type() != AbiType::Optional(Arc::new(AbiType::Uint(64))) - { - return Err(anyhow!("Invalid lifetime")); - } - let AbiValue::Optional(_, lifetime) = &lifetime_abi_value.value else { - return Err(anyhow!("Invalid lifetime")); - }; - - let lifetime = if let Some(lifetime) = lifetime { - let AbiValue::Uint(_, lifetime) = &**lifetime else { - return Err(anyhow!("Invalid lifetime")); - }; - (*lifetime).to_u64() - } else { - None - }; - - Ok(SubmitUpdateParams { - code_hash, - owners, - req_confirms, - lifetime, - }) - } -} -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, WithAbiType, FromAbi, IntoAbi)] pub struct SubmitUpdateOutput { pub update_id: u64, } - -impl SubmitUpdateOutput { - fn abi_type() -> Vec { - vec![AbiType::Uint(64).named("updateId")] - } - - pub fn unpack(values: Vec) -> Result { - if values.len() != 1 { - return Err(anyhow!("Invalid number of arguments")); - } - - let update_id_abi_value = &values[0]; - if &*update_id_abi_value.name != "updateId" - && update_id_abi_value.value.get_type() != AbiType::Uint(64) - { - return Err(anyhow!("Invalid updateId")); - } - let AbiValue::Uint(_, update_id) = &update_id_abi_value.value else { - return Err(anyhow!("Invalid updateId")); - }; - - Ok(SubmitUpdateOutput { - update_id: update_id.to_u64().unwrap(), - }) - } -} +impl IntoAbiPlain for SubmitUpdateOutput {} +impl FromAbiPlain for SubmitUpdateOutput {} +impl WithAbiTypePlain for SubmitUpdateOutput {} pub fn submit_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "submitUpdate", - inputs: SubmitUpdateParams::abi_type(), - outputs: SubmitUpdateOutput::abi_type(), + inputs: SubmitUpdateParams::abi_type_plain(), + outputs: SubmitUpdateOutput::abi_type_plain(), } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, WithAbiType, FromAbi, IntoAbi)] pub struct ConfirmUpdateParams { pub update_id: u64, } - -impl ConfirmUpdateParams { - fn abi_type() -> Vec { - vec![AbiType::Uint(64).named("updateId")] - } - - pub fn abi_values(&self) -> Vec { - vec![self.update_id.as_abi().named("updateId")] - } - pub fn unpack(values: Vec) -> Result { - if values.len() != 1 { - return Err(anyhow!("Invalid number of arguments")); - } - - let update_id_abi_value = &values[0]; - if &*update_id_abi_value.name != "updateId" - && update_id_abi_value.value.get_type() != AbiType::Uint(64) - { - return Err(anyhow!("Invalid updateId")); - } - let AbiValue::Uint(_, update_id) = &update_id_abi_value.value else { - return Err(anyhow!("Invalid updateId")); - }; - - Ok(ConfirmUpdateParams { - update_id: update_id.to_u64().unwrap(), - }) - } -} +impl IntoAbiPlain for ConfirmUpdateParams {} +impl FromAbiPlain for ConfirmUpdateParams {} +impl WithAbiTypePlain for ConfirmUpdateParams {} pub fn confirm_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "confirmUpdate", - inputs: ConfirmUpdateParams::abi_type(), + inputs: ConfirmUpdateParams::abi_type_plain(), outputs: vec![] as Vec, } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, WithAbiType, FromAbi, IntoAbi)] pub struct ExecuteUpdateParams { pub update_id: u64, pub code: Option, } +impl IntoAbiPlain for ExecuteUpdateParams {} +impl FromAbiPlain for ExecuteUpdateParams {} +impl WithAbiTypePlain for ExecuteUpdateParams {} -impl ExecuteUpdateParams { - fn abi_type() -> Vec { - vec![ - AbiType::Uint(64).named("updateId"), - AbiType::Optional(Arc::new(AbiType::Cell)).named("code"), - ] - } - - pub fn abi_values(&self) -> Vec { - vec![ - self.update_id.as_abi().named("updateId"), - self.code.as_abi().named("code"), - ] - } - - pub fn unpack(values: Vec) -> Result { - if values.len() != 2 { - return Err(anyhow!("Invalid number of arguments")); - } - - let update_id_abi_value = &values[0]; - let code_abi_value = &values[1]; - - if &*update_id_abi_value.name != "updateId" - && update_id_abi_value.value.get_type() != AbiType::Uint(64) - { - return Err(anyhow!("Invalid updateId")); - } - let AbiValue::Uint(_, update_id) = &update_id_abi_value.value else { - return Err(anyhow!("Invalid updateId")); - }; - - if &*code_abi_value.name != "code" - && code_abi_value.value.get_type() != AbiType::Optional(Arc::new(AbiType::Cell)) - { - return Err(anyhow!("Invalid code")); - } - let AbiValue::Optional(_, code) = &code_abi_value.value else { - return Err(anyhow!("Invalid code")); - }; - - let code = if let Some(code) = code { - let AbiValue::Cell(code) = &**code else { - return Err(anyhow!("Invalid code")); - }; - Some(code.clone()) - } else { - None - }; - - Ok(ExecuteUpdateParams { - update_id: update_id.to_u64().unwrap(), - code, - }) - } -} pub fn execute_update() -> &'static Function { declare_function! { abi: v2_3, header: [pubkey, time, expire], name: "executeUpdate", - inputs: ExecuteUpdateParams::abi_type(), + inputs: ExecuteUpdateParams::abi_type_plain(), outputs: vec![] as Vec, } } From f90fc10b60b7e93972ea5013aff055575face5c7 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Tue, 10 Feb 2026 12:06:15 +0300 Subject: [PATCH 28/59] fix mnemonic --- Cargo.lock | 5 ++ Cargo.toml | 5 ++ src/client/ton/mod.rs | 17 +++-- src/utils/mnemonic/labs.rs | 75 ++++++++++++++++++++++ src/utils/mnemonic/legacy.rs | 55 ++++++++++++++++ src/utils/{mnemonic.rs => mnemonic/mod.rs} | 17 +++-- src/utils/mod.rs | 17 ++--- src/utils/multisig/models.rs | 26 ++++---- src/utils/ton_wallet/multisig.rs | 5 +- src/utils/wallets/multisig.rs | 4 +- src/utils/wallets/multisig2.rs | 8 +-- 11 files changed, 186 insertions(+), 48 deletions(-) create mode 100644 src/utils/mnemonic/labs.rs create mode 100644 src/utils/mnemonic/legacy.rs rename src/utils/{mnemonic.rs => mnemonic/mod.rs} (94%) diff --git a/Cargo.lock b/Cargo.lock index 9aabe14..73dc6f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5168,6 +5168,7 @@ dependencies = [ "num-traits", "opg", "parking_lot", + "pbkdf2 0.12.2", "pomfrit", "rand 0.8.5", "rayon", @@ -5178,12 +5179,16 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "sha2 0.10.9", + "slip10_ed25519", "sqlx", "strum", "strum_macros", "sysinfo 0.30.13", "thiserror 1.0.69", "tikv-jemallocator", + "tiny-bip39", + "tiny-hderive", "tokio", "tokio-util", "ton_abi", diff --git a/Cargo.toml b/Cargo.toml index 47ada30..40e39a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ num-bigint = "0.4" num-traits = "0.2" opg = { version = "0.2", features = ["uuid"] } parking_lot = "0.12.0" +pbkdf2 = { version = "0.12.2" } pomfrit = "0.1" rand = "0.8" rayon = "1.10" @@ -44,10 +45,14 @@ schemars = { version = "0.8.13", features = ["chrono", "bigdecimal04", "uuid1"] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.9.4" +sha2 = { version = "0.10.8" } +slip10_ed25519 = "0.1.3" sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres", "uuid", "bigdecimal", "chrono", "json"] } strum = "0.24.1" strum_macros = "0.24.1" thiserror = "1.0" +tiny-hderive = { git = "https://github.com/broxus/tiny-hderive.git" } +tiny-bip39 = { git = "https://github.com/broxus/tiny-bip39.git", default-features = false } tokio = { version = "1", features = ["sync", "fs", "rt-multi-thread", "macros", "signal", "parking_lot"] } tokio-util = "0.7" tower = { version = "0.5", features = ["limit"] } diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 85da57e..536e768 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -20,6 +20,7 @@ use crate::prelude::*; use crate::services::*; use crate::sqlx_client::*; use crate::ton_core::*; +use crate::utils::mnemonic::{derive_from_phrase, generate_key, Bip39MnemonicData, MnemonicType}; use crate::utils::ton_wallet::multisig::DeployParams; use crate::utils::ton_wallet::MultisigType; use crate::utils::*; @@ -62,14 +63,12 @@ impl TonClient { } pub async fn create_address(&self, payload: CreateAddress) -> Result { - let generated_key = nekoton::crypto::generate_key(nekoton::crypto::MnemonicType::Bip39( - nekoton::crypto::Bip39MnemonicData::labs_old(0), - )); + let generated_key = generate_key(MnemonicType::Bip39(Bip39MnemonicData::labs_old(0))); - let Keypair { public, secret } = nekoton::crypto::derive_from_phrase( - &generated_key.words.join(" "), - generated_key.account_type, - )?; + let signing_key = + derive_from_phrase(&generated_key.words.join(" "), generated_key.account_type)?; + + let public = signing_key.verifying_key(); let workchain_id = payload.workchain_id.unwrap_or_default(); let account_type = payload.account_type.unwrap_or_default(); @@ -120,10 +119,10 @@ impl TonClient { let mut custodians = Vec::with_capacity(public_keys.len()); for key in public_keys { - let mut key = [0u8; 32]; let decoded_key = hex::decode(key).map_err(|_| { TonServiceError::WrongInput("Invalid custodian".to_string()) })?; + let mut key = [0u8; 32]; key.copy_from_slice(&decoded_key); custodians.push(VerifyingKey::from_bytes(&key).map_err(|_| { @@ -151,7 +150,7 @@ impl TonClient { hex: address.address.to_string(), base64url: address.display_base64_url(true).to_string(), public_key: public.to_bytes().to_vec(), - private_key: secret.to_bytes().to_vec(), + private_key: signing_key.to_bytes().to_vec(), account_type, custodians, confirmations, diff --git a/src/utils/mnemonic/labs.rs b/src/utils/mnemonic/labs.rs new file mode 100644 index 0000000..c4ecf4b --- /dev/null +++ b/src/utils/mnemonic/labs.rs @@ -0,0 +1,75 @@ +use std::convert::TryInto; + +use anyhow::Result; +use nekoton_utils::TrustMe; + +use super::{Bip39MnemonicData, LANGUAGE}; + +pub fn derive_master_key(phrase: &str) -> Result<[u8; 64]> { + let mnemonic = bip39::Mnemonic::from_phrase(phrase, LANGUAGE)?; + let hd = bip39::Seed::new(&mnemonic, ""); + Ok(hd.as_bytes().try_into().trust_me()) +} + +pub fn derive_from_phrase( + phrase: &str, + mnemonic_data: Bip39MnemonicData, +) -> Result { + let mnemonic = bip39::Mnemonic::from_phrase(phrase, LANGUAGE)?; + let hd = bip39::Seed::new(&mnemonic, ""); + let seed_bytes = hd.as_bytes(); + + let account_id = mnemonic_data.account_id; + let derived = mnemonic_data.path.derive(seed_bytes, account_id)?; + + Ok(ed25519_dalek::SigningKey::from_bytes(&derived)) +} + +#[cfg(test)] +mod tests { + use crate::utils::mnemonic::{Bip39Entropy, Bip39Path}; + + use super::*; + + #[test] + fn invalid_bip39_phrase() { + let key = derive_from_phrase( + "pioneer fever hazard scam install wise reform corn bubble leisure amazing note", + Bip39MnemonicData { + account_id: 0, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + }, + ); + assert!(key.is_err()); + } + + #[test] + fn correct_bip39_derive() { + let signing_key = derive_from_phrase( + "pioneer fever hazard scan install wise reform corn bubble leisure amazing note", + Bip39MnemonicData { + account_id: 0, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + }, + ) + .unwrap(); + + let key_hex = + hex::decode("e371ef1d7266fc47b30d49dc886861598f09e2e6294d7f0520fe9aa460114e51") + .unwrap(); + let mut key = [0u8; 32]; + key.copy_from_slice(&key_hex); + + let target_secret = ed25519_dalek::SigningKey::from_bytes(&key); + + assert_eq!(signing_key.as_bytes(), target_secret.as_bytes()) + } + + #[test] + fn master_key_derive() { + let ph = "pioneer fever hazard scan install wise reform corn bubble leisure amazing note"; + derive_master_key(ph).unwrap(); + } +} diff --git a/src/utils/mnemonic/legacy.rs b/src/utils/mnemonic/legacy.rs new file mode 100644 index 0000000..9367975 --- /dev/null +++ b/src/utils/mnemonic/legacy.rs @@ -0,0 +1,55 @@ +use anyhow::Error; +use pbkdf2::{ + hmac::{Hmac, Mac}, + pbkdf2_hmac, +}; + +use super::LANGUAGE; + +pub fn derive_from_phrase(phrase: &str) -> Result { + const PBKDF_ITERATIONS: u32 = 100_000; + const SALT: &[u8] = b"TON default seed"; + + let wordmap = LANGUAGE.wordmap(); + let mut word_count = 0; + for word in phrase.split_whitespace() { + word_count += 1; + if word_count > 24 { + anyhow::bail!("Expected 24 words") + } + + wordmap.get_bits(word)?; + } + if word_count != 24 { + anyhow::bail!("Expected 24 words") + } + + let password = Hmac::::new_from_slice(phrase.as_bytes()) + .unwrap() + .finalize() + .into_bytes(); + + let mut res = [0; 512 / 8]; + pbkdf2_hmac::(&password, SALT, PBKDF_ITERATIONS, &mut res); + Ok(ed25519_dalek::SigningKey::from_bytes( + &res[0..32].try_into().unwrap(), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn correct_legacy_derive() { + let keypair = derive_from_phrase("unaware face erupt ceiling frost shiver crumble know party before brisk skirt fence boat powder copy plastic until butter fluid property concert say verify").unwrap(); + let expected = "o0kpHL39KRq0KX11zZ0/sCwJL66t+gA4vnfuwBjhAWU="; + let pub_expecteed = "lHW4ZS8QvCHcgR4uChD7QJWU2kf5JRMtUnZ2p1GSZjg="; + assert_eq!( + base64::encode(keypair.verifying_key().as_bytes()), + pub_expecteed + ); + let got = base64::encode(keypair.as_bytes()); + assert_eq!(got, expected); + } +} diff --git a/src/utils/mnemonic.rs b/src/utils/mnemonic/mod.rs similarity index 94% rename from src/utils/mnemonic.rs rename to src/utils/mnemonic/mod.rs index d6cf220..9e9cf73 100644 --- a/src/utils/mnemonic.rs +++ b/src/utils/mnemonic/mod.rs @@ -6,6 +6,9 @@ use sha2::Digest; use slip10_ed25519::derive_ed25519_private_key; use tiny_hderive::bip32::ExtendedPrivKey; +pub(super) mod labs; +pub(super) mod legacy; + const LANGUAGE: bip39::Language = bip39::Language::English; #[derive(Serialize, Copy, Clone, Debug, Eq, PartialEq)] @@ -136,7 +139,10 @@ pub struct GeneratedKey { pub account_type: MnemonicType, } -pub fn derive_from_phrase(phrase: &str, mnemonic_type: MnemonicType) -> Result { +pub fn derive_from_phrase( + phrase: &str, + mnemonic_type: MnemonicType, +) -> Result { match mnemonic_type { MnemonicType::Legacy => legacy::derive_from_phrase(phrase), MnemonicType::Bip39(data) => labs::derive_from_phrase(phrase, data), @@ -184,8 +190,9 @@ pub fn generate_key(account_type: MnemonicType) -> GeneratedKey { mod tests { use serde::Deserialize; - use crate::crypto::mnemonic::LANGUAGE; - use crate::crypto::{Bip39Entropy, Bip39MnemonicData, Bip39Path, MnemonicType}; + use crate::utils::mnemonic::{ + Bip39Entropy, Bip39MnemonicData, Bip39Path, MnemonicType, LANGUAGE, + }; #[test] fn mnemonic_type_deserialize() { @@ -228,8 +235,8 @@ mod tests { let derived = Bip39Path::Ton.derive(seed_bytes, 0).unwrap(); - let secret = ed25519_dalek::SecretKey::from_bytes(&derived).unwrap(); - let public = ed25519_dalek::PublicKey::from(&secret); + let secret = ed25519_dalek::SigningKey::from_bytes(&derived); + let public = secret.verifying_key(); assert_eq!( hex::encode(public.to_bytes()), diff --git a/src/utils/mod.rs b/src/utils/mod.rs index db19e26..9991770 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -188,17 +188,15 @@ pub struct ContractCall { } pub trait IntoAbiPlain: IntoAbi { - - fn into_abi_plain(self) -> Vec + fn into_abi_plain(self) -> Vec where - Self: Sized - { + Self: Sized, + { let tuple = self.into_abi(); match tuple { AbiValue::Tuple(tuple) => tuple, _ => vec![], } - } fn as_abi_plain(&self) -> Vec { let tuple = self.as_abi(); @@ -217,10 +215,9 @@ pub trait FromAbiPlain: FromAbi { pub trait WithAbiTypePlain: WithAbiType { fn abi_type_plain() -> Vec { - match Self::abi_type() - { - AbiType::Tuple(tuple) => tuple.into_iter().cloned().collect(), - _ => vec![], + match Self::abi_type() { + AbiType::Tuple(tuple) => tuple.into_iter().cloned().collect(), + _ => vec![], } } -} \ No newline at end of file +} diff --git a/src/utils/multisig/models.rs b/src/utils/multisig/models.rs index 3497bd5..ef39ab1 100644 --- a/src/utils/multisig/models.rs +++ b/src/utils/multisig/models.rs @@ -8,7 +8,8 @@ use tycho_types::{ }; use crate::utils::{ - ContractCall, FromAbiPlain, InputMessage, IntoAbiPlain, serde_address, serde_cell, serde_string, ton_wallet::multisig::UnpackerError, wallets::multisig2 + serde_address, serde_cell, serde_string, ton_wallet::multisig::UnpackerError, + wallets::multisig2, ContractCall, FromAbiPlain, InputMessage, IntoAbiPlain, }; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -33,16 +34,11 @@ pub struct MultisigConfirmTransaction { impl IntoAbiPlain for MultisigConfirmTransaction { fn into_abi_plain(self) -> Vec { - vec![ - AbiValue::Uint(64,self.transaction_id.into()).named("transactionId") - ] + vec![AbiValue::Uint(64, self.transaction_id.into()).named("transactionId")] } fn as_abi_plain(&self) -> Vec { - vec![ - AbiValue::Uint(64,self.transaction_id.into()).named("transactionId") - ] + vec![AbiValue::Uint(64, self.transaction_id.into()).named("transactionId")] } - } impl FromAbiPlain for MultisigConfirmTransaction { fn from_abi_plain(value: Vec) -> anyhow::Result { @@ -52,7 +48,9 @@ impl FromAbiPlain for MultisigConfirmTransaction { Ok(MultisigConfirmTransaction { custodian: Default::default(), - transaction_id: transaction_id.to_u64().ok_or(anyhow!("Invalid transaction id"))?, + transaction_id: transaction_id + .to_u64() + .ok_or(anyhow!("Invalid transaction id"))?, }) } } @@ -178,8 +176,8 @@ impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { type Error = UnpackerError; fn try_from((custodian, value): (HashBytes, InputMessage)) -> Result { - let output = - MultisigConfirmTransaction::from_abi_plain(value.0).map_err(|_| UnpackerError::InvalidAbi)?; + let output = MultisigConfirmTransaction::from_abi_plain(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; Ok(Self { custodian, transaction_id: output.transaction_id, @@ -187,7 +185,6 @@ impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { } } - #[derive(Clone, Debug, FromAbi)] struct MultisigSubmitTransactionInput { dest: StdAddr, @@ -199,7 +196,6 @@ struct MultisigSubmitTransactionInput { impl FromAbiPlain for MultisigSubmitTransactionInput {} - #[derive(Clone, Debug, FromAbi)] struct MultisigSubmitTransactionOutput { trans_id: u64, @@ -232,8 +228,8 @@ impl TryFrom for MultisigSendTransaction { type Error = UnpackerError; fn try_from(value: InputMessage) -> Result { - let input = - MultisigSendTransaction::from_abi_plain(value.0).map_err(|_| UnpackerError::InvalidAbi)?; + let input = MultisigSendTransaction::from_abi_plain(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; Ok(Self { dest: input.dest, diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index 004b9bc..cf75bb9 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -11,7 +11,10 @@ use tycho_types::{ }; use tycho_util::time::Clock; -use crate::utils::{IntoAbiPlain, ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}}; +use crate::utils::{ + ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}, + IntoAbiPlain, +}; use super::{Gift, TonWalletDetails}; diff --git a/src/utils/wallets/multisig.rs b/src/utils/wallets/multisig.rs index 4acedaa..505f1a5 100644 --- a/src/utils/wallets/multisig.rs +++ b/src/utils/wallets/multisig.rs @@ -122,7 +122,7 @@ pub mod safe_multisig { use super::*; - #[derive(Debug, Clone, Copy, WithAbiType )] + #[derive(Debug, Clone, Copy, WithAbiType)] pub struct SafeMultisigParams { pub max_queued_transactions: u8, pub max_custodian_count: u8, @@ -149,7 +149,7 @@ pub mod set_code_multisig { use super::*; - #[derive(Debug, Clone, Copy, WithAbiType )] + #[derive(Debug, Clone, Copy, WithAbiType)] pub struct SetCodeMultisigParams { pub max_queued_transactions: u8, pub max_custodian_count: u8, diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs index c6cc904..3742f51 100644 --- a/src/utils/wallets/multisig2.rs +++ b/src/utils/wallets/multisig2.rs @@ -1,14 +1,12 @@ use std::sync::Arc; use tycho_types::{ - abi::{ - AbiType, FromAbi, Function, IntoAbi, NamedAbiType, WithAbiType, - }, + abi::{AbiType, FromAbi, Function, IntoAbi, NamedAbiType, WithAbiType}, cell::{Cell, HashBytes}, models::StdAddr, }; -use crate::utils::{FromAbiPlain, IntoAbiPlain, WithAbiTypePlain, declare_function}; +use crate::utils::{declare_function, FromAbiPlain, IntoAbiPlain, WithAbiTypePlain}; pub fn constructor() -> &'static Function { declare_function! { @@ -128,7 +126,6 @@ impl IntoAbiPlain for SubmitUpdateParams {} impl FromAbiPlain for SubmitUpdateParams {} impl WithAbiTypePlain for SubmitUpdateParams {} - #[derive(Debug, Copy, Clone, WithAbiType, FromAbi, IntoAbi)] pub struct SubmitUpdateOutput { pub update_id: u64, @@ -174,7 +171,6 @@ impl IntoAbiPlain for ExecuteUpdateParams {} impl FromAbiPlain for ExecuteUpdateParams {} impl WithAbiTypePlain for ExecuteUpdateParams {} - pub fn execute_update() -> &'static Function { declare_function! { abi: v2_3, From fc796f6097f1f6acf762b9dc53e430b333647b3b Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 11 Feb 2026 16:21:43 +0300 Subject: [PATCH 29/59] fix deps --- Cargo.lock | 66 +++++++++++++++++------------- Cargo.toml | 17 ++++---- src/api/controllers/misc.rs | 11 +++-- src/api/requests/transactions.rs | 8 ++-- src/client/ton/mod.rs | 37 ++++++++++++++--- src/services/storage.rs | 21 ++++++---- src/ton_core/mod.rs | 3 ++ src/ton_core/ton_subscriber/mod.rs | 24 +++++++++++ src/utils/existing_contract.rs | 17 ++++---- 9 files changed, 136 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73dc6f7..12cc1bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4817,8 +4817,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tycho-block-util" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "105be4705dc8ba4983dec0d5164089920557567bc1fbeb4bc818daa4110a2a38" dependencies = [ "anyhow", "arc-swap", @@ -4836,22 +4837,25 @@ dependencies = [ [[package]] name = "tycho-build-info" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34bcd667b76ce41c8faded67e30a2f5ab5335dc8dec24a7aad3e6a9c0ce6c5ca" dependencies = [ "anyhow", ] [[package]] name = "tycho-core" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e22a046a4fecfeb0a79ed9d9f686ed065844a84ce5322e61f096eecb34e891" dependencies = [ "ahash 0.8.12", "anyhow", "arc-swap", "async-trait", "bitflags", + "blake3", "bumpalo", "bumpalo-herd", "bytes", @@ -4907,9 +4911,9 @@ dependencies = [ [[package]] name = "tycho-executor" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "148f0edab661a199f2be88448f8a463ca107a7db370e6abf58ac8b2116ec24e0" +checksum = "737e032f9c6b68cdfe1a3433b151bca08dd81f66745a875e9d981fc6f5fdb2fb" dependencies = [ "ahash 0.8.12", "anyhow", @@ -4921,8 +4925,9 @@ dependencies = [ [[package]] name = "tycho-network" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f51d4db5f49a9ab6951139cbeb384602d6ad1edbb1cb453a4a35c5c97cd53a6" dependencies = [ "ahash 0.8.12", "anyhow", @@ -4961,8 +4966,9 @@ dependencies = [ [[package]] name = "tycho-storage" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51fa9c12bf73ebb58cbbac15412a2fbdcf0d31a2c892658d3bfdb1aed8b251f" dependencies = [ "anyhow", "arc-swap", @@ -4984,8 +4990,9 @@ dependencies = [ [[package]] name = "tycho-storage-traits" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3449aeb0ad7213e38bb8047498a8af265e2ff7682922bac518af83f20d9ebfec" dependencies = [ "bytes", "smallvec", @@ -4994,9 +5001,8 @@ dependencies = [ [[package]] name = "tycho-types" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ccb37e250becb5c4b827536644c777c247b41ac6c9ddd30902ff1db29818a7" +version = "0.3.2" +source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1#f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" dependencies = [ "ahash 0.8.12", "anyhow", @@ -5014,6 +5020,8 @@ dependencies = [ "rayon", "scc", "serde", + "serde_json", + "serde_path_to_error", "sha2 0.10.9", "smallvec", "thiserror 2.0.17", @@ -5027,8 +5035,7 @@ dependencies = [ [[package]] name = "tycho-types-abi-proc" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c813c08a03554252747f9e5e88485d9af4c30077394a1c3bb6d774ddca56b07" +source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1#f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" dependencies = [ "anyhow", "proc-macro2", @@ -5039,8 +5046,7 @@ dependencies = [ [[package]] name = "tycho-types-proc" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad05cf4ab89631f8c11d85c3aa80f781502440f75361d251f866e0d76ae9d31" +source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1#f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" dependencies = [ "proc-macro2", "quote", @@ -5049,8 +5055,9 @@ dependencies = [ [[package]] name = "tycho-util" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e9f4dfca5d4d29d678e2933b0d08f8b84635e1d43f671a71c1419051dce0f8" dependencies = [ "ahash 0.8.12", "anyhow", @@ -5090,8 +5097,9 @@ dependencies = [ [[package]] name = "tycho-util-proc" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723c91dccdb15a705b0448705f631c6e32298931013c054e54f6ffd9cb592341" dependencies = [ "proc-macro2", "quote", @@ -5100,9 +5108,9 @@ dependencies = [ [[package]] name = "tycho-vm" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "013cf249ea5a32b38050adfc8cbca471da017c0c03292e7462665859638226ac" +checksum = "b7b4ae1dade385e5fbdca14e0439b1b7b518be19076324669785f4ef04d9e093" dependencies = [ "ahash 0.8.12", "anyhow", @@ -5122,9 +5130,9 @@ dependencies = [ [[package]] name = "tycho-vm-proc" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d448e5c9526dcfdd2d3f63d9e13de2a207e1aadf906ea4d1e61e45f0aeceb3" +checksum = "e79fcef10732b88d30b15c517bbfa7f7bef240934a27f70261aacff8a8e57b3a" dependencies = [ "darling 0.21.3", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 40e39a9..e14382a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ tower-http = { version = "0.6", features = ["trace", "cors", "limit", "set-heade tower-service = "0.3.3" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tycho-types = { version = "0.3.1", features = ["tycho", "stats", "serde", "abi"] } +tycho-types = { git = "https://github.com/broxus/tycho-types.git", rev = "f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" , features = ["tycho", "stats", "serde", "abi"] } uuid = { version = "1.1", features = ["v4", "serde"] } @@ -71,13 +71,13 @@ tikv-jemallocator = { version = "0.6.0", features = [ "background_threads", ] } -tycho-block-util = { git = "https://github.com/broxus/tycho.git", rev = "bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" } -tycho-core = { git = "https://github.com/broxus/tycho.git", rev = "bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e", features = ["cli"] } -tycho-storage = { git = "https://github.com/broxus/tycho.git", rev = "bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" } -tycho-util = { git = "https://github.com/broxus/tycho.git", rev = "bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e", features = ["cli"] } +tycho-block-util = { version = "0.3.6" } +tycho-core = { version = "0.3.6", features = ["cli"] } +tycho-storage = { version = "0.3.6" } +tycho-util = { version = "0.3.6", features = ["cli"] } -tycho-vm = "0.3.0" -tycho-executor = "0.3.0" +tycho-vm = "0.3.2" +tycho-executor = "0.3.2" # TON specific dependencies ton_block = { git = "https://github.com/broxus/ton-labs-block" } @@ -94,3 +94,6 @@ sysinfo = "0.30.13" [features] default = [] + +[patch.crates-io] +tycho-types = { git = "https://github.com/broxus/tycho-types.git", rev = "f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" , features = ["tycho", "stats", "serde", "abi"] } diff --git a/src/api/controllers/misc.rs b/src/api/controllers/misc.rs index 234a25c..e138e9f 100644 --- a/src/api/controllers/misc.rs +++ b/src/api/controllers/misc.rs @@ -1,7 +1,10 @@ +use std::str::FromStr; + use axum::extract::State; use axum::Json; use metrics::{histogram, increment_counter}; use tokio::time::Instant; +use tycho_types::cell::HashBytes; use uuid::Uuid; use crate::api::controllers::*; @@ -95,14 +98,14 @@ pub async fn post_prepare_generic_message( ) .await?; - ctx.memory_storage.add_message(unsigned_message); + let message_hash = ctx.memory_storage.add_message(unsigned_message); let elapsed = start.elapsed(); histogram!("execution_time_seconds", elapsed, "method" => "prepareGenericMessage"); increment_counter!("requests_processed", "method" => "prepareGenericMessage"); Ok(Json(UnsignedMessageHashResponse { - unsigned_message_hash: hex::encode(unsigned_message.hash()), + unsigned_message_hash: message_hash.to_string(), })) } @@ -111,8 +114,10 @@ pub async fn post_send_signed_message( Json(req): Json, ) -> Result> { let start = Instant::now(); + let message_hash = HashBytes::from_str(&req.hash) + .map_err(|_| ControllersError::WrongInput("Bad hash format".to_string()))?; - let res = match ctx.memory_storage.get_message(&req.hash) { + let res = match ctx.memory_storage.get_message(&message_hash) { Some(message) => { let expire_at = message.expire_at(); let signature: [u8; 64] = hex::decode(req.signature) diff --git a/src/api/requests/transactions.rs b/src/api/requests/transactions.rs index 302ad7c..0b36e00 100644 --- a/src/api/requests/transactions.rs +++ b/src/api/requests/transactions.rs @@ -127,7 +127,7 @@ impl From for TokenTransactionSend { notify_receiver: c.notify_receiver.unwrap_or(false), fee: c .fee - .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).trust_me()), + .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).unwrap()), payload: c.payload, } } @@ -156,7 +156,7 @@ impl From for TokenTransactionBurn { value: c.value, fee: c .fee - .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).trust_me()), + .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).unwrap()), } } } @@ -187,10 +187,10 @@ impl From for TokenTransactionMint { notify: c.notify.unwrap_or(false), deploy_wallet_value: c .deploy_wallet_value - .unwrap_or_else(|| BigDecimal::from_u64(DEPLOY_TOKEN_VALUE).trust_me()), + .unwrap_or_else(|| BigDecimal::from_u64(DEPLOY_TOKEN_VALUE).unwrap()), fee: c .fee - .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).trust_me()), + .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).unwrap()), } } } diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 536e768..9506179 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -10,7 +10,7 @@ use tokio::sync::oneshot; use tycho_types::abi::{Function, NamedAbiValue, UnsignedExternalMessage}; use tycho_types::boc::Boc; use tycho_types::cell::{CellBuilder, HashBytes}; -use tycho_types::models::{OwnedMessage, StdAddr}; +use tycho_types::models::{GlobalCapabilities, OwnedMessage, SignatureContext, StdAddr}; use tycho_util::time::now_sec; use uuid::Uuid; @@ -233,7 +233,12 @@ impl TonClient { let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); - let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), + }; + + let owned_message = unsigned_message.sign(&key_pair, context)?; let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let hash = cell_builder.repr_hash(); @@ -421,7 +426,12 @@ impl TonClient { let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); - let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), + }; + + let owned_message = unsigned_message.sign(&key_pair, context)?; let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let message_hash = cell_builder.repr_hash(); @@ -474,7 +484,12 @@ impl TonClient { let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); - let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), + }; + + let owned_message = unsigned_message.sign(&key_pair, context)?; let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let message_hash = cell_builder.repr_hash(); @@ -836,7 +851,12 @@ impl TonClient { let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); let expire_at = unsigned_message.expire_at(); - let owned_message = unsigned_message.sign(&key_pair, self.ton_core.signature_id())?; + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), + }; + + let owned_message = unsigned_message.sign(&key_pair, context)?; Ok((owned_message, expire_at)) } @@ -1128,7 +1148,12 @@ fn build_token_transaction( let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); - let owned_message = unsigned_message.sign(&key_pair, ton_core.signature_id())?; + let context = SignatureContext { + global_id: ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(ton_core.capabilities()), + }; + + let owned_message = unsigned_message.sign(&key_pair, context)?; let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let hash = cell_builder.repr_hash(); diff --git a/src/services/storage.rs b/src/services/storage.rs index 943a4a0..7822bbf 100644 --- a/src/services/storage.rs +++ b/src/services/storage.rs @@ -1,27 +1,30 @@ -use tycho_types::{abi::UnsignedExternalMessage, cell::CellBuilder}; +use tycho_types::{ + abi::UnsignedExternalMessage, + cell::{CellBuilder, HashBytes}, +}; use tycho_util::time::now_sec; use crate::utils::FxDashMap; #[derive(Default)] pub struct StorageHandler { - message_collection: FxDashMap, + message_collection: FxDashMap, } impl StorageHandler { - pub fn add_message(&self, message: UnsignedExternalMessage) -> String { - let cell_builder = CellBuilder::build_from(message.without_signature().unwrap()).unwrap(); + pub fn add_message(&self, message: UnsignedExternalMessage) -> HashBytes { + let cell_builder = + CellBuilder::build_from(message.clone().without_signature().unwrap()).unwrap(); let message_hash = *cell_builder.repr_hash(); - let key = message_hash.to_string(); - self.message_collection.insert(key.clone(), message); - key + self.message_collection.insert(message_hash, message); + message_hash } - pub fn get_message(&self, hash: &str) -> Option { + pub fn get_message(&self, hash: &HashBytes) -> Option { let now = now_sec(); self.message_collection .retain(|_, message| message.expire_at() > now); - let message = self.message_collection.get(hash).map(|x| x.value()); + let message = self.message_collection.get(hash).map(|x| x.value().clone()); message } } diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index ead3f70..2f763e8 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -106,6 +106,9 @@ impl TonCore { pub fn signature_id(&self) -> Option { self.context.ton_subscriber.signature_id() } + pub fn capabilities(&self) -> u64 { + self.context.ton_subscriber.capabilities() + } } pub struct TonCoreContext { diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index 041bb90..9f218e7 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -25,6 +25,7 @@ pub struct TonSubscriber { // tip block timestamp current_utime: AtomicU32, signature_id: SignatureId, + capabilities: Capabilities, state_subscriptions: RwLock>, token_subscription: RwLock>, sc_accounts: RwLock>, @@ -48,6 +49,7 @@ impl TonSubscriber { Default::default(), )), messages_queue, + capabilities: Capabilities::default(), }) } @@ -62,6 +64,7 @@ impl TonSubscriber { pub async fn start(self: &Arc, capabilities: u64, global_id: i32) -> Result<()> { self.update_signature_id(capabilities, global_id)?; + self.update_capabilies(capabilities)?; Ok(()) } @@ -73,6 +76,10 @@ impl TonSubscriber { self.signature_id.load() } + pub fn capabilities(&self) -> u64 { + self.capabilities.0.load(Ordering::Acquire) + } + pub fn add_transactions_subscription(&self, accounts: I, subscription: &Arc) where I: IntoIterator, @@ -156,6 +163,16 @@ impl TonSubscriber { } }, ); + if block_info.key_block { + let extra = block.load_extra()?; + let custom = extra.load_custom()?; + if let Some(custom) = custom { + if let Some(config) = custom.config { + let global_version = config.get_global_version()?; + self.update_capabilies(global_version.capabilities.into_inner())?; + } + } + } Ok(()) } @@ -289,6 +306,10 @@ impl TonSubscriber { self.signature_id.store(capabilities, global_id); Ok(()) } + fn update_capabilies(&self, capabilities: u64) -> Result<()> { + self.capabilities.0.store(capabilities, Ordering::Release); + Ok(()) + } } impl TonSubscriber { @@ -643,6 +664,9 @@ impl ShardAccountsMapExt for FxHashMap { } } +#[derive(Default)] +struct Capabilities(pub AtomicU64); + #[derive(Default)] struct SignatureId(AtomicU64); diff --git a/src/utils/existing_contract.rs b/src/utils/existing_contract.rs index 9ab41a1..5b180ed 100644 --- a/src/utils/existing_contract.rs +++ b/src/utils/existing_contract.rs @@ -18,17 +18,14 @@ pub trait ExistingContractExt { impl ExistingContractExt for ExistingContract { fn from_shard_account(shard_account: &ShardAccount) -> Result> { - Ok(match shard_account.read_account()? { - Account::Account(account) => Some(Self { + if let Some(account) = shard_account.load_account()? { + Ok(Some(Self { account, - timings: GenTimings::Unknown, - last_transaction_id: LastTransactionId::Exact(TransactionId { - lt: shard_account.last_trans_lt(), - hash: *shard_account.last_trans_hash(), - }), - }), - Account::AccountNone => None, - }) + last_transaction_hash: shard_account.last_trans_hash, + })) + } else { + Ok(None) + } } fn from_shard_account_opt(shard_account: &Option) -> Result> { From e186eada2428426c79c44b1cb0a03d27cee10eb6 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 11 Feb 2026 17:06:47 +0300 Subject: [PATCH 30/59] fix services --- src/api/controllers/misc.rs | 17 ++++- src/api/requests/misc.rs | 6 +- src/client/ton/mod.rs | 4 +- src/models/transactions.rs | 4 +- src/services/ton.rs | 122 +++++++++++++++++++++--------------- 5 files changed, 96 insertions(+), 57 deletions(-) diff --git a/src/api/controllers/misc.rs b/src/api/controllers/misc.rs index e138e9f..642b2f8 100644 --- a/src/api/controllers/misc.rs +++ b/src/api/controllers/misc.rs @@ -4,6 +4,8 @@ use axum::extract::State; use axum::Json; use metrics::{histogram, increment_counter}; use tokio::time::Instant; +use tycho_types::abi::SerializeAbiValue; +use tycho_types::abi::SerializeAbiValueParams; use tycho_types::cell::HashBytes; use uuid::Uuid; @@ -34,7 +36,20 @@ pub async fn post_read_contract( req.responsible.unwrap_or_default(), ) .await - .map(|value| ReadContractResponse { object: value })?; + .map(|values| { + let params = SerializeAbiValueParams::default(); + let mut result = Vec::new(); + for value in values { + if let Some(json) = + serde_json::to_value(&SerializeAbiValue::with_params(&value, params)).ok() + { + result.push(json); + } + } + ReadContractResponse { + object: serde_json::Value::from(result), + } + })?; let elapsed = start.elapsed(); histogram!("execution_time_seconds", elapsed, "method" => "readContract"); diff --git a/src/api/requests/misc.rs b/src/api/requests/misc.rs index f45e535..18098ea 100644 --- a/src/api/requests/misc.rs +++ b/src/api/requests/misc.rs @@ -30,16 +30,16 @@ pub struct FunctionDetailsDTO { #[derive(Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct InputParamDTO { + pub value: String, #[schemars(schema_with = "any_schema")] - pub param: NamedAbiType, - pub value: serde_json::Value, + pub abi_type: NamedAbiType, } impl From for InputParam { fn from(i: InputParamDTO) -> Self { Self { - param: i.param, value: i.value, + abi_type: i.abi_type, } } } diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 9506179..fe3bb90 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -7,7 +7,7 @@ use ed25519_dalek::VerifyingKey; use num_bigint::BigUint; use num_traits::FromPrimitive; use tokio::sync::oneshot; -use tycho_types::abi::{Function, NamedAbiValue, UnsignedExternalMessage}; +use tycho_types::abi::{AbiValue, Function, NamedAbiValue, UnsignedExternalMessage}; use tycho_types::boc::Boc; use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{GlobalCapabilities, OwnedMessage, SignatureContext, StdAddr}; @@ -798,7 +798,7 @@ impl TonClient { function: Function, input: &[NamedAbiValue], responsible: bool, - ) -> anyhow::Result> { + ) -> anyhow::Result>> { let state = match self.ton_core.get_contract_state(&contract_address) { Ok(a) => a, Err(e) => { diff --git a/src/models/transactions.rs b/src/models/transactions.rs index ccab5d0..4a80eb4 100644 --- a/src/models/transactions.rs +++ b/src/models/transactions.rs @@ -181,8 +181,8 @@ pub struct SentTransaction { #[derive(Clone, Deserialize, Debug)] pub struct InputParam { - pub param: NamedAbiType, - pub value: serde_json::Value, + pub value: String, + pub abi_type: NamedAbiType, } #[derive(Clone, Debug, Deserialize)] diff --git a/src/services/ton.rs b/src/services/ton.rs index 25f05a1..770fb22 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -7,8 +7,10 @@ use bigdecimal::BigDecimal; use chrono::Utc; use serde_json::Value; use tycho_types::abi::{ - AbiHeaderType, AbiVersion, Function, NamedAbiType, NamedAbiValue, UnsignedExternalMessage, + AbiHeaderType, AbiValue, AbiVersion, Function, NamedAbiType, NamedAbiValue, + UnsignedExternalMessage, }; +use tycho_types::boc::Boc; use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{OwnedMessage, StdAddr}; use uuid::Uuid; @@ -882,21 +884,22 @@ impl TonService { outputs: Vec, headers: Vec, responsible: bool, - ) -> Result { + ) -> Result, Error> { let account_addr = HashBytes::from_str(&account_addr).map_err(anyhow::Error::from)?; - let inputs = inputs + let input_params: Vec = inputs.iter().map(|x| x.abi_type.clone()).collect(); + + let inputs: Result, anyhow::Error> = inputs .into_iter() - .map(|x| NamedAbiValue { - name: x.param.name, - value: x.param.ty, + .map(|x| { + Ok(NamedAbiValue { + name: x.abi_type.name, + value: AbiValue::from_json_str(&x.value, &x.abi_type.ty) + .map_err(anyhow::Error::from)?, + }) }) .collect(); - - let input_params: Vec = inputs - .iter() - .map(|x| x.value.get_type().named(x.name.into())) - .collect(); + let inputs = inputs?; let function = Function::builder(AbiVersion::V2_2, function_name) .with_headers(headers) @@ -904,7 +907,7 @@ impl TonService { .with_outputs(outputs) .build(); - let output = match self + let tokens = match self .ton_api_client .run_local(account_addr, function, &inputs, responsible) .await? @@ -913,15 +916,9 @@ impl TonService { None => return Err(TonServiceError::ExecuteContract.into()), }; - let tokens = match output.tokens { - Some(tokens) => { - if tokens.is_empty() { - tracing::warn!("No response tokens in execution output") - } - tokens - } - None => return Err(TonServiceError::ExecuteContract.into()), - }; + if tokens.is_empty() { + tracing::warn!("No response tokens in execution output") + } Ok(tokens) } @@ -939,7 +936,7 @@ impl TonService { function_details: Option, transaction_id: Uuid, ) -> Result { - let (function, values) = match function_details { + let (function, inputs) = match function_details { Some(details) => { let function = Function::builder(AbiVersion::V2_2, details.function_name.to_string()) @@ -949,13 +946,26 @@ impl TonService { .input_params .clone() .into_iter() - .map(|x| x.value.get_type().named(x.name.into())) + .map(|x| x.abi_type) .collect::>(), ) .with_outputs(details.output_params) .build(); - (Some(function), Some(details.input_params)) + let inputs: Result, anyhow::Error> = details + .input_params + .into_iter() + .map(|x| { + Ok(NamedAbiValue { + name: x.abi_type.name, + value: AbiValue::from_json_str(&x.value, &x.abi_type.ty) + .map_err(anyhow::Error::from)?, + }) + }) + .collect(); + let inputs = inputs?; + + (Some(function), Some(inputs)) } None => (None, None), }; @@ -989,11 +999,12 @@ impl TonService { account_type, custodians, function, - values, + inputs, ) .await?; - let cell_builder = CellBuilder::build_from(owned_message).map_err(anyhow::Error::from)?; + let cell_builder = + CellBuilder::build_from(owned_message.clone()).map_err(anyhow::Error::from)?; let message_hash = *cell_builder.repr_hash(); let sent_transaction = SentTransaction { @@ -1041,7 +1052,7 @@ impl TonService { custodians: &Option, function_details: Option, ) -> Result { - let (function, values) = match function_details { + let (function, inputs) = match function_details { Some(details) => { let function = Function::builder(AbiVersion::V2_2, details.function_name.to_string()) @@ -1051,13 +1062,26 @@ impl TonService { .input_params .clone() .into_iter() - .map(|x| x.value.get_type().named(x.name.into())) + .map(|x| x.abi_type) .collect::>(), ) .with_outputs(details.output_params) .build(); - (Some(function), Some(details.input_params)) + let inputs: Result, anyhow::Error> = details + .input_params + .into_iter() + .map(|x| { + Ok(NamedAbiValue { + name: x.abi_type.name, + value: AbiValue::from_json_str(&x.value, &x.abi_type.ty) + .map_err(anyhow::Error::from)?, + }) + }) + .collect(); + let inputs = inputs?; + + (Some(function), Some(inputs)) } None => (None, None), }; @@ -1074,7 +1098,7 @@ impl TonService { account_type, custodians, function, - values, + inputs, ) .await?; @@ -1082,27 +1106,27 @@ impl TonService { } pub fn encode_tvm_cell(&self, data: Vec) -> Result { - let mut tokens: Vec = Vec::new(); - for d in data { - let token_value = ton_abi::token::Tokenizer::tokenize_parameter( - &d.param.kind, - &d.value, - &d.param.name, - )?; - let token = Token::new(&d.param.name, token_value); - tokens.push(token); - } - let initial = if tokens.is_empty() { - BuilderData::default() + let tokens: Result, anyhow::Error> = data + .into_iter() + .map(|x| { + Ok(NamedAbiValue { + name: x.abi_type.name, + value: AbiValue::from_json_str(&x.value, &x.abi_type.ty) + .map_err(anyhow::Error::from)?, + }) + }) + .collect(); + let tokens = tokens?; + + let cell = if tokens.is_empty() { + CellBuilder::default() + .build() + .map_err(anyhow::Error::from)? } else { - TokenValue::pack_values_into_chain( - tokens.as_slice(), - Default::default(), - &ABI_VERSION_2_2, - )? + NamedAbiValue::tuple_to_cell(tokens.as_slice(), AbiVersion::V2_2) + .map_err(anyhow::Error::from)? }; - let cell = initial.into_cell()?; - Ok(base64::encode(cell.write_to_bytes()?)) + Ok(Boc::encode_base64(cell)) } pub async fn send_signed_message( From 524dd16a237167372c33509cb912d2ee54b5132b Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 11 Feb 2026 17:13:22 +0300 Subject: [PATCH 31/59] remove nekoton, update nekoton tycho --- Cargo.lock | 531 ++++++++--------------------- Cargo.toml | 11 +- src/models/account_enums.rs | 10 - src/ton_core/ton_subscriber/mod.rs | 15 - src/utils/existing_contract.rs | 7 +- src/utils/mnemonic/labs.rs | 3 +- src/utils/shard_utils.rs | 12 +- src/utils/token_wallets/mod.rs | 2 +- 8 files changed, 156 insertions(+), 435 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12cc1bf..7c8be7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -723,12 +723,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "countme" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -875,19 +869,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "curve25519-dalek-ng" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.6.4", - "subtle-ng", - "zeroize", -] - [[package]] name = "darling" version = "0.20.11" @@ -1071,12 +1052,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - [[package]] name = "dunce" version = "1.0.5" @@ -1089,15 +1064,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature 1.6.4", -] - [[package]] name = "ed25519" version = "2.2.3" @@ -1105,20 +1071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature 2.2.0", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "git+https://github.com/broxus/ed25519-dalek.git#e5d68fd1490a7f6a0d473c6c1b1acef868960471" -dependencies = [ - "curve25519-dalek-ng", - "ed25519 1.5.3", - "rand 0.8.5", - "serde", - "sha2 0.9.9", - "zeroize", + "signature", ] [[package]] @@ -1128,7 +1081,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", - "ed25519 2.2.3", + "ed25519", "serde", "sha2 0.10.9", "subtle", @@ -1177,15 +1130,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" -dependencies = [ - "serde", -] - [[package]] name = "errno" version = "0.3.14" @@ -2466,140 +2410,32 @@ dependencies = [ ] [[package]] -name = "nekoton" -version = "0.13.1" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" +name = "nekoton-core" +version = "0.0.2" +source = "git+https://github.com/broxus/tycho-nekoton.git#60faf969afb88336b19b246d3c76a4f29668cda9" dependencies = [ "anyhow", "async-trait", - "base64 0.13.1", - "chacha20poly1305", - "curve25519-dalek-ng", - "downcast-rs", - "dyn-clone", - "ed25519-dalek 1.0.1", - "erased-serde", "futures-util", - "getrandom 0.2.16", - "hex", - "hmac 0.11.0", - "log", - "nekoton-abi", - "nekoton-contracts", - "nekoton-utils", - "num-bigint", - "once_cell", - "parking_lot", - "pbkdf2 0.12.2", - "quick_cache 0.4.3", - "rand 0.8.5", - "secstr", - "serde", - "serde_json", - "sha2 0.10.9", - "slip10_ed25519", - "thiserror 1.0.69", - "tiny-bip39", - "tiny-hderive", - "tokio", - "ton_abi", - "ton_block", - "ton_executor", - "ton_types", - "zeroize", -] - -[[package]] -name = "nekoton-abi" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" -dependencies = [ - "anyhow", - "base64 0.13.1", - "ed25519-dalek 1.0.1", - "hex", - "log", - "nekoton-derive", - "nekoton-utils", - "num-bigint", - "num-traits", - "once_cell", - "rustc-hash 1.1.0", - "serde", - "serde_json", - "smallvec", - "thiserror 1.0.69", - "ton_abi", - "ton_block", - "ton_executor", - "ton_types", - "ton_vm", -] - -[[package]] -name = "nekoton-contracts" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" -dependencies = [ - "anyhow", - "nekoton-abi", - "nekoton-jetton", - "nekoton-utils", - "once_cell", - "serde", - "thiserror 1.0.69", - "ton_abi", - "ton_block", - "ton_types", -] - -[[package]] -name = "nekoton-derive" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" -dependencies = [ - "either", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "nekoton-jetton" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" -dependencies = [ - "anyhow", - "lazy_static", "nekoton-utils", "num-bigint", "num-traits", + "pin-project", "serde", - "sha2 0.10.9", - "ton_abi", - "ton_block", - "ton_types", + "thiserror 2.0.17", + "tokio", + "tycho-executor 0.2.1", + "tycho-types 0.2.1", + "tycho-vm 0.2.1", ] [[package]] name = "nekoton-utils" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" +version = "0.0.2" +source = "git+https://github.com/broxus/tycho-nekoton.git#60faf969afb88336b19b246d3c76a4f29668cda9" dependencies = [ - "anyhow", - "base64 0.13.1", - "chacha20poly1305", - "ed25519-dalek 1.0.1", "hex", - "hmac 0.11.0", - "pbkdf2 0.12.2", - "secstr", "serde", - "sha2 0.10.9", - "thiserror 1.0.69", - "ton_block", - "ton_types", - "zeroize", ] [[package]] @@ -2639,20 +2475,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -2679,32 +2501,12 @@ dependencies = [ "zeroize", ] -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -2725,17 +2527,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2980,6 +2771,26 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -3109,18 +2920,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "quick_cache" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a4b807ec70346b4fac3c13ae967634237847d49871f623fe0d455403346bad4" -dependencies = [ - "ahash 0.8.12", - "equivalent", - "hashbrown 0.14.5", - "parking_lot", -] - [[package]] name = "quick_cache" version = "0.6.18" @@ -3435,7 +3234,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", - "signature 2.2.0", + "signature", "spki", "subtle", "zeroize", @@ -3604,16 +3403,6 @@ version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" -[[package]] -name = "secstr" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04f657244f605c4cf38f6de5993e8bd050c8a303f86aeabff142d5c7c113e12" -dependencies = [ - "libc", - "serde", -] - [[package]] name = "security-framework" version = "2.11.1" @@ -3818,12 +3607,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - [[package]] name = "signature" version = "2.2.0" @@ -4152,12 +3935,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "subtle-ng" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" - [[package]] name = "syn" version = "1.0.109" @@ -4547,106 +4324,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "ton_abi" -version = "2.1.0" -source = "git+https://github.com/broxus/ton-labs-abi#7d84f87a1799b727e33f9b09c8e38c764fbd5c68" -dependencies = [ - "anyhow", - "base64 0.13.1", - "byteorder", - "ed25519 1.5.3", - "ed25519-dalek 1.0.1", - "hex", - "num-bigint", - "num-traits", - "serde", - "serde_json", - "sha2 0.9.9", - "smallvec", - "thiserror 1.0.69", - "ton_block", - "ton_types", -] - -[[package]] -name = "ton_block" -version = "1.9.73" -source = "git+https://github.com/broxus/ton-labs-block#f1c3e222ee6a2b2ccf663f4f2df5e1335b95961a" -dependencies = [ - "anyhow", - "base64 0.13.1", - "crc", - "ed25519 1.5.3", - "ed25519-dalek 1.0.1", - "hex", - "log", - "num", - "num-traits", - "rand 0.8.5", - "rustc-hash 1.1.0", - "sha2 0.9.9", - "smallvec", - "thiserror 1.0.69", - "ton_types", -] - -[[package]] -name = "ton_executor" -version = "1.15.54" -source = "git+https://github.com/broxus/ton-labs-executor.git#b299a0fc8f3c3ecc28e8e38a9c014be56d4ce52d" -dependencies = [ - "anyhow", - "log", - "thiserror 1.0.69", - "ton_block", - "ton_types", - "ton_vm", -] - -[[package]] -name = "ton_types" -version = "1.10.2" -source = "git+https://github.com/broxus/ton-labs-types#8556b60547a20f16d50abcab084479d0c9db3756" -dependencies = [ - "anyhow", - "base64 0.13.1", - "countme", - "crc", - "dashmap 5.5.3", - "hex", - "log", - "num", - "num-derive", - "num-traits", - "rand 0.8.5", - "rustc-hash 1.1.0", - "sha2 0.9.9", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "ton_vm" -version = "1.8.29" -source = "git+https://github.com/broxus/ton-labs-vm.git#211bd88f46fa257ac4b939447f209465d3e201e1" -dependencies = [ - "anyhow", - "ed25519 1.5.3", - "ed25519-dalek 1.0.1", - "hex", - "lazy_static", - "log", - "num", - "num-traits", - "rand 0.8.5", - "sha2 0.9.9", - "smallvec", - "thiserror 1.0.69", - "ton_block", - "ton_types", -] - [[package]] name = "tower" version = "0.5.2" @@ -4831,7 +4508,7 @@ dependencies = [ "thiserror 2.0.17", "tl-proto", "tycho-storage-traits", - "tycho-types", + "tycho-types 0.3.2", "tycho-util", ] @@ -4872,7 +4549,7 @@ dependencies = [ "parking_lot", "parking_lot_core", "pin-project-lite", - "quick_cache 0.6.18", + "quick_cache", "rand 0.9.2", "scopeguard", "serde", @@ -4889,7 +4566,7 @@ dependencies = [ "tycho-crypto", "tycho-network", "tycho-storage", - "tycho-types", + "tycho-types 0.3.2", "tycho-util", "weedb", ] @@ -4909,6 +4586,20 @@ dependencies = [ "tl-proto", ] +[[package]] +name = "tycho-executor" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1caac6fbbfebd1ab9922d4dc021a7f1646bbd668ea928faeec7959006f6d534" +dependencies = [ + "ahash 0.8.12", + "anyhow", + "num-bigint", + "thiserror 2.0.17", + "tycho-types 0.2.1", + "tycho-vm 0.2.1", +] + [[package]] name = "tycho-executor" version = "0.3.2" @@ -4919,8 +4610,8 @@ dependencies = [ "anyhow", "num-bigint", "thiserror 2.0.17", - "tycho-types", - "tycho-vm", + "tycho-types 0.3.2", + "tycho-vm 0.3.2", ] [[package]] @@ -4937,7 +4628,7 @@ dependencies = [ "bytesize", "castaway", "dashmap 6.1.0", - "ed25519 2.2.3", + "ed25519", "exponential-backoff", "futures-util", "hex", @@ -4996,7 +4687,35 @@ checksum = "3449aeb0ad7213e38bb8047498a8af265e2ff7682922bac518af83f20d9ebfec" dependencies = [ "bytes", "smallvec", - "tycho-types", + "tycho-types 0.3.2", +] + +[[package]] +name = "tycho-types" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acf40ab2845c62d22fa75bca0470eb4b9f054b8a9d551d99be180bd9f8f8c4a" +dependencies = [ + "ahash 0.8.12", + "anyhow", + "base64 0.22.1", + "bitflags", + "bytes", + "crc32c", + "ed25519-dalek", + "hex", + "num-bigint", + "num-traits", + "scc", + "serde", + "sha2 0.10.9", + "smallvec", + "thiserror 2.0.17", + "tl-proto", + "tycho-crypto", + "tycho-types-abi-proc 0.2.0", + "tycho-types-proc 0.2.0", + "typeid", ] [[package]] @@ -5012,7 +4731,7 @@ dependencies = [ "bytes", "crc32c", "dashmap 6.1.0", - "ed25519-dalek 2.2.0", + "ed25519-dalek", "hex", "num-bigint", "num-traits", @@ -5027,11 +4746,23 @@ dependencies = [ "thiserror 2.0.17", "tl-proto", "tycho-crypto", - "tycho-types-abi-proc", - "tycho-types-proc", + "tycho-types-abi-proc 0.3.0", + "tycho-types-proc 0.3.0", "typeid", ] +[[package]] +name = "tycho-types-abi-proc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ef3008d7786bcca8e4de0481f1812c364b9669164f542ee1b4b80f57c0a6e0" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "tycho-types-abi-proc" version = "0.3.0" @@ -5043,6 +4774,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tycho-types-proc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d303e410fed076d6ff43c930598e8c4fcff75bfed2f197c130cc314aa9123b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "tycho-types-proc" version = "0.3.0" @@ -5106,6 +4848,29 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tycho-vm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b65d687d7f06dcb2645a3a3aa2a8bb6608cbbf55d01b230086898222294d0c" +dependencies = [ + "ahash 0.8.12", + "anyhow", + "bitflags", + "blake2", + "dyn-clone", + "num-bigint", + "num-integer", + "num-traits", + "sha2 0.10.9", + "thiserror 2.0.17", + "tl-proto", + "tracing", + "tycho-crypto", + "tycho-types 0.2.1", + "tycho-vm-proc 0.2.1", +] + [[package]] name = "tycho-vm" version = "0.3.2" @@ -5124,8 +4889,20 @@ dependencies = [ "thiserror 2.0.17", "tl-proto", "tycho-crypto", - "tycho-types", - "tycho-vm-proc", + "tycho-types 0.3.2", + "tycho-vm-proc 0.3.2", +] + +[[package]] +name = "tycho-vm-proc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3548fea44ef0cd0494084439572b2ece3e570f88e51d8b7aac95ea2b47706012" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -5156,7 +4933,7 @@ dependencies = [ "clap", "dashmap 5.5.3", "derive_more", - "ed25519-dalek 2.2.0", + "ed25519-dalek", "futures", "futures-util", "hex", @@ -5168,10 +4945,7 @@ dependencies = [ "lru", "metrics 0.20.1", "metrics-exporter-prometheus 0.16.2", - "nekoton", - "nekoton-abi", - "nekoton-contracts", - "nekoton-utils", + "nekoton-core", "num-bigint", "num-traits", "opg", @@ -5199,9 +4973,6 @@ dependencies = [ "tiny-hderive", "tokio", "tokio-util", - "ton_abi", - "ton_block", - "ton_types", "tower", "tower-http", "tower-service", @@ -5209,11 +4980,11 @@ dependencies = [ "tracing-subscriber", "tycho-block-util", "tycho-core", - "tycho-executor", + "tycho-executor 0.3.2", "tycho-storage", - "tycho-types", + "tycho-types 0.3.2", "tycho-util", - "tycho-vm", + "tycho-vm 0.3.2", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index e14382a..43f88bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,16 +79,7 @@ tycho-util = { version = "0.3.6", features = ["cli"] } tycho-vm = "0.3.2" tycho-executor = "0.3.2" -# TON specific dependencies -ton_block = { git = "https://github.com/broxus/ton-labs-block" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } -ton_types = { git = "https://github.com/broxus/ton-labs-types" } - -# Nekoton SDK -nekoton = { git = "https://github.com/broxus/nekoton.git", default-features = true } -nekoton-abi = { git = "https://github.com/broxus/nekoton.git", features = ["derive"] } -nekoton-utils = { git = "https://github.com/broxus/nekoton.git" } -nekoton-contracts = { git = "https://github.com/broxus/nekoton.git" } +nekoton-core = { git = "https://github.com/broxus/tycho-nekoton.git" } sysinfo = "0.30.13" diff --git a/src/models/account_enums.rs b/src/models/account_enums.rs index 31d5da0..75b973d 100644 --- a/src/models/account_enums.rs +++ b/src/models/account_enums.rs @@ -40,16 +40,6 @@ impl From for AccountStatus { } } -impl From for AccountStatus { - fn from(state: ton_block::AccountState) -> Self { - match state { - ton_block::AccountState::AccountUninit => AccountStatus::UnInit, - ton_block::AccountState::AccountActive { .. } => AccountStatus::Active, - ton_block::AccountState::AccountFrozen { .. } => AccountStatus::Frozen, - } - } -} - #[derive( Debug, Deserialize, Serialize, Clone, JsonSchema, Eq, PartialEq, sqlx::Type, Copy, EnumString, )] diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index 9f218e7..e195eb2 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -7,11 +7,8 @@ use futures::stream::FuturesUnordered; use futures::StreamExt; use parking_lot::{Mutex, RwLock, RwLockReadGuard}; use rustc_hash::FxHashMap; -use tycho_types::boc::Boc; use tycho_types::cell::{Cell, CellBuilder, HashBytes, Load}; -use ton_block::Deserializable; -use ton_types::SliceData; use tycho_block_util::block::BlockStuff; use tycho_block_util::state::{RefMcStateHandle, ShardStateStuff}; use tycho_vm::StackValue; @@ -609,18 +606,6 @@ pub fn make_existing_contract(state: Option) -> Result Result> { - let cell = CellBuilder::build_from(account)?; - let bytes = Boc::encode(cell); - let cell = ton_types::deserialize_tree_of_cells(&mut &*bytes)?; - match ton_block::Account::construct_from(&mut SliceData::load_cell(cell)?)? { - ton_block::Account::AccountNone => Ok(None), - ton_block::Account::Account(stuff) => Ok(Some(stuff)), - } -} - pub struct CachedAccounts { pub accounts: ShardAccounts, pub state_handle: RefMcStateHandle, diff --git a/src/utils/existing_contract.rs b/src/utils/existing_contract.rs index 5b180ed..7f9e204 100644 --- a/src/utils/existing_contract.rs +++ b/src/utils/existing_contract.rs @@ -43,12 +43,7 @@ impl ExistingContractExt for ExistingContract { let ExecutionOutput { tokens, result_code, - } = function.run_local( - &nekoton_utils::SimpleClock, - self.account.clone(), - input, - &[], - )?; + } = function.run_local(self.account.clone(), input, &[])?; tokens.ok_or_else(|| ExistingContractError::NonZeroResultCode(result_code).into()) } diff --git a/src/utils/mnemonic/labs.rs b/src/utils/mnemonic/labs.rs index c4ecf4b..c483530 100644 --- a/src/utils/mnemonic/labs.rs +++ b/src/utils/mnemonic/labs.rs @@ -1,14 +1,13 @@ use std::convert::TryInto; use anyhow::Result; -use nekoton_utils::TrustMe; use super::{Bip39MnemonicData, LANGUAGE}; pub fn derive_master_key(phrase: &str) -> Result<[u8; 64]> { let mnemonic = bip39::Mnemonic::from_phrase(phrase, LANGUAGE)?; let hd = bip39::Seed::new(&mnemonic, ""); - Ok(hd.as_bytes().try_into().trust_me()) + Ok(hd.as_bytes().try_into().unwrap()) } pub fn derive_from_phrase( diff --git a/src/utils/shard_utils.rs b/src/utils/shard_utils.rs index 5236332..76eced0 100644 --- a/src/utils/shard_utils.rs +++ b/src/utils/shard_utils.rs @@ -1,18 +1,8 @@ -use std::collections::HashMap; - use anyhow::Result; use tycho_types::{cell::HashBytes, models::ShardIdent}; use crate::models::ExistingContract; -pub type ShardsMap = HashMap; - -#[derive(Debug, Clone)] -pub struct LatestShardBlocks { - pub current_utime: u32, - pub block_ids: ShardsMap, -} - /// Helper trait to reduce boilerplate for getting accounts from shards state pub trait ShardAccountsMapExt { /// Looks for a suitable shard and tries to extract information about the contract from it @@ -86,7 +76,7 @@ mod tests { .unwrap(), ); - let mut shards = vec![ShardIdent::new(0, ton_block::SHARD_FULL).unwrap()]; + let mut shards = vec![ShardIdent::new(0, ShardIdent::PREFIX_FULL).unwrap()]; for _ in 0..4 { let mut new_shards = vec![]; for shard in &shards { diff --git a/src/utils/token_wallets/mod.rs b/src/utils/token_wallets/mod.rs index f02814e..a9da602 100644 --- a/src/utils/token_wallets/mod.rs +++ b/src/utils/token_wallets/mod.rs @@ -614,7 +614,7 @@ pub struct AcceptBurnInputs { pub wallet_owner: StdAddr, pub remaining_gas_to: StdAddr, pub callback_to: StdAddr, - pub payload: ton_types::Cell, + pub payload: Cell, } impl AcceptBurnInputs { From 3622e233d79411234d119cc1335be724b770b6b2 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 12 Feb 2026 11:17:29 +0300 Subject: [PATCH 32/59] fix token wallets parsing --- Cargo.lock | 135 ++------- Cargo.toml | 6 +- src/api/controllers/misc.rs | 19 +- src/api/responses/misc.rs | 13 +- src/client/ton/mod.rs | 25 +- src/services/ton.rs | 2 +- src/ton_core/mod.rs | 4 + src/ton_core/ton_subscriber/mod.rs | 24 +- src/utils/existing_contract.rs | 27 +- src/utils/token_wallet.rs | 25 +- src/utils/token_wallets/mod.rs | 235 ++++++++++++++++ src/utils/token_wallets/models.rs | 429 +++++++++++++++++++++++++++-- src/utils/ton_wallet/multisig.rs | 50 ++-- 13 files changed, 786 insertions(+), 208 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c8be7b..5bcef1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2412,7 +2412,7 @@ dependencies = [ [[package]] name = "nekoton-core" version = "0.0.2" -source = "git+https://github.com/broxus/tycho-nekoton.git#60faf969afb88336b19b246d3c76a4f29668cda9" +source = "git+https://github.com/broxus/tycho-nekoton.git?branch=feature%2Fupgrade-tycho-types#3ee48557f83ea1002c8417e428715c5022d93523" dependencies = [ "anyhow", "async-trait", @@ -2424,15 +2424,15 @@ dependencies = [ "serde", "thiserror 2.0.17", "tokio", - "tycho-executor 0.2.1", - "tycho-types 0.2.1", - "tycho-vm 0.2.1", + "tycho-executor", + "tycho-types", + "tycho-vm", ] [[package]] name = "nekoton-utils" version = "0.0.2" -source = "git+https://github.com/broxus/tycho-nekoton.git#60faf969afb88336b19b246d3c76a4f29668cda9" +source = "git+https://github.com/broxus/tycho-nekoton.git?branch=feature%2Fupgrade-tycho-types#3ee48557f83ea1002c8417e428715c5022d93523" dependencies = [ "hex", "serde", @@ -4508,7 +4508,7 @@ dependencies = [ "thiserror 2.0.17", "tl-proto", "tycho-storage-traits", - "tycho-types 0.3.2", + "tycho-types", "tycho-util", ] @@ -4566,7 +4566,7 @@ dependencies = [ "tycho-crypto", "tycho-network", "tycho-storage", - "tycho-types 0.3.2", + "tycho-types", "tycho-util", "weedb", ] @@ -4586,20 +4586,6 @@ dependencies = [ "tl-proto", ] -[[package]] -name = "tycho-executor" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1caac6fbbfebd1ab9922d4dc021a7f1646bbd668ea928faeec7959006f6d534" -dependencies = [ - "ahash 0.8.12", - "anyhow", - "num-bigint", - "thiserror 2.0.17", - "tycho-types 0.2.1", - "tycho-vm 0.2.1", -] - [[package]] name = "tycho-executor" version = "0.3.2" @@ -4610,8 +4596,8 @@ dependencies = [ "anyhow", "num-bigint", "thiserror 2.0.17", - "tycho-types 0.3.2", - "tycho-vm 0.3.2", + "tycho-types", + "tycho-vm", ] [[package]] @@ -4687,35 +4673,7 @@ checksum = "3449aeb0ad7213e38bb8047498a8af265e2ff7682922bac518af83f20d9ebfec" dependencies = [ "bytes", "smallvec", - "tycho-types 0.3.2", -] - -[[package]] -name = "tycho-types" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acf40ab2845c62d22fa75bca0470eb4b9f054b8a9d551d99be180bd9f8f8c4a" -dependencies = [ - "ahash 0.8.12", - "anyhow", - "base64 0.22.1", - "bitflags", - "bytes", - "crc32c", - "ed25519-dalek", - "hex", - "num-bigint", - "num-traits", - "scc", - "serde", - "sha2 0.10.9", - "smallvec", - "thiserror 2.0.17", - "tl-proto", - "tycho-crypto", - "tycho-types-abi-proc 0.2.0", - "tycho-types-proc 0.2.0", - "typeid", + "tycho-types", ] [[package]] @@ -4746,23 +4704,11 @@ dependencies = [ "thiserror 2.0.17", "tl-proto", "tycho-crypto", - "tycho-types-abi-proc 0.3.0", - "tycho-types-proc 0.3.0", + "tycho-types-abi-proc", + "tycho-types-proc", "typeid", ] -[[package]] -name = "tycho-types-abi-proc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ef3008d7786bcca8e4de0481f1812c364b9669164f542ee1b4b80f57c0a6e0" -dependencies = [ - "anyhow", - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "tycho-types-abi-proc" version = "0.3.0" @@ -4774,17 +4720,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "tycho-types-proc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d303e410fed076d6ff43c930598e8c4fcff75bfed2f197c130cc314aa9123b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "tycho-types-proc" version = "0.3.0" @@ -4848,29 +4783,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "tycho-vm" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b65d687d7f06dcb2645a3a3aa2a8bb6608cbbf55d01b230086898222294d0c" -dependencies = [ - "ahash 0.8.12", - "anyhow", - "bitflags", - "blake2", - "dyn-clone", - "num-bigint", - "num-integer", - "num-traits", - "sha2 0.10.9", - "thiserror 2.0.17", - "tl-proto", - "tracing", - "tycho-crypto", - "tycho-types 0.2.1", - "tycho-vm-proc 0.2.1", -] - [[package]] name = "tycho-vm" version = "0.3.2" @@ -4888,21 +4800,10 @@ dependencies = [ "sha2 0.10.9", "thiserror 2.0.17", "tl-proto", + "tracing", "tycho-crypto", - "tycho-types 0.3.2", - "tycho-vm-proc 0.3.2", -] - -[[package]] -name = "tycho-vm-proc" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3548fea44ef0cd0494084439572b2ece3e570f88e51d8b7aac95ea2b47706012" -dependencies = [ - "darling 0.21.3", - "proc-macro2", - "quote", - "syn 2.0.111", + "tycho-types", + "tycho-vm-proc", ] [[package]] @@ -4980,11 +4881,11 @@ dependencies = [ "tracing-subscriber", "tycho-block-util", "tycho-core", - "tycho-executor 0.3.2", + "tycho-executor", "tycho-storage", - "tycho-types 0.3.2", + "tycho-types", "tycho-util", - "tycho-vm 0.3.2", + "tycho-vm", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index 43f88bd..f735d48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ tower-http = { version = "0.6", features = ["trace", "cors", "limit", "set-heade tower-service = "0.3.3" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tycho-types = { git = "https://github.com/broxus/tycho-types.git", rev = "f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" , features = ["tycho", "stats", "serde", "abi"] } +tycho-types = { version = "0.3.2" , features = ["tycho", "stats", "serde", "abi"] } uuid = { version = "1.1", features = ["v4", "serde"] } @@ -79,7 +79,7 @@ tycho-util = { version = "0.3.6", features = ["cli"] } tycho-vm = "0.3.2" tycho-executor = "0.3.2" -nekoton-core = { git = "https://github.com/broxus/tycho-nekoton.git" } +nekoton-core = { git = "https://github.com/broxus/tycho-nekoton.git", branch = "feature/upgrade-tycho-types" } sysinfo = "0.30.13" @@ -87,4 +87,4 @@ sysinfo = "0.30.13" default = [] [patch.crates-io] -tycho-types = { git = "https://github.com/broxus/tycho-types.git", rev = "f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" , features = ["tycho", "stats", "serde", "abi"] } +tycho-types = { git = "https://github.com/broxus/tycho-types.git", rev = "f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1"} diff --git a/src/api/controllers/misc.rs b/src/api/controllers/misc.rs index 642b2f8..8ae27b1 100644 --- a/src/api/controllers/misc.rs +++ b/src/api/controllers/misc.rs @@ -38,17 +38,18 @@ pub async fn post_read_contract( .await .map(|values| { let params = SerializeAbiValueParams::default(); - let mut result = Vec::new(); + let mut output = Vec::new(); for value in values { - if let Some(json) = - serde_json::to_value(&SerializeAbiValue::with_params(&value, params)).ok() - { - result.push(json); - } - } - ReadContractResponse { - object: serde_json::Value::from(result), + output.push(OutputParamDTO { + abi_value: serde_json::to_string(&SerializeAbiValue::with_params( + &value.value, + params, + )) + .unwrap_or_default(), + name: value.name.to_string(), + }); } + ReadContractResponse { output } })?; let elapsed = start.elapsed(); diff --git a/src/api/responses/misc.rs b/src/api/responses/misc.rs index 64d5004..0254236 100644 --- a/src/api/responses/misc.rs +++ b/src/api/responses/misc.rs @@ -1,8 +1,8 @@ -use crate::{models::WhitelistedTokenFromDb, utils::token_wallets::models::TokenWalletVersion}; - use schemars::JsonSchema; use serde::Serialize; +use crate::{models::WhitelistedTokenFromDb, utils::token_wallets::models::TokenWalletVersion}; + #[derive(Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct ResubscribeResponse {} @@ -10,7 +10,7 @@ pub struct ResubscribeResponse {} #[derive(Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct ReadContractResponse { - pub object: serde_json::Value, + pub output: Vec, } #[derive(Serialize, JsonSchema)] @@ -61,3 +61,10 @@ pub struct TokenWhitelistResponse { pub count: i32, pub items: Vec, } + +#[derive(Serialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct OutputParamDTO { + pub abi_value: String, + pub name: String, +} diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index fe3bb90..1c30edb 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -4,10 +4,12 @@ use std::sync::Arc; use axum::http::StatusCode; use bigdecimal::{BigDecimal, ToPrimitive}; use ed25519_dalek::VerifyingKey; +use nekoton_core::contracts::function_ext::ExecutionOutput; +use nekoton_core::contracts::function_ext::FunctionExt; use num_bigint::BigUint; use num_traits::FromPrimitive; use tokio::sync::oneshot; -use tycho_types::abi::{AbiValue, Function, NamedAbiValue, UnsignedExternalMessage}; +use tycho_types::abi::{Function, NamedAbiValue, UnsignedExternalMessage}; use tycho_types::boc::Boc; use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{GlobalCapabilities, OwnedMessage, SignatureContext, StdAddr}; @@ -798,8 +800,8 @@ impl TonClient { function: Function, input: &[NamedAbiValue], responsible: bool, - ) -> anyhow::Result>> { - let state = match self.ton_core.get_contract_state(&contract_address) { + ) -> anyhow::Result>> { + let mut state = match self.ton_core.get_contract_state(&contract_address) { Ok(a) => a, Err(e) => { tracing::error!("Failed to get contract state: {e:?}"); @@ -807,13 +809,18 @@ impl TonClient { } }; - let res = if responsible { - function.run_local_responsible(&SimpleClock, state.account, input, &[]) - } else { - function.run_local(&SimpleClock, state.account, input, &[]) - }; + let ExecutionOutput { values, exit_code } = function.run_local( + &mut state.account, + input, + responsible, + &mut self.ton_core.blockchain_context(), + )?; + + if exit_code != 0 { + return Err(anyhow::anyhow!("Non-zero result code: {exit_code}")); + } - res.map(Some) + Ok(Some(values)) } pub async fn prepare_signed_generic_message( diff --git a/src/services/ton.rs b/src/services/ton.rs index 770fb22..c989a48 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -884,7 +884,7 @@ impl TonService { outputs: Vec, headers: Vec, responsible: bool, - ) -> Result, Error> { + ) -> Result, Error> { let account_addr = HashBytes::from_str(&account_addr).map_err(anyhow::Error::from)?; let input_params: Vec = inputs.iter().map(|x| x.abi_type.clone()).collect(); diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index 2f763e8..bf5a362 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use std::sync::Arc; use anyhow::{Context, Result}; +use nekoton_core::contracts::blockchain_context::BlockchainContext; use parking_lot::Mutex; use tokio::sync::{mpsc, oneshot}; use tycho_core::blockchain_rpc::BlockchainRpcClient; @@ -109,6 +110,9 @@ impl TonCore { pub fn capabilities(&self) -> u64 { self.context.ton_subscriber.capabilities() } + pub fn blockchain_context(&self) -> BlockchainContext { + self.context.ton_subscriber.blockchain_context() + } } pub struct TonCoreContext { diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index e195eb2..db34f82 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -9,6 +9,8 @@ use parking_lot::{Mutex, RwLock, RwLockReadGuard}; use rustc_hash::FxHashMap; use tycho_types::cell::{Cell, CellBuilder, HashBytes, Load}; +use nekoton_core::contracts::blockchain_context::{BlockchainContext, BlockchainContextBuilder}; +use nekoton_core::transport::SimpleTransport; use tycho_block_util::block::BlockStuff; use tycho_block_util::state::{RefMcStateHandle, ShardStateStuff}; use tycho_vm::StackValue; @@ -19,7 +21,6 @@ use crate::utils::token_wallets::models::TokenWalletVersion; use crate::utils::token_wallets::parsing; pub struct TonSubscriber { - // tip block timestamp current_utime: AtomicU32, signature_id: SignatureId, capabilities: Capabilities, @@ -28,6 +29,7 @@ pub struct TonSubscriber { sc_accounts: RwLock>, mc_block_awaiters: Mutex>>, messages_queue: Arc, + blockchain_context: RwLock, } impl TonSubscriber { @@ -47,6 +49,7 @@ impl TonSubscriber { )), messages_queue, capabilities: Capabilities::default(), + blockchain_context: RwLock::new(BlockchainContextBuilder::default().build().unwrap()), }) } @@ -77,6 +80,10 @@ impl TonSubscriber { self.capabilities.0.load(Ordering::Acquire) } + pub fn blockchain_context(&self) -> BlockchainContext { + self.blockchain_context.read().clone() + } + pub fn add_transactions_subscription(&self, accounts: I, subscription: &Arc) where I: IntoIterator, @@ -167,6 +174,7 @@ impl TonSubscriber { if let Some(config) = custom.config { let global_version = config.get_global_version()?; self.update_capabilies(global_version.capabilities.into_inner())?; + self.update_blockchain_context(config)?; } } } @@ -307,6 +315,20 @@ impl TonSubscriber { self.capabilities.0.store(capabilities, Ordering::Release); Ok(()) } + + fn update_blockchain_context(&self, config: BlockchainConfig) -> Result<()> { + let mut blockchain_context = self.blockchain_context.write(); + + let transport = SimpleTransport::new(vec![], config.clone())?; + + let context = BlockchainContextBuilder::new() + .with_config(config) + .with_transport(Arc::new(transport)) + .build()?; + + *blockchain_context = context; + Ok(()) + } } impl TonSubscriber { diff --git a/src/utils/existing_contract.rs b/src/utils/existing_contract.rs index 7f9e204..2ffe68a 100644 --- a/src/utils/existing_contract.rs +++ b/src/utils/existing_contract.rs @@ -1,4 +1,6 @@ use anyhow::Result; +use nekoton_core::contracts::blockchain_context::BlockchainContext; +use nekoton_core::contracts::function_ext::{ExecutionOutput, FunctionExt}; use tycho_types::{ abi::{Function, NamedAbiValue}, models::ShardAccount, @@ -12,8 +14,13 @@ pub trait ExistingContractExt { shard_account: &Option, ) -> Result>; - fn run_local(&self, function: &Function, input: &[NamedAbiValue]) - -> Result>; + fn run_local( + &mut self, + function: &Function, + input: &[NamedAbiValue], + responsible: bool, + context: &mut BlockchainContext, + ) -> Result>; } impl ExistingContractExt for ExistingContract { @@ -36,16 +43,20 @@ impl ExistingContractExt for ExistingContract { } fn run_local( - &self, + &mut self, function: &Function, input: &[NamedAbiValue], + responsible: bool, + context: &mut BlockchainContext, ) -> Result> { - let ExecutionOutput { - tokens, - result_code, - } = function.run_local(self.account.clone(), input, &[])?; + let ExecutionOutput { values, exit_code } = + function.run_local(&mut self.account, input, responsible, context)?; + + if exit_code != 0 { + return Err(ExistingContractError::NonZeroResultCode(exit_code).into()); + } - tokens.ok_or_else(|| ExistingContractError::NonZeroResultCode(result_code).into()) + Ok(values) } } diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index 82515c9..cebaf5c 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -4,9 +4,11 @@ use num_bigint::BigUint; use num_traits::ToPrimitive; use tycho_types::abi::AbiValue; use tycho_types::cell::{Cell, HashBytes}; -use tycho_types::models::StdAddr; +use tycho_types::models::{AccountState, StdAddr}; use crate::models::ExistingContract; +use crate::utils::token_wallets::models::RootTokenContractState; +use crate::utils::token_wallets::models::TokenWalletContractState; use crate::utils::token_wallets::models::{ RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion, }; @@ -153,7 +155,6 @@ pub fn get_token_wallet_address( owner: &StdAddr, ) -> Result { let root_contract_state = RootTokenContractState(ExecutionContext { - clock: &SimpleClock, account_stuff: &root_contract.account, libraries: &[], }); @@ -167,7 +168,6 @@ pub fn get_token_wallet_account( owner: &StdAddr, ) -> Result { let root_contract_state = RootTokenContractState(ExecutionContext { - clock: &SimpleClock, account_stuff: &root_contract.account, libraries: &[], }); @@ -183,7 +183,6 @@ pub fn get_token_wallet_basic_info( token_contract: &ExistingContract, ) -> Result<(TokenWalletVersion, BigDecimal)> { let token_wallet_state = TokenWalletContractState(ExecutionContext { - clock: &SimpleClock, account_stuff: &token_contract.account, libraries: &[], }); @@ -196,23 +195,31 @@ pub fn get_token_wallet_basic_info( pub fn get_token_wallet_details( token_contract: &ExistingContract, -) -> Result<(TokenWalletDetails, TokenWalletVersion, [u8; 32])> { +) -> Result<(TokenWalletDetails, TokenWalletVersion, HashBytes)> { let contract_state = TokenWalletContractState(ExecutionContext { - clock: &SimpleClock, account_stuff: &token_contract.account, libraries: &[], }); - let hash = *contract_state.get_code_hash()?.as_slice(); + let hash = match &token_contract.account.state { + AccountState::Active(state_init) => { + let code = state_init + .code + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Wallet not deployed"))?; + *code.repr_hash() + } + _ => anyhow::bail!("Wallet not deployed"), + }; + let version = contract_state.get_version()?; let details = contract_state.get_details(version)?; Ok((details, version, hash)) } -pub fn get_root_token_version(root_contract: &ExistingContract) -> Result { +pub fn get_root_token_version(root_contract: &ExistingContract, context: BlockchainContext) -> Result { let root_contract_state = RootTokenContractState(ExecutionContext { - clock: &SimpleClock, account_stuff: &root_contract.account, libraries: &[], }); diff --git a/src/utils/token_wallets/mod.rs b/src/utils/token_wallets/mod.rs index a9da602..a79d7ed 100644 --- a/src/utils/token_wallets/mod.rs +++ b/src/utils/token_wallets/mod.rs @@ -649,3 +649,238 @@ pub fn accept_burn() -> &'static Function { outputs: Vec::new(), } } + +/// Returns the token wallet code. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `walletCode: cell` +/// +pub fn wallet_code() -> &'static Function { + declare_function! { + name: "walletCode", + inputs: vec![ + AbiType::Uint(32).named("answerId"),], + outputs: vec![ + AbiType::Cell.named("walletCode")], + } +} + +/// Returns the total token supply. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `totalSupply: string` +/// +pub fn total_supply() -> &'static Function { + declare_function! { + name: "totalSupply", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + AbiType::Uint(128).named("totalSupply"), + ], + } +} + +/// Returns the name of the token - e.g. `MyToken`. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `name: string` +/// +pub fn name() -> &'static Function { + declare_function! { + name: "name", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + AbiType::String.named("name"), + ], + } +} + +/// Returns the symbol of the token. E.g. "HIX". +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `symbol: string` +/// +pub fn symbol() -> &'static Function { + declare_function! { + name: "symbol", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + + AbiType::String.named("symbol"), + ], + } +} + +/// Returns the number of decimals the token uses - e.g. 8, +/// means to divide the token amount by 100000000 to get its user representation. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `decimals: uint8` +/// +pub fn decimals() -> &'static Function { + declare_function! { + name: "decimals", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + + AbiType::Uint(8).named("decimals"),], + } +} + +/// A contract that is compliant with TIP6 shall implement the following interface +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// * `interfaceID: bytes4` - interface ID +/// +/// # Outputs +/// * `name: string` +/// +pub fn supports_interface() -> &'static Function { + declare_function! { + name: "supportsInterface", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + AbiType::Uint(32).named("interfaceID"), + ], + outputs: vec![ + AbiType::Bool.named("supports"), + ], + } +} + +/// Returns the token root address. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `root: address` +/// +pub fn root() -> &'static Function { + declare_function! { + name: "root", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + AbiType::Address.named("root"), + ], + } +} + +/// Returns the token wallet balance. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `balance: uint128` +/// +pub fn balance() -> &'static Function { + declare_function! { + name: "balance", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + + AbiType::Uint(128).named("balance"), + ], + } +} + +/// Get root owner +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `owner: address` - owner wallet address +/// +pub fn root_owner() -> &'static Function { + declare_function! { + name: "rootOwner", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + + AbiType::Address.named("rootOwner"),], + } +} + +/// Derive `TokenWallet` address from owner address +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// * `owner: address` - owner address +/// +/// # Outputs +/// * `walletAddress: address` - owner wallet address +/// +pub fn wallet_of() -> &'static Function { + declare_function! { + name: "walletOf", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + AbiType::Address.named("owner"), + ], + outputs: vec![ + AbiType::Address.named("walletAddress"), + ], + } +} diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs index e51c0ec..be81682 100644 --- a/src/utils/token_wallets/models.rs +++ b/src/utils/token_wallets/models.rs @@ -1,10 +1,13 @@ use std::fmt::Display; -use num_bigint::BigUint; +use nekoton_core::contracts::blockchain_context::BlockchainAccount; +use nekoton_core::contracts::function_ext::ExecutionOutput; +use num_traits::ToPrimitive; use serde::{Deserialize, Serialize}; use tycho_types::{ - cell::{Cell, HashBytes}, - models::{AccountState, StdAddr}, + abi::{AbiValue, NamedAbiValue}, + cell::Cell, + models::{AnyAddr, StdAddr}, }; use crate::utils::{serde_address, serde_cell, serde_string}; @@ -122,40 +125,27 @@ pub enum Tip3Error { WalletNotDeployed, } -pub struct TokenWalletContractState<'a>(pub ExecutionContext<'a>); +pub struct TokenWalletContractState<'a>(&'a mut BlockchainAccount); impl TokenWalletContractState<'_> { - pub fn get_code_hash(&self) -> anyhow::Result { - match &self.0.account.state { - AccountState::Active(state_init) => { - let code = state_init - .code - .as_ref() - .ok_or(Tip3Error::WalletNotDeployed)?; - Ok(*code.repr_hash()) - } - _ => Err(Tip3Error::WalletNotDeployed.into()), - } - } - - pub fn get_balance(&self, version: TokenWalletVersion) -> anyhow::Result { + pub fn get_balance(&mut self, version: TokenWalletVersion) -> anyhow::Result { match version { TokenWalletVersion::OldTip3v4 => Err(Tip3Error::UnknownVersion.into()), - TokenWalletVersion::Tip3 => tip3::TokenWalletContract(self.0).balance(), + TokenWalletVersion::Tip3 => TokenWalletContract(self.0).balance(), } } - pub fn get_details(&self, version: TokenWalletVersion) -> anyhow::Result { + pub fn get_details(&mut self, version: TokenWalletVersion) -> anyhow::Result { Ok(match version { TokenWalletVersion::OldTip3v4 => { return Err(Tip3Error::UnknownVersion.into()); } TokenWalletVersion::Tip3 => { - let token_wallet = tip3::TokenWalletContract(self.0); + let mut token_wallet = TokenWalletContract(self.0); let root_address = token_wallet.root()?; let balance = token_wallet.balance()?; - let token_wallet = tip3_1::TokenWalletContract(self.0); + let mut token_wallet = TokenWalletContract(self.0); let owner_address = token_wallet.owner()?; TokenWalletDetails { @@ -167,14 +157,399 @@ impl TokenWalletContractState<'_> { }) } - pub fn get_version(&self) -> anyhow::Result { - if let Ok(true) = tip6::SidContract(self.0).supports_interfaces(&[ - tip3::token_wallet_contract::INTERFACE_ID, - tip3_1::token_wallet_contract::INTERFACE_ID, - ]) { + pub fn get_version(&mut self) -> anyhow::Result { + if let Ok(true) = SidContract(self.0).supports_interfaces(&[0x2a4ac43e, 0x4F479FA3]) { Ok(TokenWalletVersion::Tip3) } else { Err(Tip3Error::UnknownVersion.into()) } } } + +pub struct RootTokenContractState<'a>(&'a mut BlockchainAccount); + +impl RootTokenContractState<'_> { + /// Calculates token wallet address + pub fn get_wallet_address( + &mut self, + version: TokenWalletVersion, + owner: &StdAddr, + ) -> anyhow::Result { + match version { + TokenWalletVersion::OldTip3v4 => { + anyhow::bail!(Tip3Error::UnknownVersion) + } + TokenWalletVersion::Tip3 => RootTokenContract(self.0).wallet_of(owner.clone()), + } + } + + /// Tries to guess version and retrieve details + pub fn guess_details(&mut self) -> anyhow::Result { + if let Ok(true) = SidContract(self.0).supports_interfaces(&[0x4371D8ED, 0x0b1fd263]) { + return self.get_details(TokenWalletVersion::Tip3); + } + + self.get_details(TokenWalletVersion::Tip3) + } + + /// Retrieve details using specified version + pub fn get_details( + &mut self, + version: TokenWalletVersion, + ) -> anyhow::Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + anyhow::bail!(Tip3Error::UnknownVersion) + } + TokenWalletVersion::Tip3 => { + let mut root_contract = RootTokenContract(self.0); + let name = root_contract.name()?; + let symbol = root_contract.symbol()?; + let decimals = root_contract.decimals()?; + let total_supply = root_contract.total_supply()?; + let owner_address = root_contract.root_owner()?; + + RootTokenContractDetails { + version, + name, + symbol, + decimals, + owner_address, + total_supply, + } + } + }) + } +} + +pub struct RootTokenContract<'a>(&'a mut BlockchainAccount); + +impl RootTokenContract<'_> { + pub fn name(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::name(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get name with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get name"))? + .clone(); + + let AbiValue::String(name) = result.value else { + return Err(anyhow::anyhow!("Failed to get name")); + }; + + Ok(name) + } + + pub fn symbol(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::symbol(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get symbol with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get symbol"))? + .clone(); + + let AbiValue::String(symbol) = result.value else { + return Err(anyhow::anyhow!("Failed to get symbol")); + }; + + Ok(symbol) + } + + pub fn decimals(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::decimals(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get decimals with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get decimals"))? + .clone(); + + let AbiValue::Uint(_, decimals) = result.value else { + return Err(anyhow::anyhow!("Failed to get decimals")); + }; + + Ok(decimals.to_u8().unwrap()) + } + + pub fn total_supply(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = self + .0 + .run_local_responsible(super::total_supply(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get total_supply with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get total_supply"))? + .clone(); + + let AbiValue::Uint(_, total_supply) = result.value else { + return Err(anyhow::anyhow!("Failed to get total_supply")); + }; + + Ok(total_supply.to_u128().unwrap()) + } + + pub fn wallet_code(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = self + .0 + .run_local_responsible(super::wallet_code(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get wallet_code with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get wallet_code"))? + .clone(); + + let AbiValue::Cell(wallet_code) = result.value else { + return Err(anyhow::anyhow!("Failed to get wallet_code")); + }; + + Ok(wallet_code) + } + + pub fn root_owner(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::root_owner(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get root_owner with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get root_owner"))? + .clone(); + + let AbiValue::Address(root_owner) = result.value else { + return Err(anyhow::anyhow!("Failed to get root_owner")); + }; + + let AnyAddr::Std(root_owner) = &*root_owner else { + return Err(anyhow::anyhow!("Failed to get root_owner")); + }; + + Ok(root_owner.clone()) + } + + pub fn wallet_of(&mut self, owner: StdAddr) -> anyhow::Result { + let inputs = [ + AbiValue::uint(32, 0u32).named("answerId"), + AbiValue::address(owner).named("owner"), + ]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::wallet_of(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get wallet_of with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get wallet_of"))? + .clone(); + + let AbiValue::Address(wallet_of) = result.value else { + return Err(anyhow::anyhow!("Failed to get wallet_of")); + }; + let AnyAddr::Std(wallet_of) = &*wallet_of else { + return Err(anyhow::anyhow!("Failed to get wallet_of")); + }; + + Ok(wallet_of.clone()) + } +} + +pub struct TokenWalletContract<'a>(&'a mut BlockchainAccount); + +impl TokenWalletContract<'_> { + pub fn owner(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::owner(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get owner with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get owner"))? + .clone(); + + let AbiValue::Address(owner) = result.value else { + return Err(anyhow::anyhow!("Failed to get owner")); + }; + let AnyAddr::Std(owner) = &*owner else { + return Err(anyhow::anyhow!("Failed to get owner")); + }; + + Ok(owner.clone()) + } + pub fn root(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::root(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get root with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get root"))? + .clone(); + + let AbiValue::Address(root) = result.value else { + return Err(anyhow::anyhow!("Failed to get owrootner")); + }; + let AnyAddr::Std(root) = &*root else { + return Err(anyhow::anyhow!("Failed to get root")); + }; + + Ok(root.clone()) + } + + pub fn balance(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::balance(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get balance with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get balance"))? + .clone(); + + let AbiValue::Uint(_, balance) = result.value else { + return Err(anyhow::anyhow!("Failed to get balance")); + }; + + Ok(balance.to_u128().unwrap()) + } +} + +pub struct SidContract<'a>(&'a mut BlockchainAccount); + +impl SidContract<'_> { + pub fn supports_interfaces(&mut self, interfaces: &[u32]) -> anyhow::Result { + let mut inputs: Option<[NamedAbiValue; 2]> = None; + + for &interface in interfaces { + let inputs = match &mut inputs { + Some(inputs) => { + inputs[1] = make_interface_id(interface); + inputs + } + None => inputs.insert([ + AbiValue::uint(32, 0u32).named("answerId"), + make_interface_id(interface), + ]), + }; + + let ExecutionOutput { values, exit_code } = self + .0 + .run_local_responsible(super::supports_interface(), inputs.as_ref())?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get name with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get name"))? + .clone(); + + let AbiValue::Bool(b) = result.value else { + return Err(anyhow::anyhow!("Failed to get name")); + }; + + if !b { + return Ok(false); + } + } + + Ok(true) + } + + pub fn supports_interface(&mut self, interface: u32) -> anyhow::Result { + let inputs = [ + AbiValue::uint(32, 0u32).named("answerId"), + make_interface_id(interface), + ]; + let ExecutionOutput { values, exit_code } = self + .0 + .run_local_responsible(super::supports_interface(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get supports_interface with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get supports_interface"))? + .clone(); + + let AbiValue::Bool(supports_interface) = result.value else { + return Err(anyhow::anyhow!("Failed to get supports_interface")); + }; + + Ok(supports_interface) + } +} + +fn make_interface_id(interface: u32) -> NamedAbiValue { + AbiValue::uint(32, interface).named("interfaceID") +} diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index cf75bb9..a90ceee 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -3,13 +3,15 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::VerifyingKey; +use nekoton_core::contracts::blockchain_context::BlockchainContext; +use nekoton_core::contracts::function_ext::ExecutionOutput; +use nekoton_core::contracts::function_ext::FunctionExt; use tycho_types::{ abi::{AbiValue, FromAbi, Function, IntoAbi, NamedAbiValue, UnsignedExternalMessage}, cell::{Cell, CellBuilder, CellDataBuilder, HashBytes, Load}, dict::RawDict, models::{Account, StateInit, StdAddr}, }; -use tycho_util::time::Clock; use crate::utils::{ ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}, @@ -457,16 +459,20 @@ pub fn prepare_state_init( } fn run_local( - clock: &dyn Clock, function: &Function, - account_stuff: Account, + mut account_stuff: Account, + input: &[NamedAbiValue], + responsible: bool, + context: &mut BlockchainContext, ) -> Result> { - unimplemented!() - //let ExecutionOutput { - // tokens, - // result_code, - //} = function.run_local(clock, account_stuff, &[], &[])?; - //tokens.ok_or_else(|| MultisigError::NonZeroResultCode(result_code).into()) + let ExecutionOutput { values, exit_code } = + function.run_local(&mut account_stuff, input, responsible, context)?; + + if exit_code != 0 { + return Err(MultisigError::NonZeroResultCode(exit_code).into()); + } + + Ok(values) } #[derive(Copy, Clone)] @@ -517,9 +523,9 @@ impl TryFrom> for MultisigParamsPrefix { } pub fn get_params( - clock: &dyn Clock, multisig_type: MultisigType, account: Cow<'_, Account>, + context: &mut BlockchainContext, ) -> Result { let function = match multisig_type { MultisigType::Multisig2 | MultisigType::Multisig2_1 => { @@ -537,21 +543,23 @@ pub fn get_params( } }; - let output = run_local(clock, function, account.into_owned())?; + let output = run_local(function, account.into_owned(), &[], false, context)?; MultisigParamsPrefix::try_from(output) } pub fn get_custodians( - clock: &dyn Clock, multisig_type: MultisigType, account: Cow<'_, Account>, + context: &mut BlockchainContext, ) -> Result> { let function = if multisig_type.is_multisig2() { crate::utils::wallets::multisig2::get_custodians() } else { crate::utils::wallets::multisig::get_custodians() }; - run_local(clock, function, account.into_owned()).and_then(parse_multisig_contract_custodians) + + let output = run_local(function, account.into_owned(), &[], false, context)?; + parse_multisig_contract_custodians(output) } fn parse_multisig_contract_custodians(tokens: Vec) -> Result> { @@ -571,10 +579,10 @@ fn parse_multisig_contract_custodians(tokens: Vec) -> Result, pending_transaction_id: u64, + context: &mut BlockchainContext, ) -> Result { #[derive(Copy, Clone)] pub struct MultisigTransactionId { @@ -607,7 +615,7 @@ pub fn find_pending_transaction( crate::utils::wallets::multisig::get_transactions() }; - let tokens = run_local(clock, function, account.into_owned())?; + let tokens = run_local(function, account.into_owned(), &[], false, context)?; let array = match tokens.into_iter().next().map(|v| v.value) { Some(AbiValue::Array(_, tokens)) => tokens, @@ -624,10 +632,10 @@ pub fn find_pending_transaction( } pub fn find_pending_update( - clock: &dyn Clock, multisig_type: MultisigType, account: Cow<'_, Account>, update_id: u64, + context: &mut BlockchainContext, ) -> Result> { use crate::utils::wallets::multisig2; @@ -637,7 +645,7 @@ pub fn find_pending_update( _ => return Ok(None), }; - let tokens = run_local(clock, function, account.into_owned())?; + let tokens = run_local(function, account.into_owned(), &[], false, context)?; let array = match tokens.into_iter().next().map(|v| v.value) { Some(AbiValue::Array(_, tokens)) => tokens, @@ -668,17 +676,17 @@ pub struct UpdatedParams { } pub fn get_pending_transactions( - clock: &dyn Clock, multisig_type: MultisigType, account: Cow<'_, Account>, custodians: &[HashBytes], + context: &mut BlockchainContext, ) -> Result> { let function = if multisig_type.is_multisig2() { crate::utils::wallets::multisig2::get_transactions() } else { crate::utils::wallets::multisig::get_transactions() }; - run_local(clock, function, account.into_owned()).and_then(|tokens| { + run_local(function, account.into_owned(), &[], false, context).and_then(|tokens| { let array = match tokens.into_iter().next().map(|v| v.value) { Some(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), @@ -699,10 +707,10 @@ pub fn get_pending_transactions( } pub fn get_pending_updates( - clock: &dyn Clock, multisig_type: MultisigType, account: Cow<'_, Account>, custodians: &[HashBytes], + context: &mut BlockchainContext, ) -> Result> { use crate::utils::wallets::multisig2; @@ -712,7 +720,7 @@ pub fn get_pending_updates( _ => return Ok(Vec::new()), }; - run_local(clock, function, account.into_owned()).and_then(|tokens| { + run_local(function, account.into_owned(), &[], false, context).and_then(|tokens| { let array = match tokens.into_iter().next().map(|v| v.value) { Some(AbiValue::Array(_, tokens)) => tokens, _ => return Err(UnpackerError::InvalidAbi.into()), From 53008009b03d02d6a7b2d2032af4ab9bf36071cc Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Fri, 13 Feb 2026 10:41:23 +0300 Subject: [PATCH 33/59] fix blockchain context --- src/client/ton/mod.rs | 20 +++++-- src/models/owners_cache.rs | 9 +-- .../monitoring/token_transaction_parser.rs | 21 ++++--- src/ton_core/ton_subscriber/mod.rs | 3 +- src/utils/token_wallet.rs | 59 ++++++++++--------- src/utils/token_wallets/models.rs | 10 ++-- 6 files changed, 72 insertions(+), 50 deletions(-) diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 1c30edb..dde4880 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -15,6 +15,7 @@ use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{GlobalCapabilities, OwnedMessage, SignatureContext, StdAddr}; use tycho_util::time::now_sec; use uuid::Uuid; +use nekoton_core::contracts::blockchain_context::BlockchainContextBuilder; use crate::api::*; use crate::models::*; @@ -522,7 +523,9 @@ impl TonClient { let root_account = root_address.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; - let token_address = get_token_wallet_address(&root_contract, owner)?; + let context = BlockchainContextBuilder::new().build()?; + + let token_address = get_token_wallet_address(root_contract, context.clone(), owner)?; let token_account = token_address.address; let token_contract = match self.ton_core.get_contract_state(&token_account) { Ok(contract) => contract, @@ -534,7 +537,11 @@ impl TonClient { } }; - let (version, network_balance) = get_token_wallet_basic_info(&token_contract)?; + let account_status = token_contract.account.state.clone().into(); + let last_transaction_hash = Some(token_contract.last_transaction_hash.to_string()); + let last_transaction_lt = Some(token_contract.account.last_trans_lt.to_string()); + + let (version, network_balance) = get_token_wallet_basic_info(token_contract, context)?; Ok(NetworkTokenAddressData { workchain_id: token_address.workchain as i32, @@ -542,9 +549,9 @@ impl TonClient { root_address: root_address.to_string(), version: version.to_string(), network_balance, - account_status: token_contract.account.state.into(), - last_transaction_hash: Some(token_contract.last_transaction_hash.to_string()), - last_transaction_lt: Some(token_contract.account.last_trans_lt.to_string()), + account_status, + last_transaction_hash, + last_transaction_lt, sync_u_time: 0, // TODO: fix }) } @@ -695,8 +702,9 @@ impl TonClient { let root_account = root_token.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; + let context = BlockchainContextBuilder::new().build()?; - let version = get_root_token_version(&root_contract)?; + let version = get_root_token_version(root_contract, context)?; let (value, _) = input.value.clone().as_bigint_and_exponent(); let tokens = value.to_biguint().ok_or(TonClientError::ParseBigUint)?; diff --git a/src/models/owners_cache.rs b/src/models/owners_cache.rs index 45e17e0..0ab1d69 100644 --- a/src/models/owners_cache.rs +++ b/src/models/owners_cache.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use lru::LruCache; use parking_lot::Mutex; +use tycho_types::cell::HashBytes; use tycho_types::models::StdAddr; use crate::models::sqlx::*; @@ -38,7 +39,7 @@ impl OwnersCache { )) .unwrap(), root_address: StdAddr::from_str(&got.root_address).unwrap(), - code_hash: got.code_hash, + code_hash: HashBytes::from_slice(&got.code_hash), version: got.version.into(), } } @@ -54,7 +55,7 @@ impl OwnersCache { owner_account_workchain_id: value.owner_address.workchain as i32, owner_account_hex: value.owner_address.address.to_string(), root_address: value.root_address.to_string(), - code_hash: value.code_hash, + code_hash: value.code_hash.as_array().to_vec(), created_at: chrono::Utc::now().naive_utc(), //doesn't matter version: value.version.into(), }; @@ -68,7 +69,7 @@ impl OwnersCache { pub struct OwnerInfo { pub owner_address: StdAddr, pub root_address: StdAddr, - pub code_hash: Vec, + pub code_hash: HashBytes, pub version: TokenWalletVersion, } @@ -90,7 +91,7 @@ impl OwnersCache { OwnerInfo { owner_address, root_address, - code_hash: x.code_hash, + code_hash: HashBytes::from_slice(&x.code_hash), version: x.version.into(), }, ); diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index 1e1571a..fa8bf03 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -2,6 +2,7 @@ use anyhow::Result; use bigdecimal::BigDecimal; use tycho_types::cell::Cell; use uuid::Uuid; +use nekoton_core::contracts::blockchain_context::BlockchainContextBuilder; use crate::ton_core::*; use crate::utils::token_wallets::models::{TokenIncomingTransfer, TokenWalletTransaction}; @@ -58,8 +59,9 @@ async fn internal_transfer_send( ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); + let context = BlockchainContextBuilder::new().build()?; let owner_info = - get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; + get_token_wallet_info(&address, &parse_ctx, token_transaction_ctx.token_state, context).await?; let mut message_hash = Default::default(); for message in token_transaction_ctx.transaction.iter_out_msgs() { @@ -105,9 +107,10 @@ async fn internal_transfer_receive( parse_ctx: ParseContext<'_>, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); + let context = BlockchainContextBuilder::new().build()?; let owner_info = - get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; + get_token_wallet_info(&address, &parse_ctx, token_transaction_ctx.token_state, context).await?; let message_hash = token_transaction_ctx .transaction @@ -148,9 +151,11 @@ async fn internal_transfer_bounced( parse_ctx: ParseContext<'_>, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); + + let context = BlockchainContextBuilder::new().build()?; let owner_info = - get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; + get_token_wallet_info(&address, &parse_ctx, token_transaction_ctx.token_state, context).await?; let message_hash = token_transaction_ctx .transaction @@ -190,8 +195,9 @@ async fn internal_transfer_mint( ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); + let context = BlockchainContextBuilder::new().build()?; let owner_info = - get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; + get_token_wallet_info(&address, &parse_ctx, token_transaction_ctx.token_state, context).await?; let message_hash = token_transaction_ctx .transaction @@ -227,15 +233,16 @@ async fn internal_transfer_mint( async fn get_token_wallet_info( contract_address: &StdAddr, parse_ctx: &ParseContext<'_>, - contract: &ExistingContract, + contract: ExistingContract, + context: BlockchainContext, ) -> Result { let res = match parse_ctx.owners_cache.get(contract_address).await { None => { - let (wallet, version, hash) = get_token_wallet_details(contract)?; + let (wallet, version, hash) = get_token_wallet_details(contract, context)?; let info = OwnerInfo { owner_address: wallet.owner_address, root_address: wallet.root_address, - code_hash: hash.to_vec(), + code_hash: hash, version, }; diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index db34f82..0c6b944 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -512,7 +512,8 @@ impl TokenSubscription { .find_account(&HashBytes::from_slice(account.as_slice()))? .ok_or_else(|| TonCoreError::AccountNotExist(account.to_string()))?; - let (token_wallet_details, ..) = get_token_wallet_details(&token_contract)?; + let context = BlockchainContextBuilder::new().build()?; + let (token_wallet_details, ..) = get_token_wallet_details(token_contract.clone(), context)?; let owner_account = &token_wallet_details.owner_address.address; if state_subscriptions.get(owner_account).is_some() { diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index cebaf5c..1be991e 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -5,6 +5,8 @@ use num_traits::ToPrimitive; use tycho_types::abi::AbiValue; use tycho_types::cell::{Cell, HashBytes}; use tycho_types::models::{AccountState, StdAddr}; +use nekoton_core::contracts::blockchain_context::BlockchainAccount; +use nekoton_core::contracts::blockchain_context::BlockchainContext; use crate::models::ExistingContract; use crate::utils::token_wallets::models::RootTokenContractState; @@ -151,12 +153,13 @@ pub fn prepare_token_mint( } pub fn get_token_wallet_address( - root_contract: &ExistingContract, + root_contract: ExistingContract, + context: BlockchainContext, owner: &StdAddr, ) -> Result { - let root_contract_state = RootTokenContractState(ExecutionContext { - account_stuff: &root_contract.account, - libraries: &[], + let mut root_contract_state = RootTokenContractState(&mut BlockchainAccount{ + account: root_contract.account, + context }); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; @@ -164,12 +167,13 @@ pub fn get_token_wallet_address( } pub fn get_token_wallet_account( - root_contract: &ExistingContract, + root_contract: ExistingContract, + context: BlockchainContext, owner: &StdAddr, ) -> Result { - let root_contract_state = RootTokenContractState(ExecutionContext { - account_stuff: &root_contract.account, - libraries: &[], + let mut root_contract_state = RootTokenContractState(&mut BlockchainAccount{ + account: root_contract.account, + context }); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; @@ -180,26 +184,23 @@ pub fn get_token_wallet_account( } pub fn get_token_wallet_basic_info( - token_contract: &ExistingContract, + token_contract: ExistingContract, + context: BlockchainContext ) -> Result<(TokenWalletVersion, BigDecimal)> { - let token_wallet_state = TokenWalletContractState(ExecutionContext { - account_stuff: &token_contract.account, - libraries: &[], + let mut token_wallet_state = TokenWalletContractState(&mut BlockchainAccount{ + account: token_contract.account, + context }); - let version = token_wallet_state.get_version()?; - let balance = BigDecimal::new(token_wallet_state.get_balance(version)?.into(), 0); + let balance = BigDecimal::new(token_wallet_state.get_balance(TokenWalletVersion::Tip3)?.into(), 0); - Ok((version, balance)) + Ok((TokenWalletVersion::Tip3, balance)) } pub fn get_token_wallet_details( - token_contract: &ExistingContract, + token_contract: ExistingContract, + context: BlockchainContext ) -> Result<(TokenWalletDetails, TokenWalletVersion, HashBytes)> { - let contract_state = TokenWalletContractState(ExecutionContext { - account_stuff: &token_contract.account, - libraries: &[], - }); let hash = match &token_contract.account.state { AccountState::Active(state_init) => { @@ -212,16 +213,20 @@ pub fn get_token_wallet_details( _ => anyhow::bail!("Wallet not deployed"), }; - let version = contract_state.get_version()?; - let details = contract_state.get_details(version)?; + let mut contract_state = TokenWalletContractState(&mut BlockchainAccount{ + account: token_contract.account, + context + }); + + let details = contract_state.get_details(TokenWalletVersion::Tip3)?; - Ok((details, version, hash)) + Ok((details, TokenWalletVersion::Tip3, hash)) } -pub fn get_root_token_version(root_contract: &ExistingContract, context: BlockchainContext) -> Result { - let root_contract_state = RootTokenContractState(ExecutionContext { - account_stuff: &root_contract.account, - libraries: &[], +pub fn get_root_token_version(root_contract: ExistingContract, context: BlockchainContext) -> Result { + let mut root_contract_state = RootTokenContractState(&mut BlockchainAccount{ + account: root_contract.account, + context, }); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs index be81682..c401047 100644 --- a/src/utils/token_wallets/models.rs +++ b/src/utils/token_wallets/models.rs @@ -125,7 +125,7 @@ pub enum Tip3Error { WalletNotDeployed, } -pub struct TokenWalletContractState<'a>(&'a mut BlockchainAccount); +pub struct TokenWalletContractState<'a>(pub &'a mut BlockchainAccount); impl TokenWalletContractState<'_> { pub fn get_balance(&mut self, version: TokenWalletVersion) -> anyhow::Result { @@ -166,7 +166,7 @@ impl TokenWalletContractState<'_> { } } -pub struct RootTokenContractState<'a>(&'a mut BlockchainAccount); +pub struct RootTokenContractState<'a>(pub &'a mut BlockchainAccount); impl RootTokenContractState<'_> { /// Calculates token wallet address @@ -222,7 +222,7 @@ impl RootTokenContractState<'_> { } } -pub struct RootTokenContract<'a>(&'a mut BlockchainAccount); +pub struct RootTokenContract<'a>(pub &'a mut BlockchainAccount); impl RootTokenContract<'_> { pub fn name(&mut self) -> anyhow::Result { @@ -399,7 +399,7 @@ impl RootTokenContract<'_> { } } -pub struct TokenWalletContract<'a>(&'a mut BlockchainAccount); +pub struct TokenWalletContract<'a>(pub &'a mut BlockchainAccount); impl TokenWalletContract<'_> { pub fn owner(&mut self) -> anyhow::Result { @@ -477,7 +477,7 @@ impl TokenWalletContract<'_> { } } -pub struct SidContract<'a>(&'a mut BlockchainAccount); +pub struct SidContract<'a>(pub &'a mut BlockchainAccount); impl SidContract<'_> { pub fn supports_interfaces(&mut self, interfaces: &[u32]) -> anyhow::Result { From 016ff4de3b156bdc45b2520feb8db16b382f0746 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Fri, 13 Feb 2026 14:09:04 +0300 Subject: [PATCH 34/59] fix token parser --- Cargo.lock | 745 +++++++++++------- Cargo.toml | 2 +- src/client/ton/mod.rs | 2 +- .../monitoring/token_transaction_parser.rs | 40 +- src/ton_core/ton_subscriber/mod.rs | 3 +- src/utils/token_wallet.rs | 51 +- src/utils/token_wallets/models.rs | 5 +- 7 files changed, 527 insertions(+), 321 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bcef1e..7dc01e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "version_check", ] @@ -87,7 +87,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -157,15 +157,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +dependencies = [ + "rustversion", +] [[package]] name = "argon2" @@ -198,7 +201,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -224,9 +227,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-sys", "zeroize", @@ -234,9 +237,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -332,7 +335,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -355,15 +358,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bigdecimal" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560f42649de9fa436b73517378a147ec21f6c997a546581df4b4b31677828934" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" dependencies = [ "autocfg", "libm", @@ -388,7 +391,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -411,15 +414,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "cpufeatures", "memmap2", "rayon-core", ] @@ -444,9 +448,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bumpalo-herd" @@ -465,9 +469,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -506,7 +510,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -521,9 +525,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.48" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -578,9 +582,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -614,9 +618,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", "clap_derive", @@ -624,9 +628,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -636,27 +640,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -684,9 +688,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" @@ -866,7 +870,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -900,7 +904,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -914,7 +918,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -925,7 +929,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -936,7 +940,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -968,9 +972,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "der" @@ -985,9 +989,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", ] @@ -1010,7 +1014,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", "unicode-xid", ] @@ -1043,7 +1047,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -1121,7 +1125,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -1195,9 +1199,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flume" @@ -1325,7 +1329,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -1376,15 +1380,15 @@ checksum = "271272f4aa3689fd08e21dc3d2656156a67271a0bc21a370c8cc9a7b212d51a0" dependencies = [ "futures-util", "hickory-client", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", ] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1407,6 +1411,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "glob" version = "0.3.3" @@ -1415,9 +1432,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1508,7 +1525,7 @@ dependencies = [ "once_cell", "radix_trie", "rand 0.9.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -1531,7 +1548,7 @@ dependencies = [ "once_cell", "rand 0.9.2", "ring", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tokio", "tracing", @@ -1589,9 +1606,9 @@ dependencies = [ [[package]] name = "hmac-sha256" -version = "1.1.12" +version = "1.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" [[package]] name = "hmac-sha512" @@ -1762,14 +1779,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -1778,7 +1794,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -1788,9 +1804,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1858,9 +1874,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1872,9 +1888,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -1891,6 +1907,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1920,9 +1942,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1947,9 +1969,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -1981,9 +2003,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -1997,9 +2019,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -2014,11 +2036,17 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.178" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libloading" @@ -2032,19 +2060,19 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", - "redox_syscall", + "redox_syscall 0.7.1", ] [[package]] @@ -2210,9 +2238,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -2247,7 +2275,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" dependencies = [ "ahash 0.8.12", - "portable-atomic 1.11.1", + "portable-atomic 1.13.1", ] [[package]] @@ -2286,7 +2314,7 @@ dependencies = [ "metrics 0.24.3", "metrics-util 0.20.1", "quanta", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -2348,9 +2376,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -2359,17 +2387,16 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.11" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", "parking_lot", - "portable-atomic 1.11.1", - "rustc_version", + "portable-atomic 1.13.1", "smallvec", "tagptr", "uuid", @@ -2394,14 +2421,14 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "6cdede44f9a69cab2899a2049e2c3bd49bf911a157f6a3353d4a91c61abbce44" dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -2412,7 +2439,7 @@ dependencies = [ [[package]] name = "nekoton-core" version = "0.0.2" -source = "git+https://github.com/broxus/tycho-nekoton.git?branch=feature%2Fupgrade-tycho-types#3ee48557f83ea1002c8417e428715c5022d93523" +source = "git+https://github.com/broxus/tycho-nekoton.git#9b9fe00e8accaf4ad0643c98dddba796d86e9c01" dependencies = [ "anyhow", "async-trait", @@ -2422,7 +2449,7 @@ dependencies = [ "num-traits", "pin-project", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tycho-executor", "tycho-types", @@ -2432,7 +2459,7 @@ dependencies = [ [[package]] name = "nekoton-utils" version = "0.0.2" -source = "git+https://github.com/broxus/tycho-nekoton.git?branch=feature%2Fupgrade-tycho-types#3ee48557f83ea1002c8417e428715c5022d93523" +source = "git+https://github.com/broxus/tycho-nekoton.git#9b9fe00e8accaf4ad0643c98dddba796d86e9c01" dependencies = [ "hex", "serde", @@ -2459,9 +2486,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -2503,9 +2530,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -2564,7 +2591,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" dependencies = [ "critical-section", "parking_lot_core", - "portable-atomic 1.11.1", + "portable-atomic 1.13.1", ] [[package]] @@ -2602,7 +2629,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -2611,6 +2638,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -2672,7 +2705,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link 0.2.1", ] @@ -2730,9 +2763,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -2740,9 +2773,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -2750,22 +2783,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", "sha2 0.10.9", @@ -2788,7 +2821,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -2863,14 +2896,14 @@ version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" dependencies = [ - "portable-atomic 1.11.1", + "portable-atomic 1.13.1", ] [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" @@ -2896,11 +2929,21 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.115", +] + [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -2945,8 +2988,8 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.6.1", - "thiserror 2.0.17", + "socket2 0.6.2", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -2967,7 +3010,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -2982,16 +3025,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -3030,7 +3073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3050,7 +3093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3059,14 +3102,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -3077,7 +3120,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3118,11 +3161,20 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -3132,9 +3184,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3143,15 +3195,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", @@ -3195,7 +3247,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3222,9 +3274,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest 0.10.7", @@ -3263,9 +3315,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", @@ -3276,9 +3328,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", @@ -3292,11 +3344,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.1", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -3304,9 +3356,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -3314,9 +3366,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -3332,9 +3384,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -3388,7 +3440,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -3472,7 +3524,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -3483,20 +3535,20 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -3600,10 +3652,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -3625,9 +3678,9 @@ checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slip10_ed25519" @@ -3659,9 +3712,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -3729,7 +3782,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -3747,7 +3800,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -3770,7 +3823,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.111", + "syn 2.0.115", "tokio", "url", ] @@ -3814,7 +3867,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "uuid", "whoami", @@ -3855,7 +3908,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "uuid", "whoami", @@ -3881,7 +3934,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", "uuid", @@ -3948,9 +4001,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -3974,7 +4027,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -4008,9 +4061,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", "core-foundation 0.9.4", @@ -4035,12 +4088,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4057,11 +4110,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4072,18 +4125,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -4128,30 +4181,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -4221,7 +4274,7 @@ dependencies = [ "digest 0.10.7", "sha2 0.10.9", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto-proc", ] @@ -4234,7 +4287,7 @@ dependencies = [ "proc-macro2", "quote", "rustc-hash 2.1.1", - "syn 2.0.111", + "syn 2.0.115", "tl-scheme", ] @@ -4248,14 +4301,14 @@ dependencies = [ "pest", "pest_derive", "rustc-hash 2.1.1", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -4263,7 +4316,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -4276,7 +4329,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -4301,9 +4354,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -4312,9 +4365,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -4326,9 +4379,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -4343,9 +4396,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags", "bytes", @@ -4376,9 +4429,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -4393,7 +4446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -4406,14 +4459,14 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -4505,7 +4558,7 @@ dependencies = [ "metrics 0.24.3", "parking_lot", "rayon", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto", "tycho-storage-traits", "tycho-types", @@ -4556,7 +4609,7 @@ dependencies = [ "sha2 0.10.9", "smallvec", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto", "tokio", "tracing", @@ -4588,14 +4641,14 @@ dependencies = [ [[package]] name = "tycho-executor" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737e032f9c6b68cdfe1a3433b151bca08dd81f66745a875e9d981fc6f5fdb2fb" +checksum = "a4ba91906bc028e800c91e232dfee409b5503e102f91d7931701d5b0d717e846" dependencies = [ "ahash 0.8.12", "anyhow", "num-bigint", - "thiserror 2.0.17", + "thiserror 2.0.18", "tycho-types", "tycho-vm", ] @@ -4631,8 +4684,8 @@ dependencies = [ "rustls-pki-types", "rustls-webpki", "serde", - "socket2 0.6.1", - "thiserror 2.0.17", + "socket2 0.6.2", + "thiserror 2.0.18", "tl-proto", "tokio", "tokio-util", @@ -4701,7 +4754,7 @@ dependencies = [ "serde_path_to_error", "sha2 0.10.9", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto", "tycho-crypto", "tycho-types-abi-proc", @@ -4717,7 +4770,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -4727,7 +4780,7 @@ source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -4758,7 +4811,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "sysinfo 0.37.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tikv-jemalloc-ctl", "tl-proto", "tokio", @@ -4780,14 +4833,14 @@ checksum = "723c91dccdb15a705b0448705f631c6e32298931013c054e54f6ffd9cb592341" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] name = "tycho-vm" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b4ae1dade385e5fbdca14e0439b1b7b518be19076324669785f4ef04d9e093" +checksum = "bef4b5767addc9c546991fe85519684444814527f317a3bd80a852b501250d71" dependencies = [ "ahash 0.8.12", "anyhow", @@ -4798,7 +4851,7 @@ dependencies = [ "num-integer", "num-traits", "sha2 0.10.9", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto", "tracing", "tycho-crypto", @@ -4808,14 +4861,14 @@ dependencies = [ [[package]] name = "tycho-vm-proc" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79fcef10732b88d30b15c517bbfa7f7bef240934a27f70261aacff8a8e57b3a" +checksum = "6aac25f611eb7ab6031a0a3c0ea14884989a7280073bb154b431a991f9adb779" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -4915,9 +4968,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-normalization" @@ -4970,9 +5023,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -4994,9 +5047,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -5049,9 +5102,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] @@ -5064,9 +5126,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -5077,11 +5139,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -5090,9 +5153,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5100,31 +5163,65 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -5149,7 +5246,7 @@ dependencies = [ "librocksdb-sys", "metrics 0.24.3", "rocksdb", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -5280,7 +5377,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -5291,7 +5388,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -5596,9 +5693,91 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn 2.0.115", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.115", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -5625,28 +5804,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -5666,7 +5845,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", "synstructure", ] @@ -5681,13 +5860,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] [[package]] @@ -5720,9 +5899,15 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.115", ] +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + [[package]] name = "zstd-safe" version = "7.2.4" diff --git a/Cargo.toml b/Cargo.toml index f735d48..5bbd85f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ tycho-util = { version = "0.3.6", features = ["cli"] } tycho-vm = "0.3.2" tycho-executor = "0.3.2" -nekoton-core = { git = "https://github.com/broxus/tycho-nekoton.git", branch = "feature/upgrade-tycho-types" } +nekoton-core = { git = "https://github.com/broxus/tycho-nekoton.git" } sysinfo = "0.30.13" diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index dde4880..5609cd4 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use axum::http::StatusCode; use bigdecimal::{BigDecimal, ToPrimitive}; use ed25519_dalek::VerifyingKey; +use nekoton_core::contracts::blockchain_context::BlockchainContextBuilder; use nekoton_core::contracts::function_ext::ExecutionOutput; use nekoton_core::contracts::function_ext::FunctionExt; use num_bigint::BigUint; @@ -15,7 +16,6 @@ use tycho_types::cell::{CellBuilder, HashBytes}; use tycho_types::models::{GlobalCapabilities, OwnedMessage, SignatureContext, StdAddr}; use tycho_util::time::now_sec; use uuid::Uuid; -use nekoton_core::contracts::blockchain_context::BlockchainContextBuilder; use crate::api::*; use crate::models::*; diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index fa8bf03..d3554ec 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -1,8 +1,8 @@ use anyhow::Result; use bigdecimal::BigDecimal; +use nekoton_core::contracts::blockchain_context::BlockchainContextBuilder; use tycho_types::cell::Cell; use uuid::Uuid; -use nekoton_core::contracts::blockchain_context::BlockchainContextBuilder; use crate::ton_core::*; use crate::utils::token_wallets::models::{TokenIncomingTransfer, TokenWalletTransaction}; @@ -60,8 +60,13 @@ async fn internal_transfer_send( let address = StdAddr::new(0, token_transaction_ctx.account); let context = BlockchainContextBuilder::new().build()?; - let owner_info = - get_token_wallet_info(&address, &parse_ctx, token_transaction_ctx.token_state, context).await?; + let owner_info = get_token_wallet_info( + &address, + &parse_ctx, + token_transaction_ctx.token_state, + context, + ) + .await?; let mut message_hash = Default::default(); for message in token_transaction_ctx.transaction.iter_out_msgs() { @@ -109,8 +114,13 @@ async fn internal_transfer_receive( let address = StdAddr::new(0, token_transaction_ctx.account); let context = BlockchainContextBuilder::new().build()?; - let owner_info = - get_token_wallet_info(&address, &parse_ctx, token_transaction_ctx.token_state, context).await?; + let owner_info = get_token_wallet_info( + &address, + &parse_ctx, + token_transaction_ctx.token_state, + context, + ) + .await?; let message_hash = token_transaction_ctx .transaction @@ -151,11 +161,16 @@ async fn internal_transfer_bounced( parse_ctx: ParseContext<'_>, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); - + let context = BlockchainContextBuilder::new().build()?; - let owner_info = - get_token_wallet_info(&address, &parse_ctx, token_transaction_ctx.token_state, context).await?; + let owner_info = get_token_wallet_info( + &address, + &parse_ctx, + token_transaction_ctx.token_state, + context, + ) + .await?; let message_hash = token_transaction_ctx .transaction @@ -196,8 +211,13 @@ async fn internal_transfer_mint( let address = StdAddr::new(0, token_transaction_ctx.account); let context = BlockchainContextBuilder::new().build()?; - let owner_info = - get_token_wallet_info(&address, &parse_ctx, token_transaction_ctx.token_state, context).await?; + let owner_info = get_token_wallet_info( + &address, + &parse_ctx, + token_transaction_ctx.token_state, + context, + ) + .await?; let message_hash = token_transaction_ctx .transaction diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index 0c6b944..4816e6e 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -513,7 +513,8 @@ impl TokenSubscription { .ok_or_else(|| TonCoreError::AccountNotExist(account.to_string()))?; let context = BlockchainContextBuilder::new().build()?; - let (token_wallet_details, ..) = get_token_wallet_details(token_contract.clone(), context)?; + let (token_wallet_details, ..) = + get_token_wallet_details(token_contract.clone(), context)?; let owner_account = &token_wallet_details.owner_address.address; if state_subscriptions.get(owner_account).is_some() { diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index 1be991e..c65d5e9 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -1,12 +1,12 @@ use anyhow::Result; use bigdecimal::BigDecimal; +use nekoton_core::contracts::blockchain_context::BlockchainAccount; +use nekoton_core::contracts::blockchain_context::BlockchainContext; use num_bigint::BigUint; use num_traits::ToPrimitive; use tycho_types::abi::AbiValue; use tycho_types::cell::{Cell, HashBytes}; use tycho_types::models::{AccountState, StdAddr}; -use nekoton_core::contracts::blockchain_context::BlockchainAccount; -use nekoton_core::contracts::blockchain_context::BlockchainContext; use crate::models::ExistingContract; use crate::utils::token_wallets::models::RootTokenContractState; @@ -157,10 +157,8 @@ pub fn get_token_wallet_address( context: BlockchainContext, owner: &StdAddr, ) -> Result { - let mut root_contract_state = RootTokenContractState(&mut BlockchainAccount{ - account: root_contract.account, - context - }); + let mut root_contract_state = + RootTokenContractState(&mut BlockchainAccount::new(context, root_contract.account)); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; root_contract_state.get_wallet_address(version, owner) @@ -171,10 +169,8 @@ pub fn get_token_wallet_account( context: BlockchainContext, owner: &StdAddr, ) -> Result { - let mut root_contract_state = RootTokenContractState(&mut BlockchainAccount{ - account: root_contract.account, - context - }); + let mut root_contract_state = + RootTokenContractState(&mut BlockchainAccount::new(context, root_contract.account)); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; let token_wallet_address = root_contract_state.get_wallet_address(version, owner)?; @@ -185,23 +181,25 @@ pub fn get_token_wallet_account( pub fn get_token_wallet_basic_info( token_contract: ExistingContract, - context: BlockchainContext + context: BlockchainContext, ) -> Result<(TokenWalletVersion, BigDecimal)> { - let mut token_wallet_state = TokenWalletContractState(&mut BlockchainAccount{ - account: token_contract.account, - context - }); + let mut token_wallet_state = + TokenWalletContractState(&mut BlockchainAccount::new(context, token_contract.account)); - let balance = BigDecimal::new(token_wallet_state.get_balance(TokenWalletVersion::Tip3)?.into(), 0); + let balance = BigDecimal::new( + token_wallet_state + .get_balance(TokenWalletVersion::Tip3)? + .into(), + 0, + ); Ok((TokenWalletVersion::Tip3, balance)) } pub fn get_token_wallet_details( token_contract: ExistingContract, - context: BlockchainContext + context: BlockchainContext, ) -> Result<(TokenWalletDetails, TokenWalletVersion, HashBytes)> { - let hash = match &token_contract.account.state { AccountState::Active(state_init) => { let code = state_init @@ -213,21 +211,20 @@ pub fn get_token_wallet_details( _ => anyhow::bail!("Wallet not deployed"), }; - let mut contract_state = TokenWalletContractState(&mut BlockchainAccount{ - account: token_contract.account, - context - }); + let mut contract_state = + TokenWalletContractState(&mut BlockchainAccount::new(context, token_contract.account)); let details = contract_state.get_details(TokenWalletVersion::Tip3)?; Ok((details, TokenWalletVersion::Tip3, hash)) } -pub fn get_root_token_version(root_contract: ExistingContract, context: BlockchainContext) -> Result { - let mut root_contract_state = RootTokenContractState(&mut BlockchainAccount{ - account: root_contract.account, - context, - }); +pub fn get_root_token_version( + root_contract: ExistingContract, + context: BlockchainContext, +) -> Result { + let mut root_contract_state = + RootTokenContractState(&mut BlockchainAccount::new(context, root_contract.account)); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; Ok(version) diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs index c401047..7a4f640 100644 --- a/src/utils/token_wallets/models.rs +++ b/src/utils/token_wallets/models.rs @@ -135,7 +135,10 @@ impl TokenWalletContractState<'_> { } } - pub fn get_details(&mut self, version: TokenWalletVersion) -> anyhow::Result { + pub fn get_details( + &mut self, + version: TokenWalletVersion, + ) -> anyhow::Result { Ok(match version { TokenWalletVersion::OldTip3v4 => { return Err(Tip3Error::UnknownVersion.into()); From 6c4e86468f4edeb675b899ba93a8e2ed8aaa989e Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 16 Feb 2026 10:12:04 +0300 Subject: [PATCH 35/59] fix from str for address and clippy warnings --- src/client/ton/mod.rs | 104 +++++++++++------- src/server.rs | 10 +- src/services/ton.rs | 41 ++++--- src/sqlx_client/transactions.rs | 5 +- src/ton_core/mod.rs | 2 +- .../monitoring/token_transaction_parser.rs | 2 +- src/utils/mnemonic/labs.rs | 1 + src/utils/mod.rs | 11 +- src/utils/token_wallets/mod.rs | 18 +-- src/utils/token_wallets/parsing.rs | 30 ++--- src/utils/ton_wallet/highload_wallet_v2.rs | 3 +- src/utils/ton_wallet/multisig.rs | 6 +- src/utils/ton_wallet/wallet_v3.rs | 3 +- src/utils/ton_wallet/wallet_v3v4.rs | 3 +- src/utils/ton_wallet/wallet_v5r1.rs | 7 +- src/utils/wallets/code/mod.rs | 4 +- src/utils/wallets/multisig.rs | 2 + src/utils/wallets/multisig2.rs | 3 + 18 files changed, 152 insertions(+), 103 deletions(-) diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 5609cd4..f4bfcf4 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -13,6 +13,7 @@ use tokio::sync::oneshot; use tycho_types::abi::{Function, NamedAbiValue, UnsignedExternalMessage}; use tycho_types::boc::Boc; use tycho_types::cell::{CellBuilder, HashBytes}; +use tycho_types::models::StdAddrFormat; use tycho_types::models::{GlobalCapabilities, OwnedMessage, SignatureContext, StdAddr}; use tycho_util::time::now_sec; use uuid::Uuid; @@ -189,7 +190,7 @@ impl TonClient { private_key: &[u8], ) -> Result, Error> { let mut key = [0u8; 32]; - key.copy_from_slice(&public_key); + key.copy_from_slice(public_key); let public_key = VerifyingKey::from_bytes(&key)?; let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; @@ -232,7 +233,7 @@ impl TonClient { }; let mut key = [0u8; 32]; - key.copy_from_slice(&private_key); + key.copy_from_slice(private_key); let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); @@ -277,19 +278,19 @@ impl TonClient { let bounce = transaction.bounce.unwrap_or_default(); let mut key = [0u8; 32]; - key.copy_from_slice(&public_key); + key.copy_from_slice(public_key); let public_key = VerifyingKey::from_bytes(&key)?; - let address = - StdAddr::from_str(&transaction.from_address.0).map_err(anyhow::Error::from)?; + let (address, _) = StdAddr::from_str_ext(&transaction.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; // parse input payload let body = transaction .payload - .map(|s| Boc::decode_base64(s)) + .map(Boc::decode_base64) .transpose() .map_err(anyhow::Error::from)?; @@ -301,8 +302,9 @@ impl TonClient { let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); - let destination = StdAddr::from_str(&item.recipient_address.0) - .map_err(anyhow::Error::from)?; + let (destination, _) = + StdAddr::from_str_ext(&item.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let amount = item .value .to_u128() @@ -332,8 +334,10 @@ impl TonClient { .outputs .first() .ok_or(TonClientError::RecipientNotFound)?; - let destination = StdAddr::from_str(&recipient.recipient_address.0) - .map_err(anyhow::Error::from)?; + let (destination, _) = + StdAddr::from_str_ext(&recipient.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let amount = recipient .value .to_u128() @@ -363,8 +367,9 @@ impl TonClient { .outputs .first() .ok_or(TonClientError::RecipientNotFound)?; - let destination = StdAddr::from_str(&recipient.recipient_address.0) - .map_err(anyhow::Error::from)?; + let (destination, _) = + StdAddr::from_str_ext(&recipient.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let amount = recipient .value .to_u128() @@ -399,8 +404,9 @@ impl TonClient { let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); - let destination = StdAddr::from_str(&item.recipient_address.0) - .map_err(anyhow::Error::from)?; + let (destination, _) = + StdAddr::from_str_ext(&item.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let amount = item .value .to_u128() @@ -425,7 +431,7 @@ impl TonClient { }; let mut key = [0u8; 32]; - key.copy_from_slice(&private_key); + key.copy_from_slice(private_key); let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); @@ -463,11 +469,12 @@ impl TonClient { private_key: &[u8], ) -> Result { let mut key = [0u8; 32]; - key.copy_from_slice(&public_key); + key.copy_from_slice(public_key); let public_key = VerifyingKey::from_bytes(&key)?; - let address = StdAddr::from_str(&transaction.address.0).map_err(anyhow::Error::from)?; + let (address, _) = StdAddr::from_str_ext(&transaction.address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let account_workchain_id = address.workchain as i32; let account_hex = address.address.to_string(); @@ -483,7 +490,7 @@ impl TonClient { )?; let mut key = [0u8; 32]; - key.copy_from_slice(&private_key); + key.copy_from_slice(private_key); let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); @@ -564,7 +571,8 @@ impl TonClient { account_type: &AccountType, custodians: &Option, ) -> Result { - let owner = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; + let (owner, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let token_owner_db = self .sqlx_client @@ -576,11 +584,17 @@ impl TonClient { .await?; let token_wallet = StdAddr::from_str(&token_owner_db.address).map_err(anyhow::Error::from)?; - let destination = - StdAddr::from_str(&input.recipient_address.0).map_err(anyhow::Error::from)?; + + let (destination, _) = + StdAddr::from_str_ext(&input.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => StdAddr::from_str(&send_gas_to.0).map_err(anyhow::Error::from)?, + Some(send_gas_to) => { + let (send_gas_to, _) = StdAddr::from_str_ext(&send_gas_to.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + send_gas_to + } None => owner.clone(), }; @@ -596,7 +610,7 @@ impl TonClient { let body = input .payload .as_ref() - .map(|s| Boc::decode_base64(s)) + .map(Boc::decode_base64) .transpose() .map_err(anyhow::Error::from)?; @@ -634,7 +648,8 @@ impl TonClient { account_type: &AccountType, custodians: &Option, ) -> Result { - let owner = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; + let (owner, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let token_owner_db = self .sqlx_client @@ -649,11 +664,16 @@ impl TonClient { StdAddr::from_str(&token_owner_db.address).map_err(anyhow::Error::from)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => StdAddr::from_str(&send_gas_to.0).map_err(anyhow::Error::from)?, + Some(send_gas_to) => { + let (send_gas_to, _) = StdAddr::from_str_ext(&send_gas_to.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + send_gas_to + } None => owner.clone(), }; - let callback_to = StdAddr::from_str(&input.callback_to.0).map_err(anyhow::Error::from)?; + let (callback_to, _) = StdAddr::from_str_ext(&input.callback_to.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let version = token_owner_db.version.into(); @@ -695,10 +715,13 @@ impl TonClient { account_type: &AccountType, custodians: &Option, ) -> Result { - let owner = StdAddr::from_str(&input.owner_address.0).map_err(anyhow::Error::from)?; - let root_token = StdAddr::from_str(&input.root_address.0).map_err(anyhow::Error::from)?; - let recipient = - StdAddr::from_str(&input.recipient_address.0).map_err(anyhow::Error::from)?; + let (owner, _) = StdAddr::from_str_ext(&input.owner_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let (root_token, _) = StdAddr::from_str_ext(&input.root_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let (recipient, _) = + StdAddr::from_str_ext(&input.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let root_account = root_token.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; @@ -718,7 +741,11 @@ impl TonClient { .ok_or(TonClientError::ParseBigUint)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => StdAddr::from_str(&send_gas_to.0).map_err(anyhow::Error::from)?, + Some(send_gas_to) => { + let (send_gas_to, _) = StdAddr::from_str_ext(&send_gas_to.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + send_gas_to + } None => owner.clone(), }; @@ -861,7 +888,7 @@ impl TonClient { .await?; let mut key = [0u8; 32]; - key.copy_from_slice(&private_key); + key.copy_from_slice(private_key); let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); let expire_at = unsigned_message.expire_at(); @@ -890,11 +917,12 @@ impl TonClient { params: Option>, ) -> Result { let mut key = [0u8; 32]; - key.copy_from_slice(&public_key); + key.copy_from_slice(public_key); let public_key = VerifyingKey::from_bytes(&key)?; - let address = StdAddr::from_str(&sender_addr).map_err(anyhow::Error::from)?; + let (address, _) = StdAddr::from_str_ext(sender_addr, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; @@ -907,7 +935,9 @@ impl TonClient { .transpose() .map_err(anyhow::Error::from)?; - let destination = StdAddr::from_str(&target_addr).map_err(anyhow::Error::from)?; + let (destination, _) = StdAddr::from_str_ext(target_addr, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let amount = value.to_u128().ok_or(TonClientError::ParseBigDecimal)?; let unsigned_message = match account_type { AccountType::Wallet => { @@ -1061,7 +1091,7 @@ fn build_token_transaction( let body = Some(internal_message.body); let mut key = [0u8; 32]; - key.copy_from_slice(&public_key); + key.copy_from_slice(public_key); let public_key = VerifyingKey::from_bytes(&key)?; @@ -1159,7 +1189,7 @@ fn build_token_transaction( }; let mut key = [0u8; 32]; - key.copy_from_slice(&private_key); + key.copy_from_slice(private_key); let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); diff --git a/src/server.rs b/src/server.rs index a18ce98..8aebaf4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -41,10 +41,12 @@ impl EngineContext { .max_connections(config.db_pool_size) .connect(&config.database_url) .await - .expect(&format!( - "Failed connection to database url - {}", - config.database_url - )); + .unwrap_or_else(|_| { + panic!( + "Failed connection to database url - {}", + config.database_url + ) + }); sqlx::migrate!().run(&pool).await?; diff --git a/src/services/ton.rs b/src/services/ton.rs index c989a48..2db6264 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -12,7 +12,7 @@ use tycho_types::abi::{ }; use tycho_types::boc::Boc; use tycho_types::cell::{CellBuilder, HashBytes}; -use tycho_types::models::{OwnedMessage, StdAddr}; +use tycho_types::models::{OwnedMessage, StdAddr, StdAddrFormat}; use uuid::Uuid; use crate::api::*; @@ -106,7 +106,8 @@ impl TonService { service_id: &ServiceId, address: Address, ) -> Result<(AddressDb, NetworkAddressData), Error> { - let account = StdAddr::from_str(&address.0).map_err(anyhow::Error::from)?; + let (account, _) = + StdAddr::from_str_ext(&address.0, StdAddrFormat::any()).map_err(anyhow::Error::from)?; let address = self .sqlx_client .get_address( @@ -125,7 +126,8 @@ impl TonService { service_id: &ServiceId, address: Address, ) -> Result { - let account = StdAddr::from_str(&address.0).map_err(anyhow::Error::from)?; + let (account, _) = + StdAddr::from_str_ext(&address.0, StdAddrFormat::any()).map_err(anyhow::Error::from)?; let address = self .sqlx_client .get_address( @@ -143,7 +145,8 @@ impl TonService { service_id: &ServiceId, input: TransactionSend, ) -> Result { - let address = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; + let (address, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let network = self.ton_api_client.get_address_info(&address).await?; for transaction_output in input.outputs.iter() { @@ -227,7 +230,8 @@ impl TonService { service_id: &ServiceId, input: TransactionConfirm, ) -> Result { - let address = StdAddr::from_str(&input.address.0).map_err(anyhow::Error::from)?; + let (address, _) = StdAddr::from_str_ext(&input.address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let address_db = self .sqlx_client @@ -542,7 +546,8 @@ impl TonService { service_id: &ServiceId, address: &Address, ) -> Result, Error> { - let account = StdAddr::from_str(&address.0).map_err(anyhow::Error::from)?; + let (account, _) = + StdAddr::from_str_ext(&address.0, StdAddrFormat::any()).map_err(anyhow::Error::from)?; let balances = self .sqlx_client .get_token_balances( @@ -578,7 +583,9 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; + let (owner, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let address_db = self .sqlx_client .get_address( @@ -675,7 +682,9 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = StdAddr::from_str(&input.from_address.0).map_err(anyhow::Error::from)?; + let (owner, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let address_db = self .sqlx_client .get_address( @@ -777,7 +786,9 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = StdAddr::from_str(&input.owner_address.0).map_err(anyhow::Error::from)?; + let (owner, _) = StdAddr::from_str_ext(&input.owner_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let address_db = self .sqlx_client .get_address( @@ -885,7 +896,7 @@ impl TonService { headers: Vec, responsible: bool, ) -> Result, Error> { - let account_addr = HashBytes::from_str(&account_addr).map_err(anyhow::Error::from)?; + let account_addr = HashBytes::from_str(account_addr).map_err(anyhow::Error::from)?; let input_params: Vec = inputs.iter().map(|x| x.abi_type.clone()).collect(); @@ -970,7 +981,8 @@ impl TonService { None => (None, None), }; - let sender = StdAddr::from_str(&sender_addr).map_err(anyhow::Error::from)?; + let (sender, _) = StdAddr::from_str_ext(sender_addr, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let address_db = self .sqlx_client @@ -1136,7 +1148,8 @@ impl TonService { owned_message: OwnedMessage, expire_at: u32, ) -> Result { - let addr = StdAddr::from_str(&sender_addr).map_err(anyhow::Error::from)?; + let (addr, _) = StdAddr::from_str_ext(&sender_addr, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; self.ton_api_client .add_ton_account_subscription(addr.address); @@ -1159,7 +1172,9 @@ impl TonService { } pub async fn add_account_subscription(self: &Arc, address: String) -> Result<(), Error> { - let addr = StdAddr::from_str(&address).map_err(anyhow::Error::from)?; + let (addr, _) = + StdAddr::from_str_ext(&address, StdAddrFormat::any()).map_err(anyhow::Error::from)?; + self.ton_api_client .add_ton_account_subscription(addr.address); Ok(()) diff --git a/src/sqlx_client/transactions.rs b/src/sqlx_client/transactions.rs index 4e090fd..ddcc82e 100644 --- a/src/sqlx_client/transactions.rs +++ b/src/sqlx_client/transactions.rs @@ -1,8 +1,7 @@ -use std::str::FromStr; - use anyhow::{Context, Result}; use chrono::prelude::*; use tycho_types::models::StdAddr; +use tycho_types::models::StdAddrFormat; use uuid::Uuid; use crate::models::*; @@ -713,7 +712,7 @@ pub fn filter_transaction_query( } if let Some(account) = account { - if let Ok(account) = StdAddr::from_str(&account) { + if let Ok((account, _)) = StdAddr::from_str_ext(&account, StdAddrFormat::any()) { updates.push(format!(" AND account_workchain_id = ${} ", *args_len + 1,)); *args_len += 1; args.add(account.workchain).map_err(sqlx::Error::Encode)?; diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index bf5a362..53bb62a 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -248,7 +248,7 @@ impl TonCoreContext { let config_cell = Boc::decode_base64("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").unwrap(); let config = config_cell.parse::()?; - let config = ParsedConfig::parse(config, now_sec() as u32)?; + let config = ParsedConfig::parse(config, now_sec())?; let cell = Boc::decode_base64(message_base64)?; let mut cs = cell.as_slice()?; diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index d3554ec..2e1b62f 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -94,7 +94,7 @@ async fn internal_transfer_send( sender_hex: None, root_address: owner_info.root_address.to_string(), value: -BigDecimal::new(tokens.into(), 0), - payload: payload_cell.map(|c| Boc::encode(c)), + payload: payload_cell.map(Boc::encode), block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Send, diff --git a/src/utils/mnemonic/labs.rs b/src/utils/mnemonic/labs.rs index c483530..6c3c05c 100644 --- a/src/utils/mnemonic/labs.rs +++ b/src/utils/mnemonic/labs.rs @@ -4,6 +4,7 @@ use anyhow::Result; use super::{Bip39MnemonicData, LANGUAGE}; +#[allow(unused)] pub fn derive_master_key(phrase: &str) -> Result<[u8; 64]> { let mnemonic = bip39::Mnemonic::from_phrase(phrase, LANGUAGE)?; let hd = bip39::Seed::new(&mnemonic, ""); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9991770..82be48c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -106,11 +106,9 @@ pub mod serde_string { } pub mod serde_address { - use std::str::FromStr; - use serde::de::Error; use serde::Deserialize; - use tycho_types::models::StdAddr; + use tycho_types::models::{StdAddr, StdAddrFormat}; pub fn serialize(data: &StdAddr, serializer: S) -> Result where @@ -124,7 +122,10 @@ pub mod serde_address { D: serde::Deserializer<'de>, { let data = String::deserialize(deserializer)?; - StdAddr::from_str(&data).map_err(|_| D::Error::custom("Invalid address")) + + let (address, _) = StdAddr::from_str_ext(&data, StdAddrFormat::any()) + .map_err(|_| D::Error::custom("Invalid address"))?; + Ok(address) } } @@ -216,7 +217,7 @@ pub trait FromAbiPlain: FromAbi { pub trait WithAbiTypePlain: WithAbiType { fn abi_type_plain() -> Vec { match Self::abi_type() { - AbiType::Tuple(tuple) => tuple.into_iter().cloned().collect(), + AbiType::Tuple(tuple) => tuple.iter().cloned().collect(), _ => vec![], } } diff --git a/src/utils/token_wallets/mod.rs b/src/utils/token_wallets/mod.rs index a79d7ed..ad764b9 100644 --- a/src/utils/token_wallets/mod.rs +++ b/src/utils/token_wallets/mod.rs @@ -128,9 +128,9 @@ impl TransferInputs { Ok(Self { amount: amount.to_u128().unwrap(), - recipient: recipient, + recipient, deploy_wallet_value: deploy_wallet_value.to_u128().unwrap(), - remaining_gas_to: remaining_gas_to, + remaining_gas_to, notify: *notify, payload: payload.clone(), }) @@ -243,8 +243,8 @@ impl TransferToWalletInputs { Ok(Self { amount: amount.to_u128().unwrap(), - recipient_token_wallet: recipient_token_wallet, - remaining_gas_to: remaining_gas_to, + recipient_token_wallet, + remaining_gas_to, notify: *notify, payload: payload.clone(), }) @@ -355,8 +355,8 @@ impl AcceptTransferInputs { Ok(Self { amount: amount.to_u128().unwrap(), - sender: sender, - remaining_gas_to: remaining_gas_to, + sender, + remaining_gas_to, notify: *notify, payload: payload.clone(), }) @@ -454,7 +454,7 @@ impl AcceptMintInputs { Ok(Self { amount: amount.to_u128().unwrap(), - remaining_gas_to: remaining_gas_to, + remaining_gas_to, notify: *notify, payload: payload.clone(), }) @@ -563,8 +563,8 @@ pub mod burnable { Ok(Self { amount: amount.to_u128().unwrap(), - remaining_gas_to: remaining_gas_to, - callback_to: callback_to, + remaining_gas_to, + callback_to, payload: payload.clone(), }) } diff --git a/src/utils/token_wallets/parsing.rs b/src/utils/token_wallets/parsing.rs index afc6221..f12bbdd 100644 --- a/src/utils/token_wallets/parsing.rs +++ b/src/utils/token_wallets/parsing.rs @@ -50,7 +50,9 @@ pub fn parse_token_transaction( return None; }; - let functions = TokenWalletFunctions::for_version(version); + let Ok(functions) = TokenWalletFunctions::for_version(version) else { + return None; + }; if info.bounced { body_slice.skip_first(32, 0).ok()?; @@ -61,13 +63,13 @@ pub fn parse_token_transaction( if function_id == functions.accept_transfer.input_id { return Some(TokenWalletTransaction::TransferBounced( - body_slice.load_u128().ok()?.into(), + body_slice.load_u128().ok()?, )); } if function_id == functions.accept_burn.input_id { Some(TokenWalletTransaction::SwapBackBounced( - body_slice.load_u128().ok()?.into(), + body_slice.load_u128().ok()?, )) } else { None @@ -140,21 +142,21 @@ struct TokenWalletFunctions { } impl TokenWalletFunctions { - pub fn for_version(version: TokenWalletVersion) -> &'static TokenWalletFunctions { + pub fn for_version( + version: TokenWalletVersion, + ) -> Result<&'static TokenWalletFunctions, UnpackerError> { match version { - TokenWalletVersion::OldTip3v4 => { - unimplemented!() - } + TokenWalletVersion::OldTip3v4 => Err(UnpackerError::InvalidAbi), TokenWalletVersion::Tip3 => { static IDS: OnceLock = OnceLock::new(); - IDS.get_or_init(|| Self { + Ok(IDS.get_or_init(|| Self { accept_mint: token_wallets::accept_mint(), transfer: token_wallets::transfer(), transfer_to_wallet: token_wallets::transfer_to_wallet(), accept_transfer: token_wallets::accept_transfer(), burn: token_wallets::burnable::burn(), accept_burn: token_wallets::accept_burn(), - }) + })) } } } @@ -299,14 +301,4 @@ mod tests { let parsed = parse_token_transaction(&transaction, &description, TokenWalletVersion::Tip3); println!("parsed tx: {parsed:#?}"); } - - #[test] - fn test_parse_bounced_tokens_transfer() { - let (tx, description) = parse_transaction("te6ccgECCQEAAiEAA7V9jKvgMYxeLukedeW/PRr7QyRzEpkal33nb9KfgpelA3AAAO1mmxCMEy4UbEGiIQKVpE2nzO2Ar32k7H36ni1NMpxrcPorUNuwAADtZo+e3BYO9BHwADRwGMkIBQQBAhcMSgkCmI36GG92AhEDAgBvyYehIEwUWEAAAAAAAAQAAgAAAAKLF5Ge7DorMQ9dbEzZTgWK7Jiugap8s4dRpkiQl7CNEEBQFgwAnkP1TAqiBAAAAAAAAAAAtgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgnIBZa/nTbAD2Vcr8A6p+uT7XD4tLowmBLZEuIHLxU1zbeHGgHFi5dfeWnrNgtL3FHE6zw6ysjTJJI3LFFDAgPi3AgHgCAYBAd8HALFoAbGVfAYxi8XdI868t+ejX2hkjmJTI1LvvO36U/BS9KBvABgzjiRJUfoXsV99CuD/WnKK4QN5mlferMiVbk0Y3Jc3ECddFmAGFFhgAAAdrNNiEYTB3oI+QAD5WAHF6/YBDYNj7TABzedO3/4+ENpaE0PhwRx5NFYisFNfpQA2Mq+AxjF4u6R515b89GvtDJHMSmRqXfedv0p+Cl6UDdApiN+gBhRYYAAAHazSjHIEwd6CFH////+MaQuBAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEA="); - - assert!(matches!( - parse_token_transaction(&tx, &description, TokenWalletVersion::OldTip3v4).unwrap(), - TokenWalletTransaction::TransferBounced(_) - )); - } } diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 5c75348..1794791 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -78,7 +78,7 @@ pub fn prepare_transfer( current_state .address .as_std() - .ok_or_else(|| HighloadWalletV2Error::InvalidAddress)? + .ok_or(HighloadWalletV2Error::InvalidAddress)? .clone(), ); if with_state_init { @@ -89,6 +89,7 @@ pub fn prepare_transfer( Ok(unsigned_message) } +#[allow(unused)] struct UnsignedHighloadWalletV2Message { init_data: InitData, gifts: Vec, diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index a90ceee..7909d71 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -449,7 +449,7 @@ pub fn prepare_state_init( key_builder.store_u64(0)?; let cell_builder = CellBuilder::from_raw_data(public_key.as_bytes(), 256)?; let data_slice = cell_builder.as_data_slice(); - result.set(key_builder.as_data_slice(), &data_slice)?; + result.set(key_builder.as_data_slice(), data_slice)?; // Encode init data as mapping let cell = CellBuilder::build_from(result)?; @@ -759,7 +759,7 @@ fn extend_pending_transaction( creator: tx.creator, index: tx.index, dest: tx.dest, - value: tx.value.into(), + value: tx.value, send_flags: tx.send_flags, payload: tx.payload, bounce: tx.bounce, @@ -791,7 +791,7 @@ fn extend_pending_update( } fn make_ext_message( - public_key: &VerifyingKey, + _public_key: &VerifyingKey, address: StdAddr, expire_at: u32, function: &'static Function, diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index 8dd4a50..bfc1f84 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -122,7 +122,7 @@ pub fn prepare_transfer( current_state .address .as_std() - .ok_or_else(|| WalletV3Error::InvalidAddress)? + .ok_or(WalletV3Error::InvalidAddress)? .clone(), ); if with_state_init { @@ -133,6 +133,7 @@ pub fn prepare_transfer( Ok(unsigned_message) } +#[allow(unused)] struct UnsignedWalletV3Message { init_data: InitData, gifts: Vec, diff --git a/src/utils/ton_wallet/wallet_v3v4.rs b/src/utils/ton_wallet/wallet_v3v4.rs index 08a5f3f..f880c1e 100644 --- a/src/utils/ton_wallet/wallet_v3v4.rs +++ b/src/utils/ton_wallet/wallet_v3v4.rs @@ -81,7 +81,7 @@ pub fn prepare_transfer( current_state .address .as_std() - .ok_or_else(|| WalletV4Error::InvalidAddress)? + .ok_or(WalletV4Error::InvalidAddress)? .clone(), ); if with_state_init { @@ -92,6 +92,7 @@ pub fn prepare_transfer( Ok(unsigned_message) } +#[allow(unused)] struct UnsignedWallet { init_data: InitData, gifts: Vec, diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index cbaa069..e010fb7 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -57,9 +57,9 @@ pub fn get_init_data( match current_state.state { AccountState::Active(ref state_init) => match &state_init.data { Some(data) => Ok((InitData::try_from(data)?, false)), - None => return Err(WalletV5Error::InvalidInitData.into()), + None => Err(WalletV5Error::InvalidInitData.into()), }, - AccountState::Frozen { .. } => return Err(WalletV5Error::AccountIsFrozen.into()), + AccountState::Frozen { .. } => Err(WalletV5Error::AccountIsFrozen.into()), AccountState::Uninit => Ok((make_init_data(public_key), true)), } } @@ -97,7 +97,7 @@ pub fn prepare_transfer( current_state .address .as_std() - .ok_or_else(|| WalletV5Error::InvalidAddress)? + .ok_or(WalletV5Error::InvalidAddress)? .clone(), ); if with_state_init { @@ -108,6 +108,7 @@ pub fn prepare_transfer( Ok(unsigned_message) } +#[allow(unused)] struct UnsignedWalletV5 { init_data: InitData, gifts: Vec, diff --git a/src/utils/wallets/code/mod.rs b/src/utils/wallets/code/mod.rs index 4b27b57..3379d90 100644 --- a/src/utils/wallets/code/mod.rs +++ b/src/utils/wallets/code/mod.rs @@ -29,6 +29,6 @@ declare_tvc! { ever_wallet => "./ever_wallet_code.boc" (EVER_WALLET_CODE), } -fn load(mut data: &[u8]) -> Cell { - Boc::decode(&mut data).expect("Trust me") +fn load(data: &[u8]) -> Cell { + Boc::decode(data).expect("Trust me") } diff --git a/src/utils/wallets/multisig.rs b/src/utils/wallets/multisig.rs index 505f1a5..b60beec 100644 --- a/src/utils/wallets/multisig.rs +++ b/src/utils/wallets/multisig.rs @@ -117,6 +117,7 @@ pub fn get_custodians() -> &'static Function { } } +#[allow(unused)] pub mod safe_multisig { use crate::utils::WithAbiTypePlain; @@ -144,6 +145,7 @@ pub mod safe_multisig { } } +#[allow(unused)] pub mod set_code_multisig { use crate::utils::WithAbiTypePlain; diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs index 3742f51..e8eb054 100644 --- a/src/utils/wallets/multisig2.rs +++ b/src/utils/wallets/multisig2.rs @@ -69,6 +69,7 @@ pub fn confirm_transaction() -> &'static Function { } } +#[allow(unused)] #[derive(Debug, WithAbiType)] pub struct MultisigTransaction { pub id: u64, @@ -97,6 +98,7 @@ pub fn get_transactions() -> &'static Function { } } +#[allow(unused)] #[derive(Debug, Clone, Copy, WithAbiType)] pub struct MultisigCustodian { pub index: u8, @@ -181,6 +183,7 @@ pub fn execute_update() -> &'static Function { } } +#[allow(unused)] #[derive(Debug, Clone, Copy)] pub struct SetCodeMultisigParams { pub max_queued_transactions: u8, From 285438b7bc69c77038422ddc8f47ba2983ac64e5 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 16 Feb 2026 10:45:03 +0300 Subject: [PATCH 36/59] upgrade axum --- Cargo.lock | 308 ++++++++++++--------------- Cargo.toml | 9 +- src/api/controllers/authorization.rs | 11 +- src/api/docs.rs | 4 +- src/api/mod.rs | 15 +- 5 files changed, 152 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7dc01e2..847e655 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "aide" -version = "0.13.5" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5678d2978845ddb4bd736a026f467dd652d831e9e6254b0e41b07f7ee7523309" +checksum = "6966317188cdfe54c58c0900a195d021294afb3ece9b7073d09e4018dbb1e3a2" dependencies = [ "aide-macros", "axum", @@ -72,7 +72,7 @@ dependencies = [ "serde", "serde_json", "serde_qs", - "thiserror 1.0.69", + "thiserror 2.0.18", "tower-layer", "tower-service", "tracing", @@ -80,14 +80,14 @@ dependencies = [ [[package]] name = "aide-macros" -version = "0.7.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0487f8598afe49e6bc950a613a678bd962c4a6f431022ded62643c8b990301a" +checksum = "9f2a08f14808f3c46f3e3004b727bace64af44c3c5996d0480a14d3852b1b25a" dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -163,9 +163,9 @@ checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "arc-swap" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" dependencies = [ "rustversion", ] @@ -201,7 +201,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -249,14 +249,14 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.9" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "async-trait", "axum-core", "axum-macros", "bytes", + "form_urlencoded", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -270,8 +270,7 @@ dependencies = [ "multer", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", @@ -285,19 +284,17 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -306,36 +303,35 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.6" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" +checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" dependencies = [ "axum", "axum-core", "bytes", - "fastrand", "futures-util", "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", - "multer", "pin-project-lite", - "serde", - "tower", + "rustversion", + "serde_core", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-macros" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -391,14 +387,14 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -647,7 +643,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -870,7 +866,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -904,7 +900,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -918,7 +914,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -929,7 +925,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -940,7 +936,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1014,7 +1010,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "unicode-xid", ] @@ -1047,7 +1043,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1125,7 +1121,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -1264,9 +1260,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1279,9 +1275,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1289,15 +1285,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1317,38 +1313,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1358,7 +1354,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1814,7 +1809,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -2222,9 +2217,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" @@ -2244,9 +2239,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -2421,17 +2416,17 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cdede44f9a69cab2899a2049e2c3bd49bf911a157f6a3353d4a91c61abbce44" +checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.1.6", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -2629,15 +2624,9 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -2791,7 +2780,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -2821,7 +2810,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -2936,7 +2925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3170,6 +3159,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.116", +] + [[package]] name = "regex" version = "1.12.3" @@ -3348,10 +3357,10 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -3417,14 +3426,15 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.22" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ "bigdecimal", "chrono", "dyn-clone", "indexmap", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -3433,14 +3443,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "5016d94c77c6d32f0b8e08b781f7dc8a90c2007d4e77472cc2807bc10a8438fe" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3457,22 +3467,9 @@ checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -3483,9 +3480,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", @@ -3524,7 +3521,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3535,7 +3532,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3564,15 +3561,15 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" +checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6" dependencies = [ "axum", "futures", "percent-encoding", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -3587,19 +3584,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha1" version = "0.10.6" @@ -3800,7 +3784,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -3823,7 +3807,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.115", + "syn 2.0.116", "tokio", "url", ] @@ -4001,9 +3985,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.115" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -4027,7 +4011,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4125,7 +4109,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4136,7 +4120,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4287,7 +4271,7 @@ dependencies = [ "proc-macro2", "quote", "rustc-hash 2.1.1", - "syn 2.0.115", + "syn 2.0.116", "tl-scheme", ] @@ -4329,7 +4313,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4459,7 +4443,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4770,7 +4754,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4780,7 +4764,7 @@ source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4833,7 +4817,7 @@ checksum = "723c91dccdb15a705b0448705f631c6e32298931013c054e54f6ffd9cb592341" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4868,7 +4852,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -4892,7 +4876,7 @@ dependencies = [ "futures-util", "hex", "hmac-sha256", - "http 0.2.12", + "http 1.4.0", "itertools 0.10.5", "lazy_static", "log", @@ -4914,7 +4898,6 @@ dependencies = [ "schemars", "serde", "serde_json", - "serde_yaml", "sha2 0.10.9", "slip10_ed25519", "sqlx", @@ -4968,9 +4951,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -5009,12 +4992,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.9.0" @@ -5047,11 +5024,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -5170,7 +5147,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "wasm-bindgen-shared", ] @@ -5282,7 +5259,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -5345,19 +5322,6 @@ dependencies = [ "windows-strings 0.4.2", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-future" version = "0.2.1" @@ -5377,7 +5341,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5388,7 +5352,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5721,7 +5685,7 @@ dependencies = [ "heck 0.5.0", "indexmap", "prettyplease", - "syn 2.0.115", + "syn 2.0.116", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5737,7 +5701,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5804,7 +5768,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -5825,7 +5789,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5845,7 +5809,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", "synstructure", ] @@ -5866,7 +5830,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] @@ -5899,7 +5863,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.116", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5bbd85f..9e5afcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,9 @@ publish = false license-file = "LICENSE" [dependencies] -aide = { version = "0.13.5", features = ["axum", "axum-extra", "scalar", "macros"] } +aide = { version = "0.15.1", features = ["axum", "axum-extra", "axum-json", "scalar", "macros"] } anyhow = "1.0" -axum = { version = "0.7", features = ["multipart", "macros"] } +axum = { version = "0.8.8", features = ["multipart", "macros"] } argon2 = "0.4.1" async-trait = "0.1" base64 = "0.13" @@ -23,7 +23,7 @@ futures = "0.3" futures-util = "0.3.31" hex = "0.4" hmac-sha256 = "1.1.4" -http = "0.2" +http = "1" itertools = "0.10.1" lazy_static = "1.4.0" log = { version = "0.4", features = ["std", "serde"] } @@ -41,10 +41,9 @@ rayon = "1.10" regex = "1.5" reqwest = { version = "0.12", features = ["json"] } rustc-hash = "1.1.0" -schemars = { version = "0.8.13", features = ["chrono", "bigdecimal04", "uuid1"] } +schemars = { version = "0.9.0", features = ["chrono04", "bigdecimal04", "uuid1"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml = "0.9.4" sha2 = { version = "0.10.8" } slip10_ed25519 = "0.1.3" sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres", "uuid", "bigdecimal", "chrono", "json"] } diff --git a/src/api/controllers/authorization.rs b/src/api/controllers/authorization.rs index 288c470..03febe7 100644 --- a/src/api/controllers/authorization.rs +++ b/src/api/controllers/authorization.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use aide::{OperationInput, OperationOutput}; -use axum::async_trait; use axum::body::Body; use axum::extract::{FromRequest, FromRequestParts, OriginalUri}; use axum::http::request::Parts; @@ -10,6 +9,7 @@ use axum::http::{Method, StatusCode}; use axum::middleware::Next; use axum::response::IntoResponse; use schemars::JsonSchema; +use std::future::ready; use crate::api::int_schema; use crate::models::*; @@ -103,25 +103,24 @@ async fn check_api_key( #[derive(Debug, Clone)] pub struct IdExtractor(pub ServiceId); -#[async_trait] impl FromRequestParts for IdExtractor where S: Send + Sync, { type Rejection = Rejection; - async fn from_request_parts( + fn from_request_parts( parts: &mut Parts, _state: &S, - ) -> Result { + ) -> impl std::future::Future> + Send { let id: Option<&IdExtractor> = parts.extensions.get(); - match id { + ready(match id { Some(service_id) => Ok(IdExtractor(service_id.0)), None => Err(Rejection { reason: "Service id not found".to_string(), status_code: StatusCode::UNAUTHORIZED, }), - } + }) } } diff --git a/src/api/docs.rs b/src/api/docs.rs index cac5d58..f2daa78 100644 --- a/src/api/docs.rs +++ b/src/api/docs.rs @@ -14,7 +14,7 @@ pub fn route() -> ApiRouter { // As a result, the `serve_redoc` route will // have the `text/html` content-type correctly set // with a 200 status. - aide::gen::infer_responses(true); + aide::generate::infer_responses(true); let router = ApiRouter::new() .route( @@ -27,7 +27,7 @@ pub fn route() -> ApiRouter { // Afterwards we disable response inference because // it might be incorrect for other routes. - aide::gen::infer_responses(false); + aide::generate::infer_responses(false); router } diff --git a/src/api/mod.rs b/src/api/mod.rs index ebe06c4..5793a63 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -11,7 +11,7 @@ use axum::http::Method; use futures_util::future::BoxFuture; use metrics::{describe_counter, describe_histogram}; use metrics_exporter_prometheus::Matcher; -use schemars::schema::{InstanceType, SchemaObject}; +use schemars::{json_schema, Schema, SchemaGenerator}; use tower::ServiceBuilder; use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin, CorsLayer}; use tower_http::trace::TraceLayer; @@ -139,14 +139,9 @@ fn api_docs(api: TransformOpenApi) -> TransformOpenApi { .summary("Tycho Wallet indexer") } -pub(super) fn int_schema(_: &mut schemars::SchemaGenerator) -> schemars::schema::Schema { - let object_schema = schemars::schema::SchemaObject { - instance_type: Some(InstanceType::Number.into()), - ..Default::default() - }; - object_schema.into() +pub(super) fn int_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({"type": "number"}) } -pub(super) fn any_schema(_: &mut schemars::SchemaGenerator) -> schemars::schema::Schema { - let object_schema = SchemaObject::default(); - object_schema.into() +pub(super) fn any_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({}) } From 2b0b746a6367619cc03785a1dbe98417bf11c8c8 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 16 Feb 2026 10:55:36 +0300 Subject: [PATCH 37/59] upgrade metrics dep --- Cargo.lock | 185 ++++++++-------------------- Cargo.toml | 12 +- src/api/controllers/address.rs | 6 +- src/api/controllers/misc.rs | 34 ++--- src/api/controllers/transactions.rs | 22 ++-- src/api/router/mod.rs | 10 +- src/utils/multisig/parsing.rs | 32 ++--- 7 files changed, 114 insertions(+), 187 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 847e655..0651e5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,17 +22,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.12" @@ -497,7 +486,7 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b241c1e0296981e531864393cabeccba83061a6e79e5556cc267eca5cf2f0d92" dependencies = [ - "ahash 0.8.12", + "ahash", "blake3", "bytes", "hex", @@ -690,9 +679,9 @@ checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" -version = "0.6.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -939,19 +928,6 @@ dependencies = [ "syn 2.0.116", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "dashmap" version = "6.1.0" @@ -994,22 +970,23 @@ dependencies = [ [[package]] name = "derive_more" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", + "rustc_version", "syn 2.0.116", "unicode-xid", ] @@ -1444,15 +1421,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -1476,6 +1444,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", ] @@ -1980,18 +1950,18 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -2183,11 +2153,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.8.1" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.16.1", ] [[package]] @@ -2252,95 +2222,57 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93c0d11ac30a033511ae414355d80f70d9f29a44a49140face477117a1ee90db" -[[package]] -name = "metrics" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b9b8653cec6897f73b519a43fba5ee3d50f62fe9af80b428accdcc093b4a849" -dependencies = [ - "ahash 0.7.8", - "metrics-macros", - "portable-atomic 0.3.20", -] - [[package]] name = "metrics" version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" dependencies = [ - "ahash 0.8.12", - "portable-atomic 1.13.1", + "ahash", + "portable-atomic", ] [[package]] name = "metrics-exporter-prometheus" -version = "0.16.2" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" dependencies = [ "base64 0.22.1", "http-body-util", "hyper 1.8.1", + "hyper-rustls", "hyper-util", "indexmap", "ipnet", - "metrics 0.24.3", - "metrics-util 0.19.1", + "metrics", + "metrics-util", "quanta", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "tracing", ] [[package]] name = "metrics-exporter-prometheus" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" +checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda" dependencies = [ "base64 0.22.1", "http-body-util", "hyper 1.8.1", - "hyper-rustls", "hyper-util", "indexmap", "ipnet", - "metrics 0.24.3", - "metrics-util 0.20.1", + "metrics", + "metrics-util", "quanta", "thiserror 2.0.18", "tokio", "tracing", ] -[[package]] -name = "metrics-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "metrics-util" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.15.5", - "metrics 0.24.3", - "quanta", - "rand 0.9.2", - "rand_xoshiro", - "sketches-ddsketch", -] - [[package]] name = "metrics-util" version = "0.20.1" @@ -2350,7 +2282,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.16.1", - "metrics 0.24.3", + "metrics", "quanta", "rand 0.9.2", "rand_xoshiro", @@ -2391,7 +2323,7 @@ dependencies = [ "crossbeam-utils", "equivalent", "parking_lot", - "portable-atomic 1.13.1", + "portable-atomic", "smallvec", "tagptr", "uuid", @@ -2586,7 +2518,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" dependencies = [ "critical-section", "parking_lot_core", - "portable-atomic 1.13.1", + "portable-atomic", ] [[package]] @@ -2879,15 +2811,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "portable-atomic" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" -dependencies = [ - "portable-atomic 1.13.1", -] - [[package]] name = "portable-atomic" version = "1.13.1" @@ -2958,7 +2881,7 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" dependencies = [ - "ahash 0.8.12", + "ahash", "equivalent", "hashbrown 0.16.1", "parking_lot", @@ -4539,7 +4462,7 @@ dependencies = [ "arc-swap", "bytes", "hex", - "metrics 0.24.3", + "metrics", "parking_lot", "rayon", "thiserror 2.0.18", @@ -4564,7 +4487,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e22a046a4fecfeb0a79ed9d9f686ed065844a84ce5322e61f096eecb34e891" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "arc-swap", "async-trait", @@ -4578,10 +4501,10 @@ dependencies = [ "castaway", "clap", "crc32c", - "dashmap 6.1.0", + "dashmap", "futures-util", "humantime", - "metrics 0.24.3", + "metrics", "moka", "parking_lot", "parking_lot_core", @@ -4629,7 +4552,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ba91906bc028e800c91e232dfee409b5503e102f91d7931701d5b0d717e846" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "num-bigint", "thiserror 2.0.18", @@ -4643,20 +4566,20 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f51d4db5f49a9ab6951139cbeb384602d6ad1edbb1cb453a4a35c5c97cd53a6" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "arc-swap", "base64 0.22.1", "bytes", "bytesize", "castaway", - "dashmap 6.1.0", + "dashmap", "ed25519", "exponential-backoff", "futures-util", "hex", "indexmap", - "metrics 0.24.3", + "metrics", "moka", "parking_lot", "pin-project-lite", @@ -4689,7 +4612,7 @@ dependencies = [ "bytesize", "fdlimit", "libc", - "metrics 0.24.3", + "metrics", "rand 0.9.2", "rlimit", "scopeguard", @@ -4718,14 +4641,14 @@ name = "tycho-types" version = "0.3.2" source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1#f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "base64 0.22.1", "bitflags", "blake3", "bytes", "crc32c", - "dashmap 6.1.0", + "dashmap", "ed25519-dalek", "hex", "num-bigint", @@ -4773,20 +4696,20 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99e9f4dfca5d4d29d678e2933b0d08f8b84635e1d43f671a71c1419051dce0f8" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "base64 0.22.1", "bytes", "bytesize", "castaway", "crossbeam-deque", - "dashmap 6.1.0", + "dashmap", "futures-executor", "futures-util", "getip", "humantime", "libc", - "metrics 0.24.3", + "metrics", "metrics-exporter-prometheus 0.17.2", "rand 0.9.2", "rayon", @@ -4826,7 +4749,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef4b5767addc9c546991fe85519684444814527f317a3bd80a852b501250d71" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "bitflags", "blake2", @@ -4869,7 +4792,7 @@ dependencies = [ "chacha20poly1305", "chrono", "clap", - "dashmap 5.5.3", + "dashmap", "derive_more", "ed25519-dalek", "futures", @@ -4877,12 +4800,12 @@ dependencies = [ "hex", "hmac-sha256", "http 1.4.0", - "itertools 0.10.5", + "itertools 0.14.0", "lazy_static", "log", "lru", - "metrics 0.20.1", - "metrics-exporter-prometheus 0.16.2", + "metrics", + "metrics-exporter-prometheus 0.18.1", "nekoton-core", "num-bigint", "num-traits", @@ -5221,7 +5144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7769a20b8caefbfd7005172847727296a96c9aa19531f8f153219bc6b1fb00a" dependencies = [ "librocksdb-sys", - "metrics 0.24.3", + "metrics", "rocksdb", "thiserror 2.0.18", "tracing", @@ -5259,7 +5182,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9e5afcb..0705c4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,19 +17,19 @@ bigdecimal = { version = "0.4.5", features = ["serde"] } chacha20poly1305 = "0.10.1" chrono = { version = "0.4", features = ["serde"] } clap = { version = "4.5.3", features = ["derive"] } -dashmap = "5.3.4" -derive_more = { version = "1.0.0", features = ["full"] } +dashmap = "6.1" +derive_more = { version = "2", features = ["full"] } futures = "0.3" futures-util = "0.3.31" hex = "0.4" hmac-sha256 = "1.1.4" http = "1" -itertools = "0.10.1" +itertools = "0.14" lazy_static = "1.4.0" log = { version = "0.4", features = ["std", "serde"] } -lru = "0.8" -metrics = "0.20.1" -metrics-exporter-prometheus = { version = "0.16.0", default-features = false, features = ["http-listener"] } +lru = "0.16" +metrics = "0.24" +metrics-exporter-prometheus = { version = "0.18.1", default-features = false, features = ["http-listener"] } num-bigint = "0.4" num-traits = "0.2" opg = { version = "0.2", features = ["uuid"] } diff --git a/src/api/controllers/address.rs b/src/api/controllers/address.rs index 5e3c068..73f5910 100644 --- a/src/api/controllers/address.rs +++ b/src/api/controllers/address.rs @@ -2,7 +2,7 @@ use axum::extract::{Path, State}; use axum::Json; use tokio::time::Instant; -use metrics::{histogram, increment_counter}; +use metrics::{counter, histogram}; use crate::api::controllers::*; use crate::api::requests::*; @@ -24,8 +24,8 @@ pub async fn post_address_create( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "createAddress"); - increment_counter!("requests_processed", "method" => "createAddress"); + histogram!("execution_time_seconds", "method" => "createAddress").record(elapsed); + counter!("requests_processed", "method" => "createAddress").increment(1); Ok(Json(AddressResponse::from(address))) } diff --git a/src/api/controllers/misc.rs b/src/api/controllers/misc.rs index 8ae27b1..2c2eb86 100644 --- a/src/api/controllers/misc.rs +++ b/src/api/controllers/misc.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use axum::extract::State; use axum::Json; -use metrics::{histogram, increment_counter}; +use metrics::{counter, histogram}; use tokio::time::Instant; use tycho_types::abi::SerializeAbiValue; use tycho_types::abi::SerializeAbiValueParams; @@ -53,8 +53,8 @@ pub async fn post_read_contract( })?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "readContract"); - increment_counter!("requests_processed", "method" => "readContract"); + histogram!("execution_time_seconds", "method" => "readContract").record(elapsed); + counter!("requests_processed", "method" => "readContract").increment(1); Ok(Json(tokens)) } @@ -76,8 +76,8 @@ pub async fn post_encode_tvm_cell( .map(|cell| EncodedCellResponse { base64_cell: cell })?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "encodeTvmCell"); - increment_counter!("requests_processed", "method" => "encodeTvmCell"); + histogram!("execution_time_seconds", "method" => "encodeTvmCell").record(elapsed); + counter!("requests_processed", "method" => "encodeTvmCell").increment(1); Ok(Json(cell)) } @@ -117,8 +117,8 @@ pub async fn post_prepare_generic_message( let message_hash = ctx.memory_storage.add_message(unsigned_message); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "prepareGenericMessage"); - increment_counter!("requests_processed", "method" => "prepareGenericMessage"); + histogram!("execution_time_seconds", "method" => "prepareGenericMessage").record(elapsed); + counter!("requests_processed", "method" => "prepareGenericMessage").increment(1); Ok(Json(UnsignedMessageHashResponse { unsigned_message_hash: message_hash.to_string(), @@ -160,8 +160,8 @@ pub async fn post_send_signed_message( }?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "sendSignedMessage"); - increment_counter!("requests_processed", "method" => "sendSignedMessage"); + histogram!("execution_time_seconds", "method" => "sendSignedMessage").record(elapsed); + counter!("requests_processed", "method" => "sendSignedMessage").increment(1); Ok(Json(res)) } @@ -202,8 +202,8 @@ pub async fn post_send_generic_message( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "sendGenericMessage"); - increment_counter!("requests_processed", "method" => "sendGenericMessage"); + histogram!("execution_time_seconds", "method" => "sendGenericMessage").record(elapsed); + counter!("requests_processed", "method" => "sendGenericMessage").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -222,8 +222,8 @@ pub async fn post_set_callback( .await?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "setCallback"); - increment_counter!("requests_processed", "method" => "setCallback"); + histogram!("execution_time_seconds", "method" => "setCallback").record(elapsed); + counter!("requests_processed", "method" => "setCallback").increment(1); Ok(Json(SetCallbackResponse { callback: response })) } @@ -245,8 +245,8 @@ pub async fn get_token_whitelist( })?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "getTokenWhitelist"); - increment_counter!("requests_processed", "method" => "getTokenWhitelist"); + histogram!("execution_time_seconds", "method" => "getTokenWhitelist").record(elapsed); + counter!("requests_processed", "method" => "getTokenWhitelist").increment(1); Ok(Json(whitelist)) } @@ -259,8 +259,8 @@ pub async fn post_resubscribe_for_all_accounts( ctx.ton_service.resubscribe_for_all_accounts().await?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "resubscribeForAllAccounts"); - increment_counter!("requests_processed", "method" => "resubscribeForAllAccounts"); + histogram!("execution_time_seconds", "method" => "resubscribeForAllAccounts").record(elapsed); + counter!("requests_processed", "method" => "resubscribeForAllAccounts").increment(1); Ok(Json(ResubscribeResponse {})) } diff --git a/src/api/controllers/transactions.rs b/src/api/controllers/transactions.rs index 8793fe8..92fc560 100644 --- a/src/api/controllers/transactions.rs +++ b/src/api/controllers/transactions.rs @@ -1,6 +1,6 @@ use axum::extract::{Path, State}; use axum::Json; -use metrics::{histogram, increment_counter}; +use metrics::{counter, histogram}; use tokio::time::Instant; use uuid::Uuid; @@ -46,8 +46,8 @@ pub async fn post_transactions_create( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "transactionCreate"); - increment_counter!("requests_processed", "method" => "transactionCreate"); + histogram!("execution_time_seconds", "method" => "transactionCreate").record(elapsed); + counter!("requests_processed", "method" => "transactionCreate").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -66,8 +66,8 @@ pub async fn post_transactions_confirm( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "transactionConfirm"); - increment_counter!("requests_processed", "method" => "transactionConfirm"); + histogram!("execution_time_seconds", "method" => "transactionConfirm").record(elapsed); + counter!("requests_processed", "method" => "transactionConfirm").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -156,8 +156,8 @@ pub async fn post_tokens_transactions_create( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "tokenTransactionCreate"); - increment_counter!("requests_processed", "method" => "tokenTransactionCreate"); + histogram!("execution_time_seconds", "method" => "tokenTransactionCreate").record(elapsed); + counter!("requests_processed", "method" => "tokenTransactionCreate").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -176,8 +176,8 @@ pub async fn post_tokens_transactions_burn( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "tokenTransactionBurn"); - increment_counter!("requests_processed", "method" => "tokenTransactionBurn"); + histogram!("execution_time_seconds", "method" => "tokenTransactionBurn").record(elapsed); + counter!("requests_processed", "method" => "tokenTransactionBurn").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -196,8 +196,8 @@ pub async fn post_tokens_transactions_mint( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "tokenTransactionMint"); - increment_counter!("requests_processed", "method" => "tokenTransactionMint"); + histogram!("execution_time_seconds", "method" => "tokenTransactionMint").record(elapsed); + counter!("requests_processed", "method" => "tokenTransactionMint").increment(1); Ok(Json(TransactionResponse::from(transaction))) } diff --git a/src/api/router/mod.rs b/src/api/router/mod.rs index 677acfa..55948e9 100644 --- a/src/api/router/mod.rs +++ b/src/api/router/mod.rs @@ -25,10 +25,14 @@ pub fn router( ) -> ApiRouter { describe_gauge!("in_flight_requests", "number of inflight requests"); let (in_flight_requests_layer, counter) = InFlightRequestsLayer::pair(); - tokio::spawn(async { + let in_flight_gauge = gauge!("in_flight_requests"); + tokio::spawn(async move { counter - .run_emitter(Duration::from_secs(5), |count| async move { - gauge!("in_flight_requests", count as f64) + .run_emitter(Duration::from_secs(5), move |count| { + let gauge = in_flight_gauge.clone(); + async move { + gauge.set(count as f64) + } }) .await; }); diff --git a/src/utils/multisig/parsing.rs b/src/utils/multisig/parsing.rs index 2de8146..5b12c29 100644 --- a/src/utils/multisig/parsing.rs +++ b/src/utils/multisig/parsing.rs @@ -198,24 +198,24 @@ impl MultisigFunctions { #[cfg(test)] mod tests { - use tycho_types::{ - boc::Boc, - cell::Load, - models::{OrdinaryTxInfo, TxInfo}, - }; + // use tycho_types::{ + // boc::Boc, + // cell::Load, + // models::{OrdinaryTxInfo, TxInfo}, + // }; - use super::*; + // use super::*; - fn parse_transaction(data: &str) -> (Transaction, OrdinaryTxInfo) { - let binding = Boc::decode_base64(data).unwrap(); - let mut cell = binding.as_slice().unwrap(); - let transaction = Transaction::load_from(&mut cell).unwrap(); - let info = match transaction.load_info().unwrap() { - TxInfo::Ordinary(info) => info, - _ => panic!(), - }; - (transaction, info) - } + // fn parse_transaction(data: &str) -> (Transaction, OrdinaryTxInfo) { + // let binding = Boc::decode_base64(data).unwrap(); + // let mut cell = binding.as_slice().unwrap(); + // let transaction = Transaction::load_from(&mut cell).unwrap(); + // let info = match transaction.load_info().unwrap() { + // TxInfo::Ordinary(info) => info, + // _ => panic!(), + // }; + // (transaction, info) + // } //#[test] //fn test_parse_multisig_submit() { From faaa7d4de256eaaf6164b13435a36af8a954cf77 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 16 Feb 2026 11:13:28 +0300 Subject: [PATCH 38/59] upgrade other deps --- Cargo.lock | 334 +++++++++++++++++++----------- Cargo.toml | 14 +- src/api/responses/transactions.rs | 6 +- src/api/router/mod.rs | 4 +- src/client/callback/mod.rs | 3 +- src/models/account_enums.rs | 12 +- src/services/auth.rs | 4 +- src/settings.rs | 7 +- src/utils/encoding.rs | 8 +- src/utils/mnemonic/legacy.rs | 6 +- src/utils/mnemonic/mod.rs | 6 +- 11 files changed, 256 insertions(+), 148 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0651e5b..5c52b8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,12 +161,13 @@ dependencies = [ [[package]] name = "argon2" -version = "0.4.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", + "cpufeatures 0.2.17", "password-hash", ] @@ -408,7 +409,7 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq", - "cpufeatures", + "cpufeatures 0.2.17", "memmap2", "rayon-core", ] @@ -520,6 +521,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -549,7 +556,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", ] [[package]] @@ -559,7 +577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -629,7 +647,7 @@ version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.116", @@ -656,6 +674,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -721,6 +749,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -838,7 +875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", @@ -1095,7 +1132,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.116", @@ -1392,6 +1429,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -1458,12 +1496,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -1726,22 +1758,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.8.1", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.20" @@ -1779,7 +1795,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -1972,6 +1988,28 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -2633,9 +2671,9 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", @@ -2790,7 +2828,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2913,6 +2951,7 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -2988,6 +3027,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.1", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3026,6 +3076,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_xoshiro" version = "0.7.0" @@ -3133,9 +3189,9 @@ checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", @@ -3147,21 +3203,21 @@ dependencies = [ "http-body-util", "hyper 1.8.1", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -3296,6 +3352,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.9" @@ -3514,7 +3597,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -3526,7 +3609,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.9.0", "opaque-debug", ] @@ -3538,7 +3621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -3718,7 +3801,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck 0.5.0", + "heck", "hex", "once_cell", "proc-macro2", @@ -3870,25 +3953,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3937,21 +4001,6 @@ dependencies = [ "syn 2.0.116", ] -[[package]] -name = "sysinfo" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.52.0", -] - [[package]] name = "sysinfo" version = "0.37.2" @@ -3963,7 +4012,7 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows 0.61.3", + "windows", ] [[package]] @@ -4239,16 +4288,6 @@ dependencies = [ "syn 2.0.116", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -4717,7 +4756,7 @@ dependencies = [ "serde", "serde_json", "serde_path_to_error", - "sysinfo 0.37.2", + "sysinfo", "thiserror 2.0.18", "tikv-jemalloc-ctl", "tl-proto", @@ -4787,7 +4826,7 @@ dependencies = [ "argon2", "async-trait", "axum", - "base64 0.13.1", + "base64 0.22.1", "bigdecimal", "chacha20poly1305", "chrono", @@ -4813,20 +4852,17 @@ dependencies = [ "parking_lot", "pbkdf2 0.12.2", "pomfrit", - "rand 0.8.5", + "rand 0.10.0", "rayon", "regex", "reqwest", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "schemars", "serde", "serde_json", "sha2 0.10.9", "slip10_ed25519", "sqlx", - "strum", - "strum_macros", - "sysinfo 0.30.13", "thiserror 1.0.69", "tikv-jemallocator", "tiny-bip39", @@ -5137,6 +5173,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weedb" version = "0.6.0" @@ -5191,16 +5236,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.3" @@ -5208,7 +5243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", "windows-link 0.1.3", "windows-numerics", @@ -5220,16 +5255,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -5251,7 +5277,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link 0.1.3", "windows-threading", ] @@ -5296,7 +5322,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link 0.1.3", ] @@ -5347,6 +5373,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -5383,6 +5418,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -5440,6 +5490,12 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5458,6 +5514,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5476,6 +5538,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5506,6 +5574,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5524,6 +5598,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5542,6 +5622,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5560,6 +5646,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5594,7 +5686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck 0.5.0", + "heck", "wit-parser", ] @@ -5605,7 +5697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck 0.5.0", + "heck", "indexmap", "prettyplease", "syn 2.0.116", diff --git a/Cargo.toml b/Cargo.toml index 0705c4e..54188cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ license-file = "LICENSE" aide = { version = "0.15.1", features = ["axum", "axum-extra", "axum-json", "scalar", "macros"] } anyhow = "1.0" axum = { version = "0.8.8", features = ["multipart", "macros"] } -argon2 = "0.4.1" +argon2 = "0.5" async-trait = "0.1" -base64 = "0.13" +base64 = "0.22" bigdecimal = { version = "0.4.5", features = ["serde"] } chacha20poly1305 = "0.10.1" chrono = { version = "0.4", features = ["serde"] } @@ -36,19 +36,17 @@ opg = { version = "0.2", features = ["uuid"] } parking_lot = "0.12.0" pbkdf2 = { version = "0.12.2" } pomfrit = "0.1" -rand = "0.8" +rand = { version = "0.10", features = ["thread_rng"] } rayon = "1.10" regex = "1.5" -reqwest = { version = "0.12", features = ["json"] } -rustc-hash = "1.1.0" +reqwest = { version = "0.13", features = ["json"] } +rustc-hash = "2" schemars = { version = "0.9.0", features = ["chrono04", "bigdecimal04", "uuid1"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sha2 = { version = "0.10.8" } slip10_ed25519 = "0.1.3" sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres", "uuid", "bigdecimal", "chrono", "json"] } -strum = "0.24.1" -strum_macros = "0.24.1" thiserror = "1.0" tiny-hderive = { git = "https://github.com/broxus/tiny-hderive.git" } tiny-bip39 = { git = "https://github.com/broxus/tiny-bip39.git", default-features = false } @@ -80,8 +78,6 @@ tycho-executor = "0.3.2" nekoton-core = { git = "https://github.com/broxus/tycho-nekoton.git" } -sysinfo = "0.30.13" - [features] default = [] diff --git a/src/api/responses/transactions.rs b/src/api/responses/transactions.rs index 8b9462c..17b8a83 100644 --- a/src/api/responses/transactions.rs +++ b/src/api/responses/transactions.rs @@ -1,5 +1,7 @@ use std::str::FromStr; +use base64::{engine::general_purpose, Engine as _}; + use bigdecimal::BigDecimal; use tycho_types::models::StdAddr; @@ -249,7 +251,9 @@ impl From for TokenTransactionDataResponse { let account = StdAddr::from_str(&format!("{}:{}", c.account_workchain_id, c.account_hex)).unwrap(); let base64url = Address(account.display_base64_url(true).to_string()); - let payload = c.payload.map(base64::encode); + let payload = c + .payload + .map(|value| general_purpose::STANDARD.encode(value)); TokenTransactionDataResponse { id: c.id, diff --git a/src/api/router/mod.rs b/src/api/router/mod.rs index 55948e9..b4bc2c3 100644 --- a/src/api/router/mod.rs +++ b/src/api/router/mod.rs @@ -30,9 +30,7 @@ pub fn router( counter .run_emitter(Duration::from_secs(5), move |count| { let gauge = in_flight_gauge.clone(); - async move { - gauge.set(count as f64) - } + async move { gauge.set(count as f64) } }) .await; }); diff --git a/src/client/callback/mod.rs b/src/client/callback/mod.rs index bbf089e..95470f2 100644 --- a/src/client/callback/mod.rs +++ b/src/client/callback/mod.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use base64::{engine::general_purpose, Engine as _}; use chrono::Utc; use reqwest::{Method, StatusCode, Url}; @@ -63,5 +64,5 @@ impl CallbackClient { fn calc_sign(body: String, url: String, timestamp_ms: i64, secret: String) -> String { let concat = format!("{}{}{}", timestamp_ms, url, body); let calculated_signature = hmac_sha256::HMAC::mac(concat.as_bytes(), secret.as_bytes()); - base64::encode(calculated_signature) + general_purpose::STANDARD.encode(calculated_signature) } diff --git a/src/models/account_enums.rs b/src/models/account_enums.rs index 75b973d..51bae02 100644 --- a/src/models/account_enums.rs +++ b/src/models/account_enums.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use strum_macros::EnumString; use tycho_types::models::{AccountState, StdAddr}; use crate::{ @@ -41,7 +40,16 @@ impl From for AccountStatus { } #[derive( - Debug, Deserialize, Serialize, Clone, JsonSchema, Eq, PartialEq, sqlx::Type, Copy, EnumString, + Debug, + Deserialize, + Serialize, + Clone, + JsonSchema, + Eq, + PartialEq, + sqlx::Type, + Copy, + derive_more::FromStr, )] #[sqlx(type_name = "twa_token_wallet_version", rename_all = "PascalCase")] pub enum TokenWalletVersionDb { diff --git a/src/services/auth.rs b/src/services/auth.rs index 890ccb4..3ae7da6 100644 --- a/src/services/auth.rs +++ b/src/services/auth.rs @@ -6,6 +6,8 @@ use chrono::DateTime; use chrono::Utc; use parking_lot::Mutex; +use base64::{engine::general_purpose, Engine as _}; + use crate::models::*; use crate::sqlx_client::*; @@ -74,7 +76,7 @@ impl AuthService { let calculated_signature = hmac_sha256::HMAC::mac(concat.as_bytes(), key.secret.as_bytes()); - let expected_signature = base64::decode(signature)?; + let expected_signature = general_purpose::STANDARD.decode(signature)?; if calculated_signature != expected_signature.as_slice() { anyhow::bail!("Invalid signature"); diff --git a/src/settings.rs b/src/settings.rs index b62c398..d14a09a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,7 +2,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::Path; use anyhow::{Context, Result}; -use argon2::password_hash::PasswordHasher; +use argon2::password_hash::{PasswordHasher, Salt}; use regex; use serde::{Deserialize, Deserializer, Serialize}; @@ -71,15 +71,16 @@ fn default_key() -> Vec { let mut options = argon2::ParamsBuilder::default(); let options = options .output_len(32) //chacha key size - .and_then(|x| x.clone().params()) + .build() .unwrap(); // Argon2 with default params (Argon2id v19) let argon2 = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, options); + let salt = Salt::from_b64(&salt).unwrap(); let key = argon2 - .hash_password(secret.as_bytes(), &salt) + .hash_password(secret.as_bytes(), salt) .unwrap() .hash .context("No hash")? diff --git a/src/utils/encoding.rs b/src/utils/encoding.rs index 5a5ffd4..f44c39e 100644 --- a/src/utils/encoding.rs +++ b/src/utils/encoding.rs @@ -1,4 +1,5 @@ use anyhow::{Error, Result}; +use base64::{engine::general_purpose, Engine as _}; use chacha20poly1305::aead::AeadMut; use chacha20poly1305::{ChaCha20Poly1305, Nonce}; @@ -8,7 +9,7 @@ pub fn encrypt_private_key(private_key: &[u8], key: [u8; 32], id: &uuid::Uuid) - let key = chacha20poly1305::Key::from_slice(&key[..]); let mut encryptor = ChaCha20Poly1305::new(key); let res = encryptor.encrypt(nonce, private_key).map_err(Error::msg)?; - Ok(base64::encode(res)) + Ok(general_purpose::STANDARD.encode(res)) } pub fn decrypt_private_key(private_key: &str, key: [u8; 32], id: &uuid::Uuid) -> Result> { @@ -17,6 +18,9 @@ pub fn decrypt_private_key(private_key: &str, key: [u8; 32], id: &uuid::Uuid) -> let key = chacha20poly1305::Key::from_slice(&key[..]); let mut decrypter = ChaCha20Poly1305::new(key); decrypter - .decrypt(nonce, base64::decode(private_key)?.as_slice()) + .decrypt( + nonce, + general_purpose::STANDARD.decode(private_key)?.as_slice(), + ) .map_err(Error::msg) } diff --git a/src/utils/mnemonic/legacy.rs b/src/utils/mnemonic/legacy.rs index 9367975..e4d3293 100644 --- a/src/utils/mnemonic/legacy.rs +++ b/src/utils/mnemonic/legacy.rs @@ -40,16 +40,18 @@ pub fn derive_from_phrase(phrase: &str) -> Result GeneratedKey { use bip39::util::{Bits11, IterExt}; - let rng = &mut rand::thread_rng(); + let rng = &mut rand::rng(); pub fn generate_words(entropy: &[u8]) -> Vec<&'static str> { let wordlist = LANGUAGE.wordlist(); @@ -177,7 +177,7 @@ pub fn generate_key(account_type: MnemonicType) -> GeneratedKey { }; let entropy = (0..entropy_size) - .map(|_| rng.gen::()) + .map(|_| rng.random::()) .collect::>(); GeneratedKey { From fb4ea2bf1523b367f14fe6280506cd5958b18160 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 16 Feb 2026 14:26:33 +0300 Subject: [PATCH 39/59] refactor ton core --- src/client/ton/mod.rs | 5 +- src/cmd/run.rs | 20 ++--- src/server.rs | 31 ++++---- src/ton_core/mod.rs | 78 ++++++++----------- src/ton_core/monitoring/token_transaction.rs | 8 ++ .../monitoring/token_transaction_parser.rs | 27 ++++--- src/ton_core/ton_subscriber/mod.rs | 46 +++++++---- src/utils/tx_context.rs | 15 ++++ 8 files changed, 127 insertions(+), 103 deletions(-) diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index f4bfcf4..012cfa6 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use axum::http::StatusCode; use bigdecimal::{BigDecimal, ToPrimitive}; use ed25519_dalek::VerifyingKey; -use nekoton_core::contracts::blockchain_context::BlockchainContextBuilder; use nekoton_core::contracts::function_ext::ExecutionOutput; use nekoton_core::contracts::function_ext::FunctionExt; use num_bigint::BigUint; @@ -530,7 +529,7 @@ impl TonClient { let root_account = root_address.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; - let context = BlockchainContextBuilder::new().build()?; + let context = self.ton_core.blockchain_context(); let token_address = get_token_wallet_address(root_contract, context.clone(), owner)?; let token_account = token_address.address; @@ -725,7 +724,7 @@ impl TonClient { let root_account = root_token.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; - let context = BlockchainContextBuilder::new().build()?; + let context = self.ton_core.blockchain_context(); let version = get_root_token_version(root_contract, context)?; diff --git a/src/cmd/run.rs b/src/cmd/run.rs index 1c63b99..5e867f4 100644 --- a/src/cmd/run.rs +++ b/src/cmd/run.rs @@ -128,13 +128,6 @@ impl Cmd { .init_blockchain_rpc(NoopBroadcastListener, NoopBroadcastListener)? .build()?; - let context = EngineContext::new( - node_config.api, - node.core_storage.clone(), - node.blockchain_rpc_client.clone(), - ) - .await?; - // Sync node. node.wait_for_neighbours(3).await; @@ -151,6 +144,14 @@ impl Cmd { node.update_validator_set_from_shard_state(&init_block_id) .await?; + let context = EngineContext::new( + node_config.api, + node.core_storage.clone(), + node.blockchain_rpc_client.clone(), + &init_block_id, + ) + .await?; + // Build strider. let archive_block_provider = node.build_archive_block_provider(); let storage_block_provider = node.build_storage_block_provider(); @@ -166,11 +167,6 @@ impl Cmd { ), ); - context - .start(&init_block_id) - .await - .context("failed to start context")?; - let api = Api::bind( context.config.server_addr, context.config.public_url.clone(), diff --git a/src/server.rs b/src/server.rs index 8aebaf4..c65e922 100644 --- a/src/server.rs +++ b/src/server.rs @@ -36,6 +36,7 @@ impl EngineContext { config: AppConfig, storage: CoreStorage, blockchain_rpc_client: BlockchainRpcClient, + last_block_id: &BlockId, ) -> Result> { let pool = PgPoolOptions::new() .max_connections(config.db_pool_size) @@ -65,11 +66,17 @@ impl EngineContext { token_transaction_tx, storage, blockchain_rpc_client, + last_block_id, ) .await?; let ton_client = Arc::new(TonClient::new(ton_core.clone(), sqlx_client.clone())); + ton_client + .start() + .await + .context("failed to start ton_client")?; + let ton_service = Arc::new(TonService::new( sqlx_client.clone(), ton_client.clone(), @@ -77,7 +84,12 @@ impl EngineContext { config.key.clone(), )); - let auth_service = Arc::new(AuthService::new(sqlx_client.clone())); + ton_service + .start() + .await + .context("failed to start ton_service")?; + + let auth_service = Arc::new(AuthService::new(sqlx_client)); let memory_storage = Arc::new(StorageHandler::default()); @@ -98,23 +110,6 @@ impl EngineContext { Ok(engine_context) } - pub async fn start(&self, last_block_id: &BlockId) -> Result<()> { - self.ton_client - .start() - .await - .context("failed to start ton_client")?; - self.ton_service - .start() - .await - .context("failed to start ton_service")?; - self.ton_core - .start(last_block_id) - .await - .context("failed to start ton_core")?; - - Ok(()) - } - fn start_listening_ton_transaction(self: &Arc, mut rx: TonTransactionRx) { let engine_context = Arc::downgrade(self); diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index 53bb62a..89d34da 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -41,9 +41,16 @@ impl TonCore { token_transaction_producer: TokenTransactionTx, storage: CoreStorage, blockchain_rpc_client: BlockchainRpcClient, + last_block_id: &BlockId, ) -> Result> { - let context = - TonCoreContext::new(sqlx_client, owners_cache, storage, blockchain_rpc_client).await?; + let context = TonCoreContext::new( + sqlx_client, + owners_cache, + storage, + blockchain_rpc_client, + last_block_id, + ) + .await?; let ton_transaction = TonTransaction::new(context.clone(), ton_transaction_producer).await?; @@ -58,14 +65,6 @@ impl TonCore { })) } - pub async fn start(&self, last_block_id: &BlockId) -> Result<()> { - // Sync node and subscribers - self.context.start(last_block_id).await?; - - // Done - Ok(()) - } - pub fn add_ton_account_subscription(&self, accounts: I) where I: IntoIterator, @@ -130,55 +129,46 @@ impl TonCoreContext { owners_cache: OwnersCache, storage: CoreStorage, blockchain_rpc_client: BlockchainRpcClient, + last_block_id: &BlockId, ) -> Result> { let messages_queue = PendingMessagesQueue::new(512); - let ton_subscriber = TonSubscriber::new(messages_queue.clone()); + let mc_state = storage + .shard_state_storage() + .load_state(last_block_id.seqno, last_block_id) + .await?; - Ok(Arc::new(Self { - sqlx_client, - owners_cache, - messages_queue, - ton_subscriber, - storage, - blockchain_rpc_client, - })) - } + let config = mc_state.config_params()?.clone(); + let global_version = config.get_global_version()?; + + let ton_subscriber = TonSubscriber::new( + messages_queue.clone(), + global_version.capabilities.into_inner(), + mc_state.state().global_id, + config, + ); - async fn start(&self, last_block_id: &BlockId) -> Result<()> { // Load last states if exists - let block_ids = self.sqlx_client.get_last_key_blocks().await?; + let block_ids = sqlx_client.get_last_key_blocks().await?; for block_id in block_ids { let block_id = BlockId::from_str(&block_id.block_id)?; - if let Ok(state) = self - .storage + if let Ok(state) = storage .shard_state_storage() .load_state(last_block_id.seqno, &block_id) .await { - self.ton_subscriber - .update_shards_accounts_cache(block_id.shard, state)?; + ton_subscriber.update_shards_accounts_cache(block_id.shard, state)?; } } - let mc_state = self - .storage - .shard_state_storage() - .load_state(last_block_id.seqno, last_block_id) - .await?; - - let config = mc_state.config_params()?; - let global_version = config.get_global_version()?; - - self.ton_subscriber - .start( - global_version.capabilities.into_inner(), - mc_state.state().global_id, - ) - .await - .context("Failed to start ton_subscriber")?; - - Ok(()) + Ok(Arc::new(Self { + sqlx_client, + owners_cache, + messages_queue, + ton_subscriber, + storage, + blockchain_rpc_client, + })) } fn get_contract_state(&self, account: &HashBytes) -> Result { diff --git a/src/ton_core/monitoring/token_transaction.rs b/src/ton_core/monitoring/token_transaction.rs index 06e289d..0294b1d 100644 --- a/src/ton_core/monitoring/token_transaction.rs +++ b/src/ton_core/monitoring/token_transaction.rs @@ -51,11 +51,17 @@ impl TokenTransaction { } }; + let Some(context) = event.ctx.blockchain_context.clone() else { + tracing::error!("Failed to parse received token transaction: Failed to get blockchain context"); + continue; + }; + match token_transaction_parser::parse_token_transaction( event.ctx, event.parsed, &token_transaction.context.sqlx_client, &token_transaction.context.owners_cache, + context.blockchain_context, ) .await { @@ -87,6 +93,7 @@ pub struct TokenTransactionContext { pub transaction: Transaction, pub token_state: ExistingContract, pub in_msg: OwnedMessage, + pub blockchain_context: Option, } #[derive(Debug)] @@ -118,6 +125,7 @@ impl ReadFromTransaction for TokenTransactionEvent { transaction: ctx.transaction.clone(), token_state: token_state.clone(), in_msg: ctx.in_msg.clone(), + blockchain_context: ctx.blockchain_context.clone(), }, parsed: parsed.clone(), state, diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index 2e1b62f..247748c 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -1,6 +1,5 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton_core::contracts::blockchain_context::BlockchainContextBuilder; use tycho_types::cell::Cell; use uuid::Uuid; @@ -17,6 +16,7 @@ pub async fn parse_token_transaction( parsed_token_transaction: TokenWalletTransaction, sqlx_client: &SqlxClient, owners_cache: &OwnersCache, + context: BlockchainContext, ) -> Result { let parse_ctx = ParseContext { sqlx_client, @@ -25,10 +25,10 @@ pub async fn parse_token_transaction( let parsed = match parsed_token_transaction { TokenWalletTransaction::IncomingTransfer(transfer) => { - internal_transfer_receive(token_transaction_ctx, transfer, parse_ctx).await? + internal_transfer_receive(token_transaction_ctx, transfer, parse_ctx, context).await? } TokenWalletTransaction::Accept(tokens) => { - internal_transfer_mint(token_transaction_ctx, tokens, parse_ctx).await? + internal_transfer_mint(token_transaction_ctx, tokens, parse_ctx, context).await? } TokenWalletTransaction::OutgoingTransfer(token_transfer) => { internal_transfer_send( @@ -36,15 +36,23 @@ pub async fn parse_token_transaction( token_transfer.tokens, Some(token_transfer.payload), parse_ctx, + context, ) .await? } TokenWalletTransaction::SwapBack(swap_back) => { - internal_transfer_send(token_transaction_ctx, swap_back.tokens, None, parse_ctx).await? + internal_transfer_send( + token_transaction_ctx, + swap_back.tokens, + None, + parse_ctx, + context, + ) + .await? } TokenWalletTransaction::TransferBounced(tokens) | TokenWalletTransaction::SwapBackBounced(tokens) => { - internal_transfer_bounced(token_transaction_ctx, tokens, parse_ctx).await? + internal_transfer_bounced(token_transaction_ctx, tokens, parse_ctx, context).await? } }; @@ -56,10 +64,10 @@ async fn internal_transfer_send( tokens: u128, payload_cell: Option, parse_ctx: ParseContext<'_>, + context: BlockchainContext, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); - let context = BlockchainContextBuilder::new().build()?; let owner_info = get_token_wallet_info( &address, &parse_ctx, @@ -110,9 +118,9 @@ async fn internal_transfer_receive( token_transaction_ctx: TokenTransactionContext, token_transfer: TokenIncomingTransfer, parse_ctx: ParseContext<'_>, + context: BlockchainContext, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); - let context = BlockchainContextBuilder::new().build()?; let owner_info = get_token_wallet_info( &address, @@ -159,11 +167,10 @@ async fn internal_transfer_bounced( token_transaction_ctx: TokenTransactionContext, tokens: u128, parse_ctx: ParseContext<'_>, + context: BlockchainContext, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); - let context = BlockchainContextBuilder::new().build()?; - let owner_info = get_token_wallet_info( &address, &parse_ctx, @@ -207,10 +214,10 @@ async fn internal_transfer_mint( token_transaction_ctx: TokenTransactionContext, tokens: u128, parse_ctx: ParseContext<'_>, + context: BlockchainContext, ) -> Result { let address = StdAddr::new(0, token_transaction_ctx.account); - let context = BlockchainContextBuilder::new().build()?; let owner_info = get_token_wallet_info( &address, &parse_ctx, diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index 4816e6e..41f4d54 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -33,10 +33,28 @@ pub struct TonSubscriber { } impl TonSubscriber { - pub fn new(messages_queue: Arc) -> Arc { + pub fn new( + messages_queue: Arc, + capabilities_bits: u64, + global_id: i32, + config: BlockchainConfig, + ) -> Arc { + let signature_id = SignatureId::default(); + signature_id.store(capabilities_bits, global_id); + + let capabilities = Capabilities(AtomicU64::new(capabilities_bits)); + + let transport = SimpleTransport::new(vec![], config.clone()).unwrap(); + + let blockchain_context = BlockchainContextBuilder::new() + .with_config(config) + .with_transport(Arc::new(transport)) + .build() + .unwrap(); + Arc::new(Self { current_utime: AtomicU32::new(0), - signature_id: SignatureId::default(), + signature_id, state_subscriptions: RwLock::new(FxHashMap::with_capacity_and_hasher( 1024, Default::default(), @@ -48,8 +66,8 @@ impl TonSubscriber { Default::default(), )), messages_queue, - capabilities: Capabilities::default(), - blockchain_context: RwLock::new(BlockchainContextBuilder::default().build().unwrap()), + capabilities, + blockchain_context: RwLock::new(blockchain_context), }) } @@ -62,12 +80,6 @@ impl TonSubscriber { } } - pub async fn start(self: &Arc, capabilities: u64, global_id: i32) -> Result<()> { - self.update_signature_id(capabilities, global_id)?; - self.update_capabilies(capabilities)?; - Ok(()) - } - pub fn current_utime(&self) -> u32 { self.current_utime.load(Ordering::Acquire) } @@ -253,6 +265,7 @@ impl TonSubscriber { let state_subscriptions = self.state_subscriptions.read(); let token_subscription = self.token_subscription.read(); let shards_accounts_cache = self.sc_accounts.read(); + let blockchain_context = self.blockchain_context(); for account_block in account_blocks.iter() { let (account, _, account_block) = account_block?; @@ -287,6 +300,7 @@ impl TonSubscriber { &account_block, &account, block_hash, + blockchain_context.clone(), ) { Ok(rx_states) => { if !rx_states.is_empty() { @@ -307,10 +321,6 @@ impl TonSubscriber { Ok(states) } - fn update_signature_id(&self, capabilities: u64, global_id: i32) -> Result<()> { - self.signature_id.store(capabilities, global_id); - Ok(()) - } fn update_capabilies(&self, capabilities: u64) -> Result<()> { self.capabilities.0.store(capabilities, Ordering::Release); Ok(()) @@ -427,6 +437,7 @@ impl StateSubscription { in_msg: &in_msg, token_transaction: &None, token_state: &None, + blockchain_context: &None, }; // Handle transaction @@ -465,6 +476,7 @@ impl TokenSubscription { account_block: &AccountBlock, account: &HashBytes, block_hash: &HashBytes, + blockchain_context: BlockchainContext, ) -> Result> { let states = FuturesUnordered::new(); @@ -512,9 +524,8 @@ impl TokenSubscription { .find_account(&HashBytes::from_slice(account.as_slice()))? .ok_or_else(|| TonCoreError::AccountNotExist(account.to_string()))?; - let context = BlockchainContextBuilder::new().build()?; let (token_wallet_details, ..) = - get_token_wallet_details(token_contract.clone(), context)?; + get_token_wallet_details(token_contract.clone(), blockchain_context.clone())?; let owner_account = &token_wallet_details.owner_address.address; if state_subscriptions.get(owner_account).is_some() { @@ -533,6 +544,9 @@ impl TokenSubscription { in_msg: &in_msg, token_transaction: &Some(parsed), token_state: &Some(token_contract), + blockchain_context: &Some(BlockchainContextWrapper { + blockchain_context: blockchain_context.clone(), + }), }; if let Some(transaction_subscription) = self.transaction_subscription.upgrade() diff --git a/src/utils/tx_context.rs b/src/utils/tx_context.rs index 075aedf..50ecc70 100644 --- a/src/utils/tx_context.rs +++ b/src/utils/tx_context.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use nekoton_core::contracts::blockchain_context::BlockchainContext; use tokio::sync::oneshot; use tycho_types::{ abi::{Function, NamedAbiValue}, @@ -32,6 +33,20 @@ pub struct TxContext<'a> { pub in_msg: &'a OwnedMessage, pub token_transaction: &'a Option, pub token_state: &'a Option, + pub blockchain_context: &'a Option, +} + +#[derive(Clone)] +pub struct BlockchainContextWrapper { + pub blockchain_context: BlockchainContext, +} + +impl std::fmt::Debug for BlockchainContextWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BlockchainContextWrapper") + .field("blockchain_context", &"context") + .finish() + } } impl TxContext<'_> { From ec679348aafaf67b2bb426c2c984a6b09e4fade2 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 16 Feb 2026 14:55:57 +0300 Subject: [PATCH 40/59] fix axum syntax --- src/api/router/address.rs | 4 ++-- src/api/router/events.rs | 2 +- src/api/router/tokens.rs | 6 +++--- src/api/router/transactions.rs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/router/address.rs b/src/api/router/address.rs index 37da9b5..fd076a2 100644 --- a/src/api/router/address.rs +++ b/src/api/router/address.rs @@ -34,14 +34,14 @@ pub fn router() -> ApiRouter { taged("address"), ) .api_route_with( - "/:address", + "/{address}", get_with(controllers::get_address_balance, |op| { op.response::<200, Json>() }), taged("address"), ) .api_route_with( - "/:address/info", + "/{address}/info", get_with(controllers::get_address_info, |op| { op.response::<200, Json>() }), diff --git a/src/api/router/events.rs b/src/api/router/events.rs index 5289874..a08f4d4 100644 --- a/src/api/router/events.rs +++ b/src/api/router/events.rs @@ -31,7 +31,7 @@ pub fn router() -> ApiRouter { taged("events"), ) .api_route_with( - "/id/:id", + "/id/{id}", get_with(controllers::get_events_id, |op| { op.response::<200, Json>() }), diff --git a/src/api/router/tokens.rs b/src/api/router/tokens.rs index 7b98852..464b290 100644 --- a/src/api/router/tokens.rs +++ b/src/api/router/tokens.rs @@ -21,21 +21,21 @@ pub fn router() -> ApiRouter { taged("tokens"), ) .api_route_with( - "/address/:address", + "/address/{address}", get_with(controllers::get_token_address_balance, |op| { op.response::<200, Json>() }), taged("tokens"), ) .api_route_with( - "/transactions/id/:internal_id", + "/transactions/id/{internal_id}", get_with(controllers::get_tokens_transactions_id, |op| { op.response::<200, Json>() }), taged("tokens"), ) .api_route_with( - "/transactions/mh/:message_hash", + "/transactions/mh/{message_hash}", get_with(controllers::get_tokens_transactions_mh, |op| { op.response::<200, Json>() }), diff --git a/src/api/router/transactions.rs b/src/api/router/transactions.rs index 8d5c87a..6533b3c 100644 --- a/src/api/router/transactions.rs +++ b/src/api/router/transactions.rs @@ -31,21 +31,21 @@ pub fn router() -> ApiRouter { taged("transactions"), ) .api_route_with( - "/id/:id", + "/id/{id}", get_with(controllers::get_transactions_id, |op| { op.response::<200, Json>() }), taged("transactions"), ) .api_route_with( - "/h/:hash", + "/h/{hash}", get_with(controllers::get_transactions_h, |op| { op.response::<200, Json>() }), taged("transactions"), ) .api_route_with( - "/mh/:message_hash", + "/mh/{message_hash}", get_with(controllers::get_transactions_mh, |op| { op.response::<200, Json>() }), From 3da6767c346cdf74505a7713bdd3a3bdbc6fbf3e Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Mon, 16 Feb 2026 17:26:18 +0300 Subject: [PATCH 41/59] fix transfer and deploy external message creation for ew and multisig --- src/utils/ton_wallet/ever_wallet.rs | 14 ++++++++------ src/utils/ton_wallet/multisig.rs | 18 +++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs index 3cdc2bc..274921e 100644 --- a/src/utils/ton_wallet/ever_wallet.rs +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -35,11 +35,11 @@ pub fn prepare_deploy( .with_id(0x169e3e11) .build(); - let unsigned_body = function + let mut unsigned_message = function .encode_external(&[]) + .with_pubkey(public_key) .with_expire_at(expire_at) - .build_input()?; - let mut unsigned_message = unsigned_body.with_dst(dst); + .build_message(&dst)?; unsigned_message.set_state_init(Some(state_init)); Ok(unsigned_message) } @@ -105,9 +105,11 @@ pub fn prepare_transfer( } }; - let external_input = function.encode_external(&tokens); - let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; - let mut unsigned_message = unsigned_body.with_dst(address); + let mut unsigned_message = function + .encode_external(&tokens) + .with_pubkey(public_key) + .with_expire_at(expire_at) + .build_message(&address)?; match ¤t_state.state { AccountState::Active { .. } => {} diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs index 7909d71..0c3217d 100644 --- a/src/utils/ton_wallet/multisig.rs +++ b/src/utils/ton_wallet/multisig.rs @@ -79,10 +79,12 @@ pub fn prepare_deploy( ); } - let external_input = function.encode_external(&abi_values); + let mut unsigned_message = function + .encode_external(&abi_values) + .with_pubkey(public_key) + .with_expire_at(expire_at) + .build_message(&dst)?; - let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; - let mut unsigned_message = unsigned_body.with_dst(dst); unsigned_message.set_state_init(Some(state_init)); Ok(unsigned_message) } @@ -791,15 +793,17 @@ fn extend_pending_update( } fn make_ext_message( - _public_key: &VerifyingKey, + public_key: &VerifyingKey, address: StdAddr, expire_at: u32, function: &'static Function, input: Vec, ) -> Result { - let external_input = function.encode_external(&input); - let unsigned_body = external_input.with_expire_at(expire_at).build_input()?; - let unsigned_message = unsigned_body.with_dst(address); + let unsigned_message = function + .encode_external(&input) + .with_pubkey(public_key) + .with_expire_at(expire_at) + .build_message(&address)?; Ok(unsigned_message) } From e94b52f7282e7798d176f2d246bf9ace8f5e165c Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Tue, 17 Feb 2026 14:46:37 +0300 Subject: [PATCH 42/59] fix prepare token transfers --- src/utils/token_wallet.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index c65d5e9..7be562a 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -57,8 +57,7 @@ pub fn prepare_token_transfer( } }; - let external_input = function.encode_external(&tokens); - let (_, body) = external_input.build_input_without_signature()?; + let body = function.encode_internal_input(&tokens)?.build()?; Ok(InternalMessage { source: Some(owner), @@ -98,8 +97,7 @@ pub fn prepare_token_burn( } }; - let external_input = function.encode_external(&tokens); - let (_, body) = external_input.build_input_without_signature()?; + let body = function.encode_internal_input(&tokens)?.build()?; Ok(InternalMessage { source: Some(owner), @@ -140,8 +138,7 @@ pub fn prepare_token_mint( } }; - let external_input = function.encode_external(&tokens); - let (_, body) = external_input.build_input_without_signature()?; + let body = function.encode_internal_input(&tokens)?.build()?; Ok(InternalMessage { source: Some(owner), From 6ea8840a9b96c25ac2836eecb3002a69a664c965 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 09:47:37 +0300 Subject: [PATCH 43/59] add confirm transaction to wallet.sh --- scripts/wallet.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/scripts/wallet.sh b/scripts/wallet.sh index 2ee7cf5..71431f1 100755 --- a/scripts/wallet.sh +++ b/scripts/wallet.sh @@ -23,6 +23,11 @@ function print_help() { echo ' --dst-addr Recipient address' echo ' --root-addr Root Token address' echo ' --amount Token amount' + echo '' + echo ' - confirm_transaction - confirm a pending multisig transaction.' + echo ' Options:' + echo ' --address Multisig wallet address' + echo ' --transaction-id Multisig transaction ID to confirm' } while [[ $# -gt 0 ]]; do @@ -98,6 +103,28 @@ while [[ $# -gt 0 ]]; do exit 1 fi ;; + --address) + address="$2" + shift # past argument + if [ "$#" -gt 0 ]; then shift; + else + echo 'ERROR: Expected address' + echo '' + print_help + exit 1 + fi + ;; + --transaction-id) + transaction_id="$2" + shift # past argument + if [ "$#" -gt 0 ]; then shift; + else + echo 'ERROR: Expected transaction ID' + echo '' + print_help + exit 1 + fi + ;; *) # unknown option echo 'ERROR: Unknown option' echo '' @@ -200,6 +227,28 @@ function create_token_transaction() { --data-raw "$body" } +function confirm_transaction() { + timestamp=$1 + address=$2 + transaction_id=$3 + + uri="/ton/v3/transactions/confirm" + body='{"id": "", "address": "", "transactionId": 0}' + body=$(echo "$body" | jq --indent 4 -r --arg id "$(uuidgen)" '.id = $id') + body=$(echo "$body" | jq --indent 4 -r --arg address "$address" '.address = $address') + body=$(echo "$body" | jq --indent 4 -r --argjson transaction_id "$transaction_id" '.transactionId = $transaction_id') + + stringToSign="$timestamp$uri$body" + signature=$(create_signature "$stringToSign") + + curl -s --location --request POST "$host$uri" \ + --header 'Content-Type: application/json' \ + --header "api-key: $api_key" \ + --header "timestamp: $timestamp" \ + --header "sign: $signature" \ + --data-raw "$body" +} + case $method in create_account) if [ -z "$account_type" ]; then @@ -264,6 +313,23 @@ case $method in timestamp=$(timestamp_ms) create_token_transaction "$timestamp" "$sender" "$recipient" "$root_address" "$amount" | jq . ;; + confirm_transaction) + if [ -z "$address" ]; then + echo 'ERROR: Skipped address' + echo '' + print_help + exit 1 + fi + if [ -z "$transaction_id" ]; then + echo 'ERROR: Skipped transaction ID' + echo '' + print_help + exit 1 + fi + + timestamp=$(timestamp_ms) + confirm_transaction "$timestamp" "$address" "$transaction_id" | jq . + ;; *) # unknown method echo 'ERROR: Unknown method' echo '' From 03b6e1a81026917d184303393f26b547bf2ad5cb Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 10:18:46 +0300 Subject: [PATCH 44/59] add wallet v5r1 support --- ...312966f50f802f8b1365b2fa20330277eb4ac.json | 3 +- ...45d536b2e7d85e0e86e22fa911156d9704168.json | 3 +- ...d10324cc45c67d8e5d28950dfafa7e98880da.json | 6 +- ...630b82402146a833bb310ae665a86f0f8fa52.json | 3 +- ...cd37bc5e6189b89d06a5920714433d0dc2574.json | 3 +- README.md | 7 ++ migrations/20260218000000_add_wallet_v5r1.sql | 1 + src/client/ton/mod.rs | 114 ++++++++++++++++-- src/models/account_enums.rs | 1 + 9 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 migrations/20260218000000_add_wallet_v5r1.sql diff --git a/.sqlx/query-957f4145c738e0c81e9f0d1c6ea312966f50f802f8b1365b2fa20330277eb4ac.json b/.sqlx/query-957f4145c738e0c81e9f0d1c6ea312966f50f802f8b1365b2fa20330277eb4ac.json index 6b6878d..f44a302 100644 --- a/.sqlx/query-957f4145c738e0c81e9f0d1c6ea312966f50f802f8b1365b2fa20330277eb4ac.json +++ b/.sqlx/query-957f4145c738e0c81e9f0d1c6ea312966f50f802f8b1365b2fa20330277eb4ac.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/.sqlx/query-b07560cf0c54dddc13cb6f941fa45d536b2e7d85e0e86e22fa911156d9704168.json b/.sqlx/query-b07560cf0c54dddc13cb6f941fa45d536b2e7d85e0e86e22fa911156d9704168.json index 8a9b19c..fba945e 100644 --- a/.sqlx/query-b07560cf0c54dddc13cb6f941fa45d536b2e7d85e0e86e22fa911156d9704168.json +++ b/.sqlx/query-b07560cf0c54dddc13cb6f941fa45d536b2e7d85e0e86e22fa911156d9704168.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/.sqlx/query-b6e0976bf2960fb60ed91fe0cded10324cc45c67d8e5d28950dfafa7e98880da.json b/.sqlx/query-b6e0976bf2960fb60ed91fe0cded10324cc45c67d8e5d28950dfafa7e98880da.json index 42aa2c4..d52ce84 100644 --- a/.sqlx/query-b6e0976bf2960fb60ed91fe0cded10324cc45c67d8e5d28950dfafa7e98880da.json +++ b/.sqlx/query-b6e0976bf2960fb60ed91fe0cded10324cc45c67d8e5d28950dfafa7e98880da.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } @@ -103,7 +104,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/.sqlx/query-bea373addcff988470868b63ed5630b82402146a833bb310ae665a86f0f8fa52.json b/.sqlx/query-bea373addcff988470868b63ed5630b82402146a833bb310ae665a86f0f8fa52.json index b818959..3eab25e 100644 --- a/.sqlx/query-bea373addcff988470868b63ed5630b82402146a833bb310ae665a86f0f8fa52.json +++ b/.sqlx/query-bea373addcff988470868b63ed5630b82402146a833bb310ae665a86f0f8fa52.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/.sqlx/query-cd402400d4b0c62f598ac78c03bcd37bc5e6189b89d06a5920714433d0dc2574.json b/.sqlx/query-cd402400d4b0c62f598ac78c03bcd37bc5e6189b89d06a5920714433d0dc2574.json index 00f5689..a5da8f1 100644 --- a/.sqlx/query-cd402400d4b0c62f598ac78c03bcd37bc5e6189b89d06a5920714433d0dc2574.json +++ b/.sqlx/query-cd402400d4b0c62f598ac78c03bcd37bc5e6189b89d06a5920714433d0dc2574.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/README.md b/README.md index ba43e5f..2cfc145 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,13 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif event a `Done` state by calling `/events/mark`. 2) by polling the GET method `/transactions/id/` + To confirm a pending multisig transaction, use the script: + ```bash + API_KEY=${API_KEY} SECRET=${API_SECRET} HOST=${HOST} \ + ./scripts/wallet.sh -m confirm_transaction \ + --address {multisig_wallet_address} --transaction-id {transaction_id} + ``` + 5. #### How to process a payment from a user on the backend We generate a deposit address for the user by calling `/address/create` with empty parameters. After receiving the payment, the backend receives a callback of the form `AccountTransactionEvent` (see [swagger](https://tonapi.broxus.com/swagger.yaml)). diff --git a/migrations/20260218000000_add_wallet_v5r1.sql b/migrations/20260218000000_add_wallet_v5r1.sql new file mode 100644 index 0000000..3249980 --- /dev/null +++ b/migrations/20260218000000_add_wallet_v5r1.sql @@ -0,0 +1 @@ +ALTER TYPE twa_account_type ADD VALUE 'WalletV5R1' AFTER 'EverWallet'; \ No newline at end of file diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 012cfa6..0addc0a 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -86,6 +86,9 @@ impl TonClient { AccountType::Wallet => { ton_wallet::wallet_v3::compute_contract_address(&public, workchain_id as i8) } + AccountType::WalletV5R1 => { + ton_wallet::wallet_v5r1::compute_contract_address(&public, workchain_id as i8) + } AccountType::SafeMultisig => ton_wallet::multisig::compute_contract_address( &public, MultisigType::SafeMultisigWallet, @@ -101,9 +104,10 @@ impl TonClient { Some(payload.custodians.unwrap_or(1)), Some(payload.confirmations.unwrap_or(1)), ), - AccountType::HighloadWallet | AccountType::Wallet | AccountType::EverWallet => { - (None, None) - } + AccountType::HighloadWallet + | AccountType::Wallet + | AccountType::WalletV5R1 + | AccountType::EverWallet => (None, None), }; if let (Some(custodians), Some(confirmations)) = (custodians, confirmations) { @@ -141,7 +145,10 @@ impl TonClient { Some(custodians) } - AccountType::HighloadWallet | AccountType::Wallet | AccountType::EverWallet => None, + AccountType::HighloadWallet + | AccountType::Wallet + | AccountType::WalletV5R1 + | AccountType::EverWallet => None, }; // Subscribe to accounts @@ -226,9 +233,21 @@ impl TonClient { address.workchain_id as i8, expire_at, )?, - AccountType::HighloadWallet | AccountType::Wallet => { - return Ok(None); - } + AccountType::WalletV5R1 => ton_wallet::wallet_v5r1::prepare_deploy( + &public_key, + address.workchain_id as i8, + expire_at, + )?, + AccountType::HighloadWallet => ton_wallet::highload_wallet_v2::prepare_deploy( + &public_key, + address.workchain_id as i8, + expire_at, + )?, + AccountType::Wallet => ton_wallet::wallet_v3::prepare_deploy( + &public_key, + address.workchain_id as i8, + expire_at, + )?, }; let mut key = [0u8; 32]; @@ -361,6 +380,41 @@ impl TonClient { expire_at, )? } + AccountType::WalletV5R1 => { + let account = address.address; + let current_state = self.ton_core.get_contract_state(&account)?.account; + + let mut gifts: Vec = vec![]; + for item in transaction.outputs { + let flags = item.output_type.unwrap_or_default(); + let (destination, _) = + StdAddr::from_str_ext(&item.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let amount = item + .value + .to_u128() + .ok_or(TonClientError::ParseBigDecimal)?; + + gifts.push(ton_wallet::Gift { + flags: flags.into(), + bounce, + destination, + amount, + body: body.clone(), + state_init: None, + }); + } + + let seqno_offset = 0; // TODO: implement seqno offset if needed + + ton_wallet::wallet_v5r1::prepare_transfer( + &public_key, + ¤t_state, + seqno_offset, + gifts, + expire_at, + )? + } AccountType::SafeMultisig => { let recipient = transaction .outputs @@ -963,6 +1017,29 @@ impl TonClient { expire_at, )? } + AccountType::WalletV5R1 => { + let account = address.address; + let current_state = self.ton_core.get_contract_state(&account)?.account; + + let gift = ton_wallet::Gift { + flags: execution_flag, + bounce, + destination, + amount, + body, + state_init: None, + }; + + let seqno_offset = 0; // TODO: implement seqno offset if needed + + ton_wallet::wallet_v5r1::prepare_transfer( + &public_key, + ¤t_state, + seqno_offset, + vec![gift], + expire_at, + )? + } AccountType::SafeMultisig => { let has_multiple_owners = match custodians { Some(custodians) => *custodians > 1, @@ -1140,6 +1217,29 @@ fn build_token_transaction( expire_at, )? } + AccountType::WalletV5R1 => { + let account = owner.address; + let current_state = ton_core.get_contract_state(&account)?.account; + + let gift = ton_wallet::Gift { + flags: flags.into(), + bounce, + destination, + amount, + body, + state_init: None, + }; + + let seqno_offset = 0; // TODO: implement seqno offset if needed + + ton_wallet::wallet_v5r1::prepare_transfer( + &public_key, + ¤t_state, + seqno_offset, + vec![gift], + expire_at, + )? + } AccountType::SafeMultisig => { let has_multiple_owners = match custodians { Some(custodians) => *custodians > 1, diff --git a/src/models/account_enums.rs b/src/models/account_enums.rs index 51bae02..a1a8563 100644 --- a/src/models/account_enums.rs +++ b/src/models/account_enums.rs @@ -19,6 +19,7 @@ pub enum AccountType { Wallet, SafeMultisig, EverWallet, + WalletV5R1, } #[derive(Debug, Deserialize, Serialize, Clone, JsonSchema, sqlx::Type, Eq, PartialEq)] From 0022ca8ba42c4147634d12c5ce3fd8ba3458967a Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 10:31:09 +0300 Subject: [PATCH 45/59] fix wallet v5r1 sql name --- src/models/account_enums.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/account_enums.rs b/src/models/account_enums.rs index a1a8563..8777885 100644 --- a/src/models/account_enums.rs +++ b/src/models/account_enums.rs @@ -19,6 +19,7 @@ pub enum AccountType { Wallet, SafeMultisig, EverWallet, + #[sqlx(rename = "WalletV5R1")] WalletV5R1, } From b40a3cbc4bca9d007e418442128d4ea9f57b9a6a Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 11:11:05 +0300 Subject: [PATCH 46/59] fix abi version for highload and wallet v3 --- src/utils/ton_wallet/highload_wallet_v2.rs | 2 +- src/utils/ton_wallet/wallet_v3.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 1794791..8d90d42 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -27,7 +27,7 @@ pub fn prepare_deploy( let unsigned_body = UnsignedBody { payload, hash, - abi_version: AbiVersion::V2_3, + abi_version: AbiVersion::V1_0, expire_at, }; let mut unsigned_message = unsigned_body.with_dst(dst); diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index bfc1f84..2484ec0 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -29,7 +29,7 @@ pub fn prepare_deploy( let unsigned_body = UnsignedBody { payload, hash, - abi_version: AbiVersion::V2_3, + abi_version: AbiVersion::V1_0, expire_at, }; let mut unsigned_message = unsigned_body.with_dst(dst); @@ -192,7 +192,7 @@ impl InitData { Self { seqno: 0, wallet_id: 0, - public_key: HashBytes::from_slice(key.as_bytes()), + public_key: HashBytes(key.to_bytes()), } } From ba407db92f7351114180b04f7a80b02c6c1658b1 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 11:21:44 +0300 Subject: [PATCH 47/59] fix prepare deploy for highload and wallet --- src/client/ton/mod.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 0addc0a..3516e5d 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -238,16 +238,9 @@ impl TonClient { address.workchain_id as i8, expire_at, )?, - AccountType::HighloadWallet => ton_wallet::highload_wallet_v2::prepare_deploy( - &public_key, - address.workchain_id as i8, - expire_at, - )?, - AccountType::Wallet => ton_wallet::wallet_v3::prepare_deploy( - &public_key, - address.workchain_id as i8, - expire_at, - )?, + AccountType::HighloadWallet | AccountType::Wallet => { + return Ok(None); + } }; let mut key = [0u8; 32]; From c2e8f98d0255ad3448f7603874375d3fab5624a1 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 11:27:48 +0300 Subject: [PATCH 48/59] fix abi in make transfer for highload and wallet v3 --- src/utils/ton_wallet/highload_wallet_v2.rs | 2 +- src/utils/ton_wallet/wallet_v3.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 8d90d42..2ce780e 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -71,7 +71,7 @@ pub fn prepare_transfer( let unsigned_body = UnsignedBody { payload, hash, - abi_version: AbiVersion::V2_3, + abi_version: AbiVersion::V1_0, expire_at, }; let mut unsigned_message = unsigned_body.with_dst( diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index 2484ec0..bb82d99 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -115,7 +115,7 @@ pub fn prepare_transfer( let unsigned_body = UnsignedBody { payload, hash, - abi_version: AbiVersion::V2_3, + abi_version: AbiVersion::V1_0, expire_at, }; let mut unsigned_message = unsigned_body.with_dst( From 60ecf16098bcb0bea7ef2013fca7fd8f5e9772e9 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 17:14:40 +0300 Subject: [PATCH 49/59] fix send and deploy of ton native wallets --- src/client/ton/mod.rs | 203 +++++++++------------ src/utils/ton_wallet/highload_wallet_v2.rs | 118 +++++++----- src/utils/ton_wallet/wallet_v3.rs | 101 ++++++---- src/utils/ton_wallet/wallet_v5r1.rs | 101 ++++++---- 4 files changed, 297 insertions(+), 226 deletions(-) diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 3516e5d..23b3a26 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -3,12 +3,14 @@ use std::sync::Arc; use axum::http::StatusCode; use bigdecimal::{BigDecimal, ToPrimitive}; +use ed25519_dalek::Signer; use ed25519_dalek::VerifyingKey; use nekoton_core::contracts::function_ext::ExecutionOutput; use nekoton_core::contracts::function_ext::FunctionExt; use num_bigint::BigUint; use num_traits::FromPrimitive; use tokio::sync::oneshot; +use tycho_types::abi::extend_signature_with_id; use tycho_types::abi::{Function, NamedAbiValue, UnsignedExternalMessage}; use tycho_types::boc::Boc; use tycho_types::cell::{CellBuilder, HashBytes}; @@ -233,12 +235,7 @@ impl TonClient { address.workchain_id as i8, expire_at, )?, - AccountType::WalletV5R1 => ton_wallet::wallet_v5r1::prepare_deploy( - &public_key, - address.workchain_id as i8, - expire_at, - )?, - AccountType::HighloadWallet | AccountType::Wallet => { + AccountType::WalletV5R1 | AccountType::HighloadWallet | AccountType::Wallet => { return Ok(None); } }; @@ -305,7 +302,17 @@ impl TonClient { .transpose() .map_err(anyhow::Error::from)?; - let unsigned_message = match account_type { + let mut key = [0u8; 32]; + key.copy_from_slice(private_key); + + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), + }; + + let owned_message = match account_type { AccountType::HighloadWallet => { let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; @@ -330,12 +337,17 @@ impl TonClient { state_init: None, }); } - ton_wallet::highload_wallet_v2::prepare_transfer( + let unsigned_message = ton_wallet::highload_wallet_v2::prepare_transfer( &public_key, ¤t_state, gifts, expire_at, - )? + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), self.ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::Wallet => { let account = address.address; @@ -365,13 +377,18 @@ impl TonClient { }]; let seqno_offset = ton_wallet::wallet_v3::estimate_seqno_offset(¤t_state, &[]); - ton_wallet::wallet_v3::prepare_transfer( + let unsigned_message = ton_wallet::wallet_v3::prepare_transfer( &public_key, ¤t_state, seqno_offset, gifts, expire_at, - )? + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), self.ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::WalletV5R1 => { let account = address.address; @@ -400,13 +417,18 @@ impl TonClient { let seqno_offset = 0; // TODO: implement seqno offset if needed - ton_wallet::wallet_v5r1::prepare_transfer( + let unsigned_message = ton_wallet::wallet_v5r1::prepare_transfer( &public_key, ¤t_state, seqno_offset, gifts, expire_at, - )? + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), self.ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::SafeMultisig => { let recipient = transaction @@ -434,14 +456,16 @@ impl TonClient { body, state_init: None, }; - ton_wallet::multisig::prepare_transfer( + let unsigned_message = ton_wallet::multisig::prepare_transfer( MultisigType::SafeMultisigWallet, &public_key, has_multiple_owners, address.clone(), gift, expire_at, - )? + )?; + + unsigned_message.sign(&key_pair, context)? } AccountType::EverWallet => { let account = address.address; @@ -466,28 +490,18 @@ impl TonClient { state_init: None, }); } - ton_wallet::ever_wallet::prepare_transfer( + let unsigned_message = ton_wallet::ever_wallet::prepare_transfer( &public_key, ¤t_state, address.clone(), gifts, expire_at, - )? - } - }; - - let mut key = [0u8; 32]; - key.copy_from_slice(private_key); - - let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + )?; - let context = SignatureContext { - global_id: self.ton_core.signature_id().unwrap_or_default(), - capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), + unsigned_message.sign(&key_pair, context)? + } }; - let owned_message = unsigned_message.sign(&key_pair, context)?; - let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let message_hash = cell_builder.repr_hash(); @@ -986,52 +1000,8 @@ impl TonClient { let amount = value.to_u128().ok_or(TonClientError::ParseBigDecimal)?; let unsigned_message = match account_type { - AccountType::Wallet => { - let account = address.address; - let current_state = self.ton_core.get_contract_state(&account)?.account; - - let gifts = vec![ton_wallet::Gift { - flags: execution_flag, - bounce, - destination, - amount, - body, - state_init: None, - }]; - - let seqno_offset = - ton_wallet::wallet_v3::estimate_seqno_offset(¤t_state, &[]); - - ton_wallet::wallet_v3::prepare_transfer( - &public_key, - ¤t_state, - seqno_offset, - gifts, - expire_at, - )? - } - AccountType::WalletV5R1 => { - let account = address.address; - let current_state = self.ton_core.get_contract_state(&account)?.account; - - let gift = ton_wallet::Gift { - flags: execution_flag, - bounce, - destination, - amount, - body, - state_init: None, - }; - - let seqno_offset = 0; // TODO: implement seqno offset if needed - - ton_wallet::wallet_v5r1::prepare_transfer( - &public_key, - ¤t_state, - seqno_offset, - vec![gift], - expire_at, - )? + AccountType::Wallet | AccountType::WalletV5R1 | AccountType::HighloadWallet => { + return Err(anyhow::anyhow!("Not implemented").into()); } AccountType::SafeMultisig => { let has_multiple_owners = match custodians { @@ -1057,26 +1027,6 @@ impl TonClient { expire_at, )? } - AccountType::HighloadWallet => { - let account = address.address; - let current_state = self.ton_core.get_contract_state(&account)?.account; - - let gift = ton_wallet::Gift { - flags: execution_flag, - bounce, - destination, - amount, - body, - state_init: None, - }; - - ton_wallet::highload_wallet_v2::prepare_transfer( - &public_key, - ¤t_state, - vec![gift], - expire_at, - )? - } AccountType::EverWallet => { let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; @@ -1166,7 +1116,17 @@ fn build_token_transaction( let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; - let unsigned_message = match account_type { + let mut key = [0u8; 32]; + key.copy_from_slice(private_key); + + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let context = SignatureContext { + global_id: ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(ton_core.capabilities()), + }; + + let owned_message = match account_type { AccountType::HighloadWallet => { let account = owner.address; let current_state = ton_core.get_contract_state(&account)?.account; @@ -1180,12 +1140,17 @@ fn build_token_transaction( state_init: None, }; - ton_wallet::highload_wallet_v2::prepare_transfer( + let unsigned_message = ton_wallet::highload_wallet_v2::prepare_transfer( &public_key, ¤t_state, vec![gift], expire_at, - )? + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::Wallet => { let account = owner.address; @@ -1202,13 +1167,18 @@ fn build_token_transaction( let seqno_offset = ton_wallet::wallet_v3::estimate_seqno_offset(¤t_state, &[]); - ton_wallet::wallet_v3::prepare_transfer( + let unsigned_message = ton_wallet::wallet_v3::prepare_transfer( &public_key, ¤t_state, seqno_offset, gifts, expire_at, - )? + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::WalletV5R1 => { let account = owner.address; @@ -1225,13 +1195,18 @@ fn build_token_transaction( let seqno_offset = 0; // TODO: implement seqno offset if needed - ton_wallet::wallet_v5r1::prepare_transfer( + let unsigned_message = ton_wallet::wallet_v5r1::prepare_transfer( &public_key, ¤t_state, seqno_offset, vec![gift], expire_at, - )? + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::SafeMultisig => { let has_multiple_owners = match custodians { @@ -1248,14 +1223,16 @@ fn build_token_transaction( state_init: None, }; - ton_wallet::multisig::prepare_transfer( + let unsigned_message = ton_wallet::multisig::prepare_transfer( MultisigType::SafeMultisigWallet, &public_key, has_multiple_owners, owner.clone(), gift, expire_at, - )? + )?; + + unsigned_message.sign(&key_pair, context)? } AccountType::EverWallet => { let account = owner.address; @@ -1270,28 +1247,18 @@ fn build_token_transaction( state_init: None, }; - ton_wallet::ever_wallet::prepare_transfer( + let unsigned_message = ton_wallet::ever_wallet::prepare_transfer( &public_key, ¤t_state, owner.clone(), vec![gift], expire_at, - )? - } - }; - - let mut key = [0u8; 32]; - key.copy_from_slice(private_key); - - let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + )?; - let context = SignatureContext { - global_id: ton_core.signature_id().unwrap_or_default(), - capabilities: GlobalCapabilities::new(ton_core.capabilities()), + unsigned_message.sign(&key_pair, context)? + } }; - let owned_message = unsigned_message.sign(&key_pair, context)?; - let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; let hash = cell_builder.repr_hash(); diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 2ce780e..35fea26 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -3,12 +3,11 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::VerifyingKey; use tycho_types::{ - abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, CellFamily, HashBytes, Load, Store}, dict::Dict, models::{ - Account, AccountState, CurrencyCollection, IntAddr, IntMsgInfo, Message, MsgInfo, - StateInit, StdAddr, + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, IntMsgInfo, Message, + MsgInfo, OwnedMessage, StateInit, StdAddr, }, }; @@ -20,20 +19,27 @@ pub fn prepare_deploy( public_key: &VerifyingKey, workchain: i8, expire_at: u32, -) -> Result { +) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); let dst = compute_contract_address(public_key, workchain)?; let (hash, payload) = init_data.make_deploy_payload(expire_at)?; - let unsigned_body = UnsignedBody { + let message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std(dst), + ..Default::default() + }), + body: Default::default(), + init: Some(init_data.make_state_init()?), + layout: None, + }; + Ok(UnsignedHighloadWalletV2Message { + init_data, + gifts: vec![], payload, hash, - abi_version: AbiVersion::V1_0, expire_at, - }; - let mut unsigned_message = unsigned_body.with_dst(dst); - let state_init = init_data.make_state_init()?; - unsigned_message.set_state_init(Some(state_init)); - Ok(unsigned_message) + message, + }) } pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { @@ -46,7 +52,7 @@ pub fn prepare_transfer( current_state: &Account, gifts: Vec, expire_at: u32, -) -> Result { +) -> Result { if gifts.len() > DETAILS.max_messages { return Err(HighloadWalletV2Error::TooManyGifts.into()); } @@ -68,35 +74,63 @@ pub fn prepare_transfer( } let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at)?; - let unsigned_body = UnsignedBody { - payload, - hash, - abi_version: AbiVersion::V1_0, - expire_at, + let mut message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std( + current_state + .address + .as_std() + .ok_or(HighloadWalletV2Error::InvalidAddress)? + .clone(), + ), + ..Default::default() + }), + body: Default::default(), + init: None, + layout: None, }; - let mut unsigned_message = unsigned_body.with_dst( - current_state - .address - .as_std() - .ok_or(HighloadWalletV2Error::InvalidAddress)? - .clone(), - ); + if with_state_init { - let state_init = init_data.make_state_init()?; - unsigned_message.set_state_init(Some(state_init)); + message.init = Some(init_data.make_state_init()?); } - Ok(unsigned_message) + Ok(UnsignedHighloadWalletV2Message { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }) } -#[allow(unused)] -struct UnsignedHighloadWalletV2Message { +pub struct UnsignedHighloadWalletV2Message { init_data: InitData, gifts: Vec, - payload: Cell, + payload: CellBuilder, hash: HashBytes, expire_at: u32, - message: UnsignedExternalMessage, + message: OwnedMessage, +} + +impl UnsignedHighloadWalletV2Message { + pub fn expire_at(&self) -> u32 { + self.expire_at + } + + pub fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; + + let mut message = self.message.clone(); + message.body = payload.build()?.into(); + + Ok(message) + } } pub static CODE_HASH: &[u8; 32] = &[ @@ -183,7 +217,7 @@ impl InitData { Ok(data) } - pub fn make_deploy_payload(&self, expire_at: u32) -> Result<(HashBytes, Cell)> { + pub fn make_deploy_payload(&self, expire_at: u32) -> Result<(HashBytes, CellBuilder)> { let mut builder = CellBuilder::new(); builder.store_u32(self.wallet_id)?; @@ -191,18 +225,18 @@ impl InitData { builder.store_u32(u32::MAX)?; builder.store_bit_zero()?; - let payload = builder.build()?; + let payload = builder.clone().build()?; let hash = payload.repr_hash(); - Ok((*hash, payload)) + Ok((*hash, builder)) } pub fn make_transfer_payload( &self, gifts: impl IntoIterator, expire_at: u32, - ) -> Result<(HashBytes, Cell)> { + ) -> Result<(HashBytes, CellBuilder)> { // Prepare messages array let mut messages = Dict::::new(); for (i, gift) in gifts.into_iter().enumerate() { @@ -237,16 +271,16 @@ impl InitData { let messages_hash = messages_cell.repr_hash(); // Build payload - let mut payload = CellBuilder::new(); - payload.store_u32(self.wallet_id)?; - payload.store_u32(expire_at)?; - payload.store_raw(&messages_hash.as_slice()[28..32], 32)?; - payload.store_builder(&message_builder)?; + let mut builder = CellBuilder::new(); + builder.store_u32(self.wallet_id)?; + builder.store_u32(expire_at)?; + builder.store_raw(&messages_hash.as_slice()[28..32], 32)?; + builder.store_builder(&message_builder)?; - let payload = payload.build()?; + let payload = builder.clone().build()?; let hash = payload.repr_hash(); - Ok((*hash, payload)) + Ok((*hash, builder)) } } diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index bb82d99..ff9db08 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -3,11 +3,10 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::VerifyingKey; use tycho_types::{ - abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes}, models::{ - Account, AccountState, CurrencyCollection, IntAddr, IntMsgInfo, Message, MsgInfo, - StateInit, StdAddr, + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, IntMsgInfo, Message, + MsgInfo, OwnedMessage, StateInit, StdAddr, }, }; use tycho_util::time::now_sec; @@ -21,21 +20,28 @@ pub fn prepare_deploy( public_key: &VerifyingKey, workchain: i8, expire_at: u32, -) -> Result { +) -> Result { let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); let dst = compute_contract_address(public_key, workchain)?; let (hash, payload) = init_data.make_transfer_payload(None, expire_at)?; - let unsigned_body = UnsignedBody { + let message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std(dst), + ..Default::default() + }), + body: Default::default(), + init: Some(init_data.make_state_init()?), + layout: None, + }; + Ok(UnsignedWalletV3Message { + init_data, + gifts: vec![], payload, hash, - abi_version: AbiVersion::V1_0, expire_at, - }; - let mut unsigned_message = unsigned_body.with_dst(dst); - let state_init = init_data.make_state_init()?; - unsigned_message.set_state_init(Some(state_init)); - Ok(unsigned_message) + message, + }) } pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { @@ -92,7 +98,7 @@ pub fn prepare_transfer( seqno_offset: u32, gifts: Vec, expire_at: u32, -) -> Result { +) -> Result { if gifts.len() > MAX_MESSAGES { return Err(WalletV3Error::TooManyGifts.into()); } @@ -112,35 +118,64 @@ pub fn prepare_transfer( init_data.seqno += seqno_offset; let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at)?; - let unsigned_body = UnsignedBody { - payload, - hash, - abi_version: AbiVersion::V1_0, - expire_at, + let mut message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std( + current_state + .address + .as_std() + .ok_or(WalletV3Error::InvalidAddress)? + .clone(), + ), + ..Default::default() + }), + body: Default::default(), + init: None, + layout: None, }; - let mut unsigned_message = unsigned_body.with_dst( - current_state - .address - .as_std() - .ok_or(WalletV3Error::InvalidAddress)? - .clone(), - ); + if with_state_init { let state_init = init_data.make_state_init()?; - unsigned_message.set_state_init(Some(state_init)); + message.init = Some(state_init); } - Ok(unsigned_message) + Ok(UnsignedWalletV3Message { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }) } -#[allow(unused)] -struct UnsignedWalletV3Message { +pub struct UnsignedWalletV3Message { init_data: InitData, gifts: Vec, - payload: Cell, + payload: CellBuilder, hash: HashBytes, expire_at: u32, - message: UnsignedExternalMessage, + message: OwnedMessage, +} + +impl UnsignedWalletV3Message { + pub fn expire_at(&self) -> u32 { + self.expire_at + } + + pub fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; + + let mut message = self.message.clone(); + message.body = payload.build()?.into(); + + Ok(message) + } } pub static CODE_HASH: &[u8; 32] = &[ @@ -229,7 +264,7 @@ impl InitData { &self, gifts: impl IntoIterator, expire_at: u32, - ) -> Result<(HashBytes, Cell)> { + ) -> Result<(HashBytes, CellBuilder)> { // insert prefix let mut builder = CellBuilder::new(); builder.store_u32(self.wallet_id)?; @@ -256,10 +291,10 @@ impl InitData { builder.store_reference(CellBuilder::build_from(internal_message)?)?; } - let payload = builder.build()?; + let payload = builder.clone().build()?; let hash = payload.repr_hash(); - Ok((*hash, payload)) + Ok((*hash, builder)) } } diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index e010fb7..99abead 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -3,11 +3,10 @@ use std::convert::TryFrom; use anyhow::Result; use ed25519_dalek::VerifyingKey; use tycho_types::{ - abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, cell::{Cell, CellBuilder, HashBytes, Lazy, Load}, models::{ - Account, AccountState, CurrencyCollection, IntAddr, OutAction, OwnedRelaxedMessage, - RelaxedIntMsgInfo, RelaxedMsgInfo, SendMsgFlags, StateInit, StdAddr, + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, OutAction, OwnedMessage, + OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, SendMsgFlags, StateInit, StdAddr, }, }; @@ -23,20 +22,27 @@ pub fn prepare_deploy( public_key: &VerifyingKey, workchain: i8, expire_at: u32, -) -> Result { +) -> Result { let init_data = make_init_data(public_key); let dst = compute_contract_address(public_key, workchain)?; + let message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std(dst), + ..Default::default() + }), + body: Default::default(), + init: Some(init_data.make_state_init()?), + layout: None, + }; let (hash, payload) = init_data.make_transfer_payload(None, expire_at, false)?; - let unsigned_body = UnsignedBody { + Ok(UnsignedWalletV5 { + init_data, + gifts: vec![], payload, hash, - abi_version: AbiVersion::V2_3, expire_at, - }; - let mut unsigned_message = unsigned_body.with_dst(dst); - let state_init = init_data.make_state_init()?; - unsigned_message.set_state_init(Some(state_init)); - Ok(unsigned_message) + message, + }) } pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { @@ -77,7 +83,7 @@ pub fn prepare_transfer( seqno_offset: u32, gifts: Vec, expire_at: u32, -) -> Result { +) -> Result { if gifts.len() > MAX_MESSAGES { return Err(WalletV5Error::TooManyGifts.into()); } @@ -87,35 +93,64 @@ pub fn prepare_transfer( let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at, false)?; - let unsigned_body = UnsignedBody { - payload, - hash, - abi_version: AbiVersion::V2_3, - expire_at, + let mut message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std( + current_state + .address + .as_std() + .ok_or(WalletV5Error::InvalidAddress)? + .clone(), + ), + ..Default::default() + }), + body: Default::default(), + init: None, + layout: None, }; - let mut unsigned_message = unsigned_body.with_dst( - current_state - .address - .as_std() - .ok_or(WalletV5Error::InvalidAddress)? - .clone(), - ); + if with_state_init { let state_init = init_data.make_state_init()?; - unsigned_message.set_state_init(Some(state_init)); + message.init = Some(state_init); } - Ok(unsigned_message) + Ok(UnsignedWalletV5 { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }) } -#[allow(unused)] -struct UnsignedWalletV5 { +pub struct UnsignedWalletV5 { init_data: InitData, gifts: Vec, - payload: Cell, + payload: CellBuilder, hash: HashBytes, expire_at: u32, - message: UnsignedExternalMessage, + message: OwnedMessage, +} + +impl UnsignedWalletV5 { + pub fn expire_at(&self) -> u32 { + self.expire_at + } + + pub fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.store_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; + + let mut message = self.message.clone(); + message.body = payload.build()?.into(); + + Ok(message) + } } pub static CODE_HASH: &[u8; 32] = &[ @@ -218,7 +253,7 @@ impl InitData { gifts: impl IntoIterator, expire_at: u32, is_internal_flow: bool, - ) -> Result<(HashBytes, Cell)> { + ) -> Result<(HashBytes, CellBuilder)> { // Check if signatures are allowed if !self.is_signature_allowed { return if self.extensions.is_none() { @@ -271,10 +306,10 @@ impl InitData { // has_other_actions builder.store_bit_zero()?; - let payload = builder.build()?; + let payload = builder.clone().build()?; let hash = payload.repr_hash(); - Ok((*hash, payload)) + Ok((*hash, builder)) } } From 8a3a0c3dadd258d47ecf14505a642528f21aa480 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 19:40:26 +0300 Subject: [PATCH 50/59] fix wallet v5 outactions --- src/utils/ton_wallet/wallet_v5r1.rs | 34 ++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index 99abead..f2ecd82 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -1,9 +1,9 @@ -use std::convert::TryFrom; +use std::{collections::LinkedList, convert::TryFrom}; use anyhow::Result; use ed25519_dalek::VerifyingKey; use tycho_types::{ - cell::{Cell, CellBuilder, HashBytes, Lazy, Load}, + cell::{Cell, CellBuilder, HashBytes, Lazy, Load, Store}, models::{ Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, OutAction, OwnedMessage, OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, SendMsgFlags, StateInit, StdAddr, @@ -276,7 +276,7 @@ impl InitData { builder.store_u32(expire_at)?; builder.store_u32(self.seqno)?; - let mut actions_builder = CellBuilder::new(); + let mut actions_builder = OutActions(Default::default()); for gift in gifts { let internal_message = Lazy::new(&OwnedRelaxedMessage { @@ -297,11 +297,11 @@ impl InitData { out_msg: internal_message, }; - actions_builder.store_reference(CellBuilder::build_from(action)?)?; + actions_builder.0.push_back(action); } builder.store_bit_one()?; - builder.store_reference(actions_builder.build()?)?; + builder.store_reference(CellBuilder::build_from(&actions_builder)?)?; // has_other_actions builder.store_bit_zero()?; @@ -336,6 +336,30 @@ impl TryFrom<&Cell> for InitData { } } +pub struct OutActions(pub LinkedList); + +impl Store for OutActions { + fn store_into( + &self, + builder: &mut CellBuilder, + context: &dyn tycho_types::prelude::CellContext, + ) -> std::result::Result<(), tycho_types::error::Error> { + let mut new_builder = CellBuilder::new(); + + for action in self.0.iter() { + let mut next_builder = CellBuilder::new(); + + next_builder.store_reference(new_builder.build()?)?; + action.store_into(&mut next_builder, context)?; + + new_builder = next_builder; + } + + builder.store_builder(&new_builder)?; + Ok(()) + } +} + const WALLET_ID: u32 = 0x7FFFFF11; #[derive(thiserror::Error, Debug)] From 62066d8f935246ab23d7d9c567d4b5183eb4a27f Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 20:32:11 +0300 Subject: [PATCH 51/59] fix serialize for highload wallet --- src/utils/ton_wallet/highload_wallet_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 35fea26..dc05faf 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -212,7 +212,7 @@ impl InitData { let dict = CellBuilder::build_from(&self.data)?; builder.store_bit_one()?; - builder.store_cell_data(&dict)?; + builder.store_reference(dict)?; let data = builder.build()?; Ok(data) } From aae639a431d2ebfba5f4a9c0653b6451c5c58ba9 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 20:40:19 +0300 Subject: [PATCH 52/59] fixed v3 transfer --- src/utils/ton_wallet/wallet_v3.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index ff9db08..03a2b5f 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -5,8 +5,8 @@ use ed25519_dalek::VerifyingKey; use tycho_types::{ cell::{Cell, CellBuilder, HashBytes}, models::{ - Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, IntMsgInfo, Message, - MsgInfo, OwnedMessage, StateInit, StdAddr, + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, MsgInfo, OwnedMessage, + OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, StdAddr, }, }; use tycho_util::time::now_sec; @@ -273,9 +273,8 @@ impl InitData { // create internal message for gift in gifts { - let body = gift.body.unwrap_or(Default::default()); - let internal_message = Message { - info: MsgInfo::Int(IntMsgInfo { + let internal_message = OwnedRelaxedMessage { + info: RelaxedMsgInfo::Int(RelaxedIntMsgInfo { ihr_disabled: true, bounce: gift.bounce, dst: IntAddr::Std(gift.destination), @@ -283,7 +282,7 @@ impl InitData { ..Default::default() }), init: gift.state_init, - body: body.as_slice()?, + body: gift.body.unwrap_or(Default::default()).into(), layout: None, }; // append it to the body From 2cfffc273a5b68c7b9d88b9d8b2c0dddf43e7f5d Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Wed, 18 Feb 2026 20:48:25 +0300 Subject: [PATCH 53/59] fix highload wallet serialize --- file.txt | 40 ++++++++++++++++++++++ src/utils/ton_wallet/highload_wallet_v2.rs | 16 ++++----- 2 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 file.txt diff --git a/file.txt b/file.txt new file mode 100644 index 0000000..3f61098 --- /dev/null +++ b/file.txt @@ -0,0 +1,40 @@ +broxus-bootstrap@common-test-server:~/ever-wallet-api$ API_KEY=key SECRET=secret HOST=localhost:8081 ./scripts/wallet.sh -m create_account --account-type HighloadWallet +{ + "status": "Ok", + "data": { + "workchainId": 0, + "hex": "de4fa940cba0c452e5fdb4f89448ad3b0713042f40e4e5bde3ef51577be9d2f0", + "base64url": "EQDeT6lAy6DEUuX9tPiUSK07BxMEL0Dk5b3j71FXe-nS8Mqq" + }, + "errorMessage": null +} +broxus-bootstrap@common-test-server:~/ever-wallet-api$ API_KEY=key SECRET=secret HOST=localhost:8081 ./scripts/wallet.sh -m create_account --account-type Wallet +{ + "status": "Ok", + "data": { + "workchainId": 0, + "hex": "4d8d0a10753279da7ae53673179275ee312aebbbebb5db82c7631dd09a2035d9", + "base64url": "EQBNjQoQdTJ52nrlNnMXknXuMSrru-u124LHYx3QmiA12dAY" + }, + "errorMessage": null +} +broxus-bootstrap@common-test-server:~/ever-wallet-api$ API_KEY=key SECRET=secret HOST=localhost:8081 ./scripts/wallet.sh -m create_account --account-type SafeMultisig +{ + "status": "Ok", + "data": { + "workchainId": 0, + "hex": "d7aa1344f1bb59780bf3807d13a6d6657a2ad986e75604d33aff47c26ced8062", + "base64url": "EQDXqhNE8btZeAvzgH0TptZleirZhudWBNM6_0fCbO2AYvVe" + }, + "errorMessage": null +} +broxus-bootstrap@common-test-server:~/ever-wallet-api$ API_KEY=key SECRET=secret HOST=localhost:8081 ./scripts/wallet.sh -m create_account --account-type WalletV5R1 +{ + "status": "Ok", + "data": { + "workchainId": 0, + "hex": "86256542aa7ee7b07550b00cf0883da2ba829839c89d0f4281584f7da2c39658", + "base64url": "EQCGJWVCqn7nsHVQsAzwiD2iuoKYOcidD0KBWE99osOWWBXJ" + }, + "errorMessage": null +} diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index dc05faf..ef61502 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -7,7 +7,8 @@ use tycho_types::{ dict::Dict, models::{ Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, IntMsgInfo, Message, - MsgInfo, OwnedMessage, StateInit, StdAddr, + MsgInfo, OwnedMessage, OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, + StdAddr, }, }; @@ -208,11 +209,7 @@ impl InitData { builder.store_u32(self.wallet_id)?; builder.store_u64(self.last_cleaned)?; builder.store_u256(&self.public_key)?; - - let dict = CellBuilder::build_from(&self.data)?; - - builder.store_bit_one()?; - builder.store_reference(dict)?; + self.data.store_into(&mut builder, Cell::empty_context())?; let data = builder.build()?; Ok(data) } @@ -240,9 +237,8 @@ impl InitData { // Prepare messages array let mut messages = Dict::::new(); for (i, gift) in gifts.into_iter().enumerate() { - let body = gift.body.unwrap_or(Default::default()); - let internal_message = Message { - info: MsgInfo::Int(IntMsgInfo { + let internal_message = OwnedRelaxedMessage { + info: RelaxedMsgInfo::Int(RelaxedIntMsgInfo { ihr_disabled: true, bounce: gift.bounce, dst: IntAddr::Std(gift.destination), @@ -250,7 +246,7 @@ impl InitData { ..Default::default() }), init: gift.state_init, - body: body.as_slice()?, + body: gift.body.unwrap_or(Default::default()).into(), layout: None, }; From 25d0df89db15dd86db6eb47315b2b6c1846b09e4 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 19 Feb 2026 10:16:46 +0300 Subject: [PATCH 54/59] refactoring --- file.txt | 40 ---------------------- src/utils/ton_wallet/highload_wallet_v2.rs | 17 +++++++-- src/utils/ton_wallet/wallet_v3.rs | 14 +++++++- src/utils/ton_wallet/wallet_v5r1.rs | 12 +++++++ 4 files changed, 39 insertions(+), 44 deletions(-) delete mode 100644 file.txt diff --git a/file.txt b/file.txt deleted file mode 100644 index 3f61098..0000000 --- a/file.txt +++ /dev/null @@ -1,40 +0,0 @@ -broxus-bootstrap@common-test-server:~/ever-wallet-api$ API_KEY=key SECRET=secret HOST=localhost:8081 ./scripts/wallet.sh -m create_account --account-type HighloadWallet -{ - "status": "Ok", - "data": { - "workchainId": 0, - "hex": "de4fa940cba0c452e5fdb4f89448ad3b0713042f40e4e5bde3ef51577be9d2f0", - "base64url": "EQDeT6lAy6DEUuX9tPiUSK07BxMEL0Dk5b3j71FXe-nS8Mqq" - }, - "errorMessage": null -} -broxus-bootstrap@common-test-server:~/ever-wallet-api$ API_KEY=key SECRET=secret HOST=localhost:8081 ./scripts/wallet.sh -m create_account --account-type Wallet -{ - "status": "Ok", - "data": { - "workchainId": 0, - "hex": "4d8d0a10753279da7ae53673179275ee312aebbbebb5db82c7631dd09a2035d9", - "base64url": "EQBNjQoQdTJ52nrlNnMXknXuMSrru-u124LHYx3QmiA12dAY" - }, - "errorMessage": null -} -broxus-bootstrap@common-test-server:~/ever-wallet-api$ API_KEY=key SECRET=secret HOST=localhost:8081 ./scripts/wallet.sh -m create_account --account-type SafeMultisig -{ - "status": "Ok", - "data": { - "workchainId": 0, - "hex": "d7aa1344f1bb59780bf3807d13a6d6657a2ad986e75604d33aff47c26ced8062", - "base64url": "EQDXqhNE8btZeAvzgH0TptZleirZhudWBNM6_0fCbO2AYvVe" - }, - "errorMessage": null -} -broxus-bootstrap@common-test-server:~/ever-wallet-api$ API_KEY=key SECRET=secret HOST=localhost:8081 ./scripts/wallet.sh -m create_account --account-type WalletV5R1 -{ - "status": "Ok", - "data": { - "workchainId": 0, - "hex": "86256542aa7ee7b07550b00cf0883da2ba829839c89d0f4281584f7da2c39658", - "base64url": "EQCGJWVCqn7nsHVQsAzwiD2iuoKYOcidD0KBWE99osOWWBXJ" - }, - "errorMessage": null -} diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index ef61502..3fa3538 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -6,9 +6,8 @@ use tycho_types::{ cell::{Cell, CellBuilder, CellFamily, HashBytes, Load, Store}, dict::Dict, models::{ - Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, IntMsgInfo, Message, - MsgInfo, OwnedMessage, OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, - StdAddr, + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, OwnedMessage, + OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, StdAddr, }, }; @@ -123,6 +122,18 @@ impl UnsignedHighloadWalletV2Message { self.hash.as_slice() } + pub fn gifts(&self) -> &[Gift] { + &self.gifts + } + + pub fn message(&self) -> &OwnedMessage { + &self.message + } + + pub fn init_data(&self) -> &InitData { + &self.init_data + } + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { let mut payload = self.payload.clone(); payload.prepend_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs index 03a2b5f..eb168ad 100644 --- a/src/utils/ton_wallet/wallet_v3.rs +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -5,7 +5,7 @@ use ed25519_dalek::VerifyingKey; use tycho_types::{ cell::{Cell, CellBuilder, HashBytes}, models::{ - Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, MsgInfo, OwnedMessage, + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, OwnedMessage, OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, StdAddr, }, }; @@ -167,6 +167,18 @@ impl UnsignedWalletV3Message { self.hash.as_slice() } + pub fn gifts(&self) -> &[Gift] { + &self.gifts + } + + pub fn message(&self) -> &OwnedMessage { + &self.message + } + + pub fn init_data(&self) -> &InitData { + &self.init_data + } + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { let mut payload = self.payload.clone(); payload.prepend_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs index f2ecd82..9470017 100644 --- a/src/utils/ton_wallet/wallet_v5r1.rs +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -142,6 +142,18 @@ impl UnsignedWalletV5 { self.hash.as_slice() } + pub fn gifts(&self) -> &[Gift] { + &self.gifts + } + + pub fn message(&self) -> &OwnedMessage { + &self.message + } + + pub fn init_data(&self) -> &InitData { + &self.init_data + } + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { let mut payload = self.payload.clone(); payload.store_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; From bd612d7fce7479e1e46f08ce15f7a3d4469402c4 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 19 Feb 2026 13:51:31 +0300 Subject: [PATCH 55/59] fix highload wallet transfer --- src/utils/ton_wallet/highload_wallet_v2.rs | 39 +++++++++++++++++----- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs index 3fa3538..c4f05e3 100644 --- a/src/utils/ton_wallet/highload_wallet_v2.rs +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -246,7 +246,7 @@ impl InitData { expire_at: u32, ) -> Result<(HashBytes, CellBuilder)> { // Prepare messages array - let mut messages = Dict::::new(); + let mut messages = Dict::::new(); for (i, gift) in gifts.into_iter().enumerate() { let internal_message = OwnedRelaxedMessage { info: RelaxedMsgInfo::Int(RelaxedIntMsgInfo { @@ -263,12 +263,8 @@ impl InitData { let cell = CellBuilder::build_from(internal_message)?; - let mut item = CellBuilder::new(); - item.store_u8(gift.flags)?; - item.store_reference(cell)?; - let key = i as u16; - messages.set(key, item.build()?)?; + messages.set(key, (gift.flags, cell))?; } let mut message_builder = CellBuilder::new(); @@ -331,9 +327,17 @@ enum HighloadWalletV2Error { #[cfg(test)] pub mod tests { use anyhow::Result; - use tycho_types::{boc::Boc, cell::Load, models::AccountState}; + use ed25519_dalek::VerifyingKey; + use tycho_types::{ + boc::Boc, + cell::Load, + models::{AccountState, StdAddr}, + }; - use crate::utils::ton_wallet::highload_wallet_v2::InitData; + use crate::utils::ton_wallet::{ + highload_wallet_v2::{InitData, WALLET_ID}, + Gift, + }; #[test] fn check_state() -> Result<()> { @@ -354,4 +358,23 @@ pub mod tests { Ok(()) } + + #[test] + fn test_init_data() { + let p = hex::decode("b76bf868d742e291c9f1a9a1a47dc79728456d5b74b07471dded4bc8f06d5d8a") + .unwrap(); + let public_key = VerifyingKey::from_bytes(&p.try_into().unwrap()).unwrap(); + let init_data = InitData::from_key(&public_key).with_wallet_id(WALLET_ID); + let gifts = vec![Gift { + amount: 0, + state_init: None, + body: None, + flags: 0, + bounce: false, + destination: StdAddr::default(), + }]; + //let gifts = vec![]; + let (hash, payload) = init_data.make_transfer_payload(gifts, 0).unwrap(); + println!("{hash:?} {}", Boc::encode_base64(payload.build().unwrap())); + } } From 92138f05b341ce6264a8ad9669cad38615457e2e Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 19 Feb 2026 16:02:13 +0300 Subject: [PATCH 56/59] rename ever to tycho, make default account type wallet v5r1 --- README.md | 43 ++++++++++++++------------- builder.dockerfile | 8 ++--- nix/local-test.nix | 26 ++++++++-------- nix/module.nix | 38 +++++++++++------------ src/models/account_enums.rs | 2 +- src/sqlx_client/token_transactions.rs | 2 +- 6 files changed, 60 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 2cfc145..2feb430 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

-

Everscale Wallet API

+

Tycho Wallet API

- - Logo - -

-

Tycho Wallet API

@@ -531,7 +525,7 @@ for setting up the deployment environment. 1. **Build the builder image**: The `builder.dockerfile` is responsible for compiling the project using Rust. It builds the project based on the - specified network (either `tycho` or `venom`) and prepares the database for the application using SQLx. + specified network (either `tycho` or other) and prepares the database for the application using SQLx. Use the following command to build the builder image: @@ -591,7 +585,7 @@ Once the images are built, you can run the container using Podman or Docker. ### Troubleshooting -When the node is out of sync, which especially applies for Venom, removing database and re-syncing node may help to +When the node is out of sync, removing database and re-syncing node may help to restore service operations. `rm -rf /var/db/tycho-wallet-api` diff --git a/builder.dockerfile b/builder.dockerfile index 6c56515..9809f72 100644 --- a/builder.dockerfile +++ b/builder.dockerfile @@ -42,9 +42,6 @@ RUN cargo sqlx migrate run --database-url "$DATABASE_URL" RUN if [ "$NETWORK" = "tycho" ]; then \ cargo sqlx prepare && \ RUSTFLAGS="-C target_cpu=native" SQLX_OFFLINE=true cargo build --release; \ - elif [ "$NETWORK" = "venom" ]; then \ - cargo sqlx prepare && \ - RUSTFLAGS="-C target_cpu=native" SQLX_OFFLINE=true cargo build --release --features venom; \ else \ echo 'ERROR: Unexpected network'; \ exit 1; \ diff --git a/nix/module.nix b/nix/module.nix index d8dc2f6..be83794 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -42,7 +42,7 @@ in { type = types.str; default = "Tycho"; description = '' - Which blockchain to use: Tycho, Venom + Which blockchain to use: Tycho, any other ''; }; @@ -263,7 +263,7 @@ in { # Create systemd service systemd.services.tycho-wallet-api = { enable = true; - description = "Service that indexes transactions for Tycho or Venom"; + description = "Service that indexes transactions for Tycho or any other"; after = ["network.target" cfg.dbPasswordFileService cfg.tychoSecretFileService cfg.tychoSaltFileService]; wants = ["network.target" cfg.dbPasswordFileService cfg.tychoSecretFileService cfg.tychoSaltFileService]; path = with pkgs; [ ]; From 3ab1a3c849a7fcb2bc44d71073b456064accc9eb Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Fri, 20 Feb 2026 10:30:15 +0300 Subject: [PATCH 58/59] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63075c5..e89376e 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif ```bash API_KEY=${API_KEY} SECRET=${API_SECRET} HOST=${HOST} \ - ./scripts/wallet.sh -m create_account + ./scripts/wallet.sh -m create_account --account-type WalletV5R1 ``` 2. #### Callbacks From 36ab7e929267381b0eda28a688d3b194bda470c9 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Fri, 20 Feb 2026 10:34:56 +0300 Subject: [PATCH 59/59] update deps --- Cargo.lock | 149 +++++++++++++++++++++++++++++------------------------ Cargo.toml | 4 +- 2 files changed, 83 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c52b8d..9f2c2c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,7 +76,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -146,9 +146,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" @@ -191,7 +191,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -217,9 +217,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -321,7 +321,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -377,7 +377,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -434,9 +434,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bumpalo-herd" @@ -621,9 +621,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -631,9 +631,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -650,7 +650,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -892,7 +892,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -926,7 +926,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -940,7 +940,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -951,7 +951,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -962,7 +962,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1024,7 +1024,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.116", + "syn 2.0.117", "unicode-xid", ] @@ -1057,7 +1057,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1135,7 +1135,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1339,7 +1339,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -1795,7 +1795,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -2386,9 +2386,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -2594,7 +2594,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2750,7 +2750,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2780,7 +2780,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -2886,7 +2886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3155,7 +3155,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3456,7 +3456,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3473,9 +3473,9 @@ checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -3486,9 +3486,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.16.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3527,7 +3527,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3538,7 +3538,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3790,7 +3790,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -3813,7 +3813,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.116", + "syn 2.0.117", "tokio", "url", ] @@ -3972,9 +3972,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3998,7 +3998,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4081,7 +4081,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4092,7 +4092,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4243,7 +4243,7 @@ dependencies = [ "proc-macro2", "quote", "rustc-hash 2.1.1", - "syn 2.0.116", + "syn 2.0.117", "tl-scheme", ] @@ -4285,7 +4285,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4405,7 +4405,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4716,7 +4716,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4726,7 +4726,7 @@ source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4779,7 +4779,7 @@ checksum = "723c91dccdb15a705b0448705f631c6e32298931013c054e54f6ffd9cb592341" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4814,7 +4814,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -4863,7 +4863,7 @@ dependencies = [ "sha2 0.10.9", "slip10_ed25519", "sqlx", - "thiserror 1.0.69", + "thiserror 2.0.18", "tikv-jemallocator", "tiny-bip39", "tiny-hderive", @@ -5106,7 +5106,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -5243,7 +5243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core", + "windows-core 0.61.2", "windows-future", "windows-link 0.1.3", "windows-numerics", @@ -5255,7 +5255,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", ] [[package]] @@ -5271,13 +5271,26 @@ dependencies = [ "windows-strings 0.4.2", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-future" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", "windows-threading", ] @@ -5290,7 +5303,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5301,7 +5314,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5322,7 +5335,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", + "windows-core 0.61.2", "windows-link 0.1.3", ] @@ -5700,7 +5713,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.116", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -5716,7 +5729,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -5783,7 +5796,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -5804,7 +5817,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5824,7 +5837,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", "synstructure", ] @@ -5845,7 +5858,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] @@ -5878,7 +5891,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.116", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 54188cd..2b31e7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ base64 = "0.22" bigdecimal = { version = "0.4.5", features = ["serde"] } chacha20poly1305 = "0.10.1" chrono = { version = "0.4", features = ["serde"] } -clap = { version = "4.5.3", features = ["derive"] } +clap = { version = "4.5", features = ["derive"] } dashmap = "6.1" derive_more = { version = "2", features = ["full"] } futures = "0.3" @@ -47,7 +47,7 @@ serde_json = "1.0" sha2 = { version = "0.10.8" } slip10_ed25519 = "0.1.3" sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres", "uuid", "bigdecimal", "chrono", "json"] } -thiserror = "1.0" +thiserror = "2.0" tiny-hderive = { git = "https://github.com/broxus/tiny-hderive.git" } tiny-bip39 = { git = "https://github.com/broxus/tiny-bip39.git", default-features = false } tokio = { version = "1", features = ["sync", "fs", "rt-multi-thread", "macros", "signal", "parking_lot"] }

GitHub @@ -15,7 +15,7 @@ ### Overview This is a light node + api for sending and tracking payments. The app listens for addresses from the database and -indexes all transactions, putting information about them in the postsgres DB. All transactions with native EVERs are +indexes all transactions, putting information about them in the postsgres DB. All transactions with native TYCHOs are tracked, and there is a whitelist of root token addresses to be tracked in the settings. There is a callbacks table in the database, where you can specify the url of your backend to which callbacks will come for all transactions. @@ -133,8 +133,9 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif ### Let's start using Wallet API 1. #### Create address - Create yourself a "system address" by calling `/address/create` with empty parameters. The response will return a EVER - address. It is necessary to send EVERs on it, which will be consumed as gas for further work. + Create yourself a "system address" by calling `/address/create` with empty parameters. The response will return a TYCHO + address. It is necessary to send TYCHOs on it, which will be consumed as gas for further work. Default used account type is + `Wallet v5 r1`. **For simplicity, you use the script** @@ -162,7 +163,7 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif TOKEN_ADDRESS - Token address (example: 0:0ee39330eddb680ce731cd6a443c71d9069db06d149a9bec9569d1eb8d04eb37) TOKEN_CONTRACT_VERSION - "Tip3" or "OldTip3v4" -4. #### Transfer EVER +4. #### Transfer TYCHO Example request: ``` /transactions/create @@ -174,11 +175,11 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif "bounce":false, "outputs":[ { - // how much EVER to send. To send 1 EVER this value = 1000000000 + // how much TYCHO to send. To send 1 TYCHO this value = 1000000000 "value":"1000000000", - // Set Normal to take the number of sent EVERs from the value + // Set Normal to take the number of sent TYCHOs from the value "outputType":"Normal", - // Recipient address of EVERs + // Recipient address of TYCHOs "recipientAddress":"0:0000000000000000000000000000000000000000000000000000000000000000" } ], @@ -223,7 +224,7 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif 6. #### Transfer tokens First, check the status and balance of the address you want to send tokens from by making a GET request to /address/{string}. - The address you are sending tokens from must have at least 0.6 EVER (balance >= 600000000). + The address you are sending tokens from must have at least 0.6 TYCHO (balance >= 600000000). To transfer tokens, use the method: ``` @@ -233,13 +234,13 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif "id":"00000000-0000-0000-0000-000000000000", // The address of the sender. For example, your system address. "fromAddress":"0:0000000000000000000000000000000000000000000000000000000000000000", - // Recipient address of EVERs + // Recipient address of TYCHOs "recipientAddress":"0:0000000000000000000000000000000000000000000000000000000000000000", // The number of tokens with decimals. For example, for transferring 1 USDT this value = "1000000" "value":"1000000000", - // How much to apply EVER, the default recommended value is 0.5 EVER. The funds will be debited fromAddress. + // How much to apply TYCHO, the default recommended value is 0.5 TYCHO. The funds will be debited fromAddress. "fee": "5000000000", - // The address to which to return the residuals EVER. For example, your system address. + // The address to which to return the residuals TYCHO. For example, your system address. "sendGasTo":"0:0000000000000000000000000000000000000000000000000000000000000000", // Token Address from whitelist "rootAddress":"0:0000000000000000000000000000000000000000000000000000000000000000", @@ -530,12 +531,12 @@ for setting up the deployment environment. 1. **Build the builder image**: The `builder.dockerfile` is responsible for compiling the project using Rust. It builds the project based on the - specified network (either `everscale` or `venom`) and prepares the database for the application using SQLx. + specified network (either `tycho` or `venom`) and prepares the database for the application using SQLx. Use the following command to build the builder image: ```bash - podman build --layers --network=host -f builder.dockerfile -t builder --build-arg DATABASE_URL="postgresql://everscale:everscale@localhost:5432/everscale" + podman build --layers --network=host -f builder.dockerfile -t builder --build-arg DATABASE_URL="postgresql://tycho:tycho@localhost:5432/tycho" ``` 2. **Build the deployment image**: @@ -545,7 +546,7 @@ for setting up the deployment environment. Build the deployment image using the following command: ```bash - podman build --layers -f deploy.dockerfile -t ever-wallet + podman build --layers -f deploy.dockerfile -t tycho-wallet ``` #### Running the Container @@ -557,7 +558,7 @@ Once the images are built, you can run the container using Podman or Docker. To run the application, use the following command: ```bash - podman run --network=host ever-wallet + podman run --network=host tycho-wallet ``` This will run the `tycho-wallet-api` server using the default configuration files already existing in the container. @@ -575,14 +576,14 @@ Once the images are built, you can run the container using Podman or Docker. ```bash podman run --network=host \ - -v /tmp/everscale-data:/var/db/tycho-wallet-api - -e DB_USER=everscale \ - -e DB_PASSWORD=everscale \ + -v /tmp/tycho-data:/var/db/tycho-wallet-api + -e DB_USER=tycho \ + -e DB_PASSWORD=tycho \ -e DB_HOST=localhost \ - -e DB_NAME=everscale \ + -e DB_NAME=tycho \ -e SECRET=0xAAAAA \ -e SALT=OreOYYe5nHWTHnOPSvsmMQ \ - ever-wallet + tycho-wallet ``` It generally allows dynamically setting environment variables for database credentials, secrets, and other diff --git a/builder.dockerfile b/builder.dockerfile index 56a3bad..6c56515 100644 --- a/builder.dockerfile +++ b/builder.dockerfile @@ -30,16 +30,16 @@ WORKDIR /app # Copy the source code into the Docker image COPY . /app -# Define a build argument to pass in the network (default to "Everscale") -ARG NETWORK="everscale" -ARG DATABASE_URL=postgres://everscale:everscale@localhost:5432/everscale +# Define a build argument to pass in the network (default to "Tycho") +ARG NETWORK="tycho" +ARG DATABASE_URL=postgres://tycho:tycho@localhost:5432/tycho # Migrations first, otherwise it may not compile RUN cargo sqlx database create --database-url "$DATABASE_URL" RUN cargo sqlx migrate run --database-url "$DATABASE_URL" # Build the project based on the network variable -RUN if [ "$NETWORK" = "everscale" ]; then \ +RUN if [ "$NETWORK" = "tycho" ]; then \ cargo sqlx prepare && \ RUSTFLAGS="-C target_cpu=native" SQLX_OFFLINE=true cargo build --release; \ elif [ "$NETWORK" = "venom" ]; then \ diff --git a/nix/local-test.nix b/nix/local-test.nix index 001ac6e..e960f27 100644 --- a/nix/local-test.nix +++ b/nix/local-test.nix @@ -10,44 +10,44 @@ services.tycho-wallet-api = { enable = true; port = 7354; - chain = "Everscale"; - dbPasswordFile = "/var/everwalletapidb"; # fill it with password - everSecretFile = "/var/everwalletapisecret"; - everSaltFile = "/var/everwalletapisalt"; + chain = "Tycho"; + dbPasswordFile = "/var/tychowalletapidb"; # fill it with password + tychoSecretFile = "/var/tychowalletapisecret"; + tychoSaltFile = "/var/tychowalletapisalt"; metricsHost = "0.0.0.0"; }; systemd.services = { - everwalletapidb-key = { + tychowalletapidb-key = { enable = true; - description = "Ever wallet API password for PostgreSQL is provided"; + description = "Tycho wallet API password for PostgreSQL is provided"; wantedBy = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; script = '' - echo "Ever wallet API password for PostgreSQL is done" + echo "Tycho wallet API password for PostgreSQL is done" ''; }; - everwalletapisecret-key = { + tychowalletapisecret-key = { enable = true; - description = "Ever wallet encryption secret is provided"; + description = "Tycho wallet encryption secret is provided"; wantedBy = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; script = '' - echo "Ever wallet encryption secret is done" + echo "Tycho wallet encryption secret is done" ''; }; - everwalletapisalt-key = { + tychowalletapisalt-key = { enable = true; - description = "Ever wallet encryption salt is provided"; + description = "Tycho wallet encryption salt is provided"; wantedBy = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; script = '' - echo "Ever wallet encryption salt is done" + echo "Tycho wallet encryption salt is done" ''; }; }; diff --git a/nix/module.nix b/nix/module.nix index 57f7fe6..d8dc2f6 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -40,9 +40,9 @@ in { }; chain = mkOption { type = types.str; - default = "Everscale"; + default = "Tycho"; description = '' - Which blockchain to use: Everscale, Venom + Which blockchain to use: Tycho, Venom ''; }; @@ -199,44 +199,44 @@ in { dbPasswordFile = mkOption { type = types.str; - default = "/run/keys/everwalletapidb"; + default = "/run/keys/tychowalletapidb"; description = '' Location of file with password for RPC. ''; }; dbPasswordFileService = mkOption { type = types.str; - default = "everwalletapidb-key.service"; + default = "tychowalletapidb-key.service"; description = '' Service that indicates that dbPasswordFile is ready. ''; }; - everSecretFile = mkOption { + tychoSecretFile = mkOption { type = types.str; - default = "/run/keys/everwalletapisecret"; + default = "/run/keys/tychowalletapisecret"; description = '' Location of file with secret for decrypting transactions. ''; }; - everSecretFileService = mkOption { + tychoSecretFileService = mkOption { type = types.str; - default = "everwalletapisecret-key.service"; + default = "tychowalletapisecret-key.service"; description = '' - Service that indicates that everSecretFile is ready. + Service that indicates that tychoSecretFile is ready. ''; }; - everSaltFile = mkOption { + tychoSaltFile = mkOption { type = types.str; - default = "/run/keys/everwalletapisalt"; + default = "/run/keys/tychowalletapisalt"; description = '' Location of file with salt for ???. ''; }; - everSaltFileService = mkOption { + tychoSaltFileService = mkOption { type = types.str; - default = "everwalletapisalt-key.service"; + default = "tychowalletapisalt-key.service"; description = '' - Service that indicates that everSaltFile is ready. + Service that indicates that tychoSaltFile is ready. ''; }; }; @@ -263,14 +263,14 @@ in { # Create systemd service systemd.services.tycho-wallet-api = { enable = true; - description = "Service that indexes transactions for Ever or Venom"; - after = ["network.target" cfg.dbPasswordFileService cfg.everSecretFileService cfg.everSaltFileService]; - wants = ["network.target" cfg.dbPasswordFileService cfg.everSecretFileService cfg.everSaltFileService]; + description = "Service that indexes transactions for Tycho or Venom"; + after = ["network.target" cfg.dbPasswordFileService cfg.tychoSecretFileService cfg.tychoSaltFileService]; + wants = ["network.target" cfg.dbPasswordFileService cfg.tychoSecretFileService cfg.tychoSaltFileService]; path = with pkgs; [ ]; script = '' export DB_PASSWORD=$(cat ${cfg.dbPasswordFile} | xargs echo -n) - export SECRET=$(cat ${cfg.everSecretFile} | xargs echo -n) - export SALT=$(cat ${cfg.everSaltFile} | xargs echo -n) + export SECRET=$(cat ${cfg.tychoSecretFile} | xargs echo -n) + export SALT=$(cat ${cfg.tychoSaltFile} | xargs echo -n) ${cfg.package}/bin/tycho-wallet-api server \ --config /etc/${cfg.configdir}/config.json \ diff --git a/src/models/account_enums.rs b/src/models/account_enums.rs index 8777885..07c5bd4 100644 --- a/src/models/account_enums.rs +++ b/src/models/account_enums.rs @@ -14,12 +14,12 @@ use crate::{ )] #[sqlx(type_name = "twa_account_type", rename_all = "PascalCase")] pub enum AccountType { - #[default] HighloadWallet, Wallet, SafeMultisig, EverWallet, #[sqlx(rename = "WalletV5R1")] + #[default] WalletV5R1, } diff --git a/src/sqlx_client/token_transactions.rs b/src/sqlx_client/token_transactions.rs index 5a62183..0eae5f6 100644 --- a/src/sqlx_client/token_transactions.rs +++ b/src/sqlx_client/token_transactions.rs @@ -271,7 +271,7 @@ impl SqlxClient { #[cfg(test)] async fn prepare_test() -> SqlxClient { let pg_pool = - PgPool::connect("postgresql://everscale:everscale@localhost:5432/tycho_wallet_api_rs") + PgPool::connect("postgresql://tycho:tycho@localhost:5432/tycho_wallet_api_rs") .await .unwrap(); From c4a995f66fa644ddfd5b0082165dccc9488fb2e6 Mon Sep 17 00:00:00 2001 From: serejkaaa512 <5125402@mail.ru> Date: Thu, 19 Feb 2026 16:18:01 +0300 Subject: [PATCH 57/59] remove venom --- README.md | 10 ++-------- builder.dockerfile | 3 --- nix/module.nix | 4 ++-- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2feb430..63075c5 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -