From a45653e5e5ea0d93483767a950f402feca9ee02a Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Mon, 10 Jun 2024 15:38:08 +0000 Subject: [PATCH 01/29] Downstream 0.15.2 --- Cargo.toml | 2 +- src/cell/raw.rs | 3 ++- src/tl/types.rs | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e555422..e5540658 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.15.0-dev" +version = "0.15.3-dev" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" diff --git a/src/cell/raw.rs b/src/cell/raw.rs index 4cd87486..6c4e4ae0 100644 --- a/src/cell/raw.rs +++ b/src/cell/raw.rs @@ -297,9 +297,10 @@ fn read_var_size( #[cfg(test)] mod tests { - use super::*; use tokio_test::assert_ok; + use super::*; + #[test] fn test_raw_cell_serialize() { let raw_cell = RawCell { diff --git a/src/tl/types.rs b/src/tl/types.rs index 6c4eb993..cee58ba7 100644 --- a/src/tl/types.rs +++ b/src/tl/types.rs @@ -312,6 +312,12 @@ pub enum AccountState { wallet_id: i64, seqno: i32, }, + #[serde(rename = "wallet.v4.accountState")] + WalletV4 { + #[serde(deserialize_with = "deserialize_number_from_string")] + wallet_id: i64, + seqno: i32, + }, #[serde(rename = "wallet.highload.v1.accountState")] WalletHighloadV1 { #[serde(deserialize_with = "deserialize_number_from_string")] From 90541b6360401fa721638006817fe7c94d0c9f6a Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Wed, 12 Jun 2024 09:38:18 +0000 Subject: [PATCH 02/29] Downstream 0.15.3 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5540658..99735b5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.15.3-dev" +version = "0.15.4-dev" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" @@ -46,7 +46,7 @@ reqwest = "0.12" thiserror = "1" tokio = { version = "1", features = ["rt","macros"] } tokio-retry = "0.3" -tonlib-sys = "=2024.3.7" +tonlib-sys = "=2024.6.0" [dev-dependencies] anyhow = "1" From 70223d3ea4eeed068292159d68a1efe68547ab8d Mon Sep 17 00:00:00 2001 From: Dmitrii Korchagin Date: Thu, 6 Jun 2024 11:37:36 +0200 Subject: [PATCH 03/29] Impl 114: store_uint, store_int + tests --- src/cell/builder.rs | 221 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 171 insertions(+), 50 deletions(-) diff --git a/src/cell/builder.rs b/src/cell/builder.rs index 92178a64..730ab0bf 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -1,8 +1,9 @@ +use std::ops::Add; use std::sync::Arc; use bitstream_io::{BigEndian, BitWrite, BitWriter}; -use num_bigint::{BigInt, BigUint}; -use num_traits::Zero; +use num_bigint::{BigInt, BigUint, Sign}; +use num_traits::{One, Zero}; use crate::address::TonAddress; use crate::cell::error::{MapTonCellError, TonCellError}; @@ -75,68 +76,64 @@ impl CellBuilder { pub fn store_uint(&mut self, bit_len: usize, val: &BigUint) -> Result<&mut Self, TonCellError> { if val.bits() as usize > bit_len { return Err(TonCellError::cell_builder_error(format!( - "Value {} doesn't fit in {} bits", - val, bit_len + "Value {} doesn't fit in {} bits (takes {} bits)", + val, + bit_len, + val.bits() ))); } - let bytes = val.to_bytes_be(); - let num_full_bytes = bit_len / 8; - let num_bits_in_high_byte = bit_len % 8; - if bytes.len() > num_full_bytes + 1 { - return Err(TonCellError::cell_builder_error(format!( - "Internal error: can't fit {} into {} bits ", - val, bit_len - ))); + // example: bit_len=13, val=5. 5 = 00000101, we must store 0000000000101 + // leading_zeros_bytes = 10 + // leading_zeros_bytes = 10 / 8 = 1 + let leading_zero_bits = bit_len - val.bits() as usize; + let leading_zeros_bytes = leading_zero_bits / 8; + for _ in 0..leading_zeros_bytes { + self.store_byte(0)?; } - if num_bits_in_high_byte > 0 { - let high_byte: u8 = if bytes.len() == num_full_bytes + 1 { - bytes[0] - } else { - 0 - }; - self.store_u8(num_bits_in_high_byte, high_byte)?; + // we must align high byte of val to specified bit_len, 00101 in our case + let extra_zeros = leading_zero_bits % 8; + for _ in 0..extra_zeros { + self.store_bit(false)?; } - let num_empty_bytes = num_full_bytes - bytes.len(); - for _ in 0..num_empty_bytes { - self.store_byte(0)?; + // and then store val's high byte in minimum number of bits + let val_bytes = val.to_bytes_be(); + let high_bits_cnt = { + let cnt = val.bits() % 8; + if cnt == 0 { + 8 + } else { + cnt + } + }; + let high_byte = val_bytes[0]; + for i in 0..high_bits_cnt { + self.store_bit(high_byte & (1 << (high_bits_cnt - i - 1)) != 0)?; } - for b in bytes { - self.store_byte(b)?; + // store the rest of val + for byte in val_bytes.iter().skip(1) { + self.store_byte(*byte)?; } Ok(self) } pub fn store_int(&mut self, bit_len: usize, val: &BigInt) -> Result<&mut Self, TonCellError> { - if val.bits() as usize > bit_len { - return Err(TonCellError::cell_builder_error(format!( - "Value {} doesn't fit in {} bits", - val, bit_len - ))); - } - let bytes = val.to_signed_bytes_be(); - let num_full_bytes = bit_len / 8; - let num_bits_in_high_byte = bit_len % 8; - if bytes.len() > num_full_bytes + 1 { + let (sign, mag) = val.clone().into_parts(); + let bit_len = bit_len - 1; // reserve 1 bit for sign + if bit_len < mag.bits() as usize { return Err(TonCellError::cell_builder_error(format!( - "Internal error: can't fit {} into {} bits ", - val, bit_len + "Value {} doesn't fit in {} bits (takes {} bits)", + val, + bit_len, + mag.bits() ))); } - if num_bits_in_high_byte > 0 { - let high_byte: u8 = if bytes.len() == num_full_bytes + 1 { - bytes[0] - } else { - 0 - }; - self.store_u8(num_bits_in_high_byte, high_byte)?; - } - let num_empty_bytes = num_full_bytes - bytes.len(); - for _ in 0..num_empty_bytes { + if sign == Sign::Minus { + self.store_byte(1)?; + self.store_uint(bit_len, &extend_and_invert_bits(bit_len, &mag)?)?; + } else { self.store_byte(0)?; - } - for b in bytes { - self.store_byte(b)?; - } + self.store_uint(bit_len, &mag)?; + }; Ok(self) } @@ -285,6 +282,31 @@ impl CellBuilder { } } +fn extend_and_invert_bits(bits_cnt: usize, src: &BigUint) -> Result { + if bits_cnt < src.bits() as usize { + return Err(TonCellError::cell_builder_error(format!( + "Can't invert bits: value {} doesn't fit in {} bits", + src, bits_cnt + ))); + } + + let src_bytes = src.to_bytes_be(); + let inverted_bytes_cnt = (bits_cnt + 7) / 8; + let mut inverted = vec![0xffu8; inverted_bytes_cnt]; + // can be optimized + for (pos, byte) in src_bytes.iter().rev().enumerate() { + let inverted_pos = inverted.len() - 1 - pos; + inverted[inverted_pos] = inverted[inverted_pos] ^ byte; + if inverted_pos == 0 {} + } + let mut inverted_val_bytes = BigUint::from_bytes_be(&inverted) + .add(BigUint::one()) + .to_bytes_be(); + let leading_zeros = inverted_bytes_cnt * 8 - bits_cnt; + inverted_val_bytes[0] = inverted_val_bytes[0] & (0xffu8 >> leading_zeros); + Ok(BigUint::from_bytes_be(&inverted_val_bytes)) +} + impl Default for CellBuilder { fn default() -> Self { Self::new() @@ -294,7 +316,35 @@ impl Default for CellBuilder { #[cfg(test)] mod tests { use crate::address::TonAddress; + use crate::cell::builder::extend_and_invert_bits; use crate::cell::CellBuilder; + use num_bigint::{BigInt, BigUint, Sign}; + use std::str::FromStr; + use tokio_test::{assert_err, assert_ok}; + + #[test] + fn test_extend_and_invert_bits() -> anyhow::Result<()> { + let a = BigUint::from(1u8); + let b = extend_and_invert_bits(8, &a)?; + println!("a: {:0x}", a); + println!("b: {:0x}", b); + assert_eq!(b, BigUint::from(0xffu8)); + + let b = extend_and_invert_bits(16, &a)?; + assert_eq!(b, BigUint::from_slice(&[0xffffu32])); + + let b = extend_and_invert_bits(20, &a)?; + assert_eq!(b, BigUint::from_slice(&[0xfffffu32])); + + let b = extend_and_invert_bits(8, &a)?; + assert_eq!(b, BigUint::from_slice(&[0xffu32])); + + let b = extend_and_invert_bits(9, &a)?; + assert_eq!(b, BigUint::from_slice(&[0x1ffu32])); + + assert_err!(extend_and_invert_bits(3, &BigUint::from(10u32))); + Ok(()) + } #[test] fn write_bit() -> anyhow::Result<()> { @@ -396,4 +446,75 @@ mod tests { assert_eq!(result, addr); Ok(()) } + + #[test] + fn write_write_int() -> anyhow::Result<()> { + let value = BigInt::from_str("3")?; + let mut writer = CellBuilder::new(); + assert_ok!(writer.store_int(33, &value)); + let cell = writer.build()?; + println!("cell: {:?}", cell); + let written = BigInt::from_bytes_be(Sign::Plus, &cell.data); + assert_eq!(written, value); + + // 256 bits (+ sign) + let value = BigInt::from_str( + "97887266651548624282413032824435501549503168134499591480902563623927645013201", + )?; + let mut writer = CellBuilder::new(); + assert_ok!(writer.store_int(257, &value)); + let cell = writer.build()?; + println!("cell: {:?}", cell); + let written = BigInt::from_bytes_be(Sign::Plus, &cell.data); + assert_eq!(written, value); + + let value = BigInt::from_str("-5")?; + let mut writer = CellBuilder::new(); + assert_ok!(writer.store_int(5, &value)); + let cell = writer.build()?; + println!("cell: {:?}", cell); + let written = BigInt::from_bytes_be(Sign::Plus, &cell.data[1..]); + let expected = BigInt::from_bytes_be(Sign::Plus, &[0xB0u8]); + assert_eq!(written, expected); + Ok(()) + } + + #[test] + fn write_load_uint() -> anyhow::Result<()> { + let value = BigUint::from_str("3")?; + let mut writer = CellBuilder::new(); + assert!(writer.store_uint(1, &value).is_err()); + let bits_for_tests = vec![256, 128, 64, 8]; + + for bits_num in bits_for_tests.iter() { + assert_ok!(writer.store_uint(*bits_num, &value)); + } + let cell = writer.build()?; + println!("cell: {:?}", cell); + let mut cell_parser = cell.parser(); + for bits_num in bits_for_tests.iter() { + let written_value = assert_ok!(cell_parser.load_uint(*bits_num)); + assert_eq!(written_value, value); + } + + // 256 bit + let value = BigUint::from_str( + "97887266651548624282413032824435501549503168134499591480902563623927645013201", + )?; + let mut writer = CellBuilder::new(); + assert!(writer.store_uint(255, &value).is_err()); + let bits_for_tests = vec![496, 264, 256]; + for bits_num in bits_for_tests.iter() { + assert_ok!(writer.store_uint(*bits_num, &value)); + } + let cell = writer.build()?; + let mut cell_parser = cell.parser(); + println!("cell: {:?}", cell); + for bits_num in bits_for_tests.iter() { + let written_value = assert_ok!(cell_parser.load_uint(*bits_num)); + assert_eq!(written_value, value); + } + + Ok(()) + } } From 12df404d226d45e7343088e48ee4ac9646b3e1d8 Mon Sep 17 00:00:00 2001 From: Dmitrii Korchagin Date: Tue, 11 Jun 2024 12:00:45 +0200 Subject: [PATCH 04/29] Impl #144: fix cargo clippy --- src/cell/builder.rs | 9 ++++----- src/message/jetton.rs | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/cell/builder.rs b/src/cell/builder.rs index 730ab0bf..b7e2a61d 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -296,14 +296,13 @@ fn extend_and_invert_bits(bits_cnt: usize, src: &BigUint) -> Result> leading_zeros); + inverted_val_bytes[0] &= 0xffu8 >> leading_zeros; Ok(BigUint::from_bytes_be(&inverted_val_bytes)) } @@ -448,7 +447,7 @@ mod tests { } #[test] - fn write_write_int() -> anyhow::Result<()> { + fn write_big_int() -> anyhow::Result<()> { let value = BigInt::from_str("3")?; let mut writer = CellBuilder::new(); assert_ok!(writer.store_int(33, &value)); @@ -480,7 +479,7 @@ mod tests { } #[test] - fn write_load_uint() -> anyhow::Result<()> { + fn write_load_big_uint() -> anyhow::Result<()> { let value = BigUint::from_str("3")?; let mut writer = CellBuilder::new(); assert!(writer.store_uint(1, &value).is_err()); diff --git a/src/message/jetton.rs b/src/message/jetton.rs index d21f7ffe..7207f617 100644 --- a/src/message/jetton.rs +++ b/src/message/jetton.rs @@ -87,7 +87,7 @@ impl JettonTransferMessage { forward_ton_amount: &BigUint, forward_payload: &ArcCell, ) -> &mut Self { - self.forward_ton_amount = forward_ton_amount.clone(); + self.forward_ton_amount.clone_from(forward_ton_amount); self.forward_payload = Some(forward_payload.clone()); self } From 03e07b740087c42fb7c9610b51cf866d7bfb561b Mon Sep 17 00:00:00 2001 From: Dmitrii Korchagin Date: Thu, 13 Jun 2024 10:02:53 +0000 Subject: [PATCH 05/29] fix comments --- src/cell/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cell/builder.rs b/src/cell/builder.rs index b7e2a61d..9e62f83d 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -83,7 +83,7 @@ impl CellBuilder { ))); } // example: bit_len=13, val=5. 5 = 00000101, we must store 0000000000101 - // leading_zeros_bytes = 10 + // leading_zeros_bits = 10 // leading_zeros_bytes = 10 / 8 = 1 let leading_zero_bits = bit_len - val.bits() as usize; let leading_zeros_bytes = leading_zero_bits / 8; From fbd556e3156559d4ff8dbdd47ea63abd55a69ada Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Wed, 26 Jun 2024 08:58:10 +0000 Subject: [PATCH 06/29] NI: bump rust-build container --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2b7768ba..e1b653fc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: ${CI_REGISTRY}/ston-fi/docker/rust-build:20.10.24_1.76.0-5641dcc8 +image: ${CI_REGISTRY}/ston-fi/docker/rust-build:20.10.24_1.79.0-bb606509 # Prevent duplicate pipelines, branch pipeline and merge_request pipeline workflow: From d29d3fc20306c377caef9648ca14d50c61f26c6c Mon Sep 17 00:00:00 2001 From: Dmitrii Korchagin Date: Wed, 19 Jun 2024 12:23:31 +0200 Subject: [PATCH 07/29] Impl #NI: add trace logs around TvmEmulatorUnsafe methods --- src/emulator/unsafe_emulator.rs | 35 +++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/emulator/unsafe_emulator.rs b/src/emulator/unsafe_emulator.rs index 413fbdad..35f71b08 100644 --- a/src/emulator/unsafe_emulator.rs +++ b/src/emulator/unsafe_emulator.rs @@ -25,6 +25,7 @@ impl TvmEmulatorUnsafe { data: &[u8], vm_log_verbosity: u32, ) -> Result { + log::trace!("tvm_emulator_unsafe: creating..."); let code = CString::new(STANDARD.encode(code))?; let data = CString::new(STANDARD.encode(data))?; @@ -33,8 +34,10 @@ impl TvmEmulatorUnsafe { TvmEmulatorUnsafe { ptr } }; if emulator.ptr.is_null() { + log::trace!("tvm_emulator_unsafe: creating failed"); Err(TvmEmulatorError::CreationFailed()) } else { + log::trace!("tvm_emulator_unsafe: created"); Ok(emulator) } } @@ -44,12 +47,21 @@ impl TvmEmulatorUnsafe { method_id: i32, stack_boc: &[u8], ) -> Result { + log::trace!( + "run_get_method_req: method_id: {}, stack_boc: {:?}", + method_id, + stack_boc + ); let data: CString = CString::new(STANDARD.encode(stack_boc))?; let c_str = unsafe { tvm_emulator_run_get_method(self.ptr, method_id, data.as_ptr()) }; let json_str: &str = unsafe { std::ffi::CStr::from_ptr(c_str).to_str()? }; - log::trace!("response {}", json_str); - + log::trace!( + "run_get_method_rsp: method_id: {}, stack_boc: {:?}, rsp: {}", + method_id, + stack_boc, + json_str + ); Ok(json_str.to_string()) } @@ -58,21 +70,36 @@ impl TvmEmulatorUnsafe { message: &[u8], amount: u64, ) -> Result { + log::trace!( + "send_internal_message_req: msg: {:?}, amount: {}", + message, + amount + ); let message_encoded = CString::new(STANDARD.encode(message))?; let c_str = unsafe { tvm_emulator_send_internal_message(self.ptr, message_encoded.into_raw(), amount) }; let json_str = unsafe { std::ffi::CStr::from_ptr(c_str).to_str() }?; - log::trace!("response {}", json_str); + log::trace!( + "send_internal_message_rsp: msg: {:?}, amount: {}, rsp: {}", + message, + amount, + json_str + ); Ok(json_str.to_string()) } pub fn send_external_message(&mut self, message: &[u8]) -> Result { + log::trace!("send_internal_message_req: msg: {:?}", message); let message_encoded = CString::new(STANDARD.encode(message))?; let c_str = unsafe { tvm_emulator_send_external_message(self.ptr, message_encoded.into_raw()) }; let json_str = unsafe { std::ffi::CStr::from_ptr(c_str).to_str() }?; - log::trace!("response {}", json_str); + log::trace!( + "send_external_message_rsp: msg: {:?}, rsp: {}", + message, + json_str + ); Ok(json_str.to_string()) } From d707be7771579e162aa7586bdbfc8b137e071126 Mon Sep 17 00:00:00 2001 From: Aleksei Dolgii Date: Wed, 26 Jun 2024 12:40:14 +0000 Subject: [PATCH 08/29] Impl #142: Added timestamp limit to LatestContactTransactionCache --- src/contract/latest_transactions_cache.rs | 28 ++++++++++- tests/transactions_test.rs | 60 ++++++++++++++++++++--- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/contract/latest_transactions_cache.rs b/src/contract/latest_transactions_cache.rs index bb67269c..358a3d87 100644 --- a/src/contract/latest_transactions_cache.rs +++ b/src/contract/latest_transactions_cache.rs @@ -1,6 +1,7 @@ use std::collections::LinkedList; +use std::ops::Sub; use std::sync::Arc; - +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::sync::Mutex; use crate::address::TonAddress; @@ -14,6 +15,7 @@ pub struct LatestContractTransactionsCache { address: TonAddress, soft_limit: bool, + tx_age_limit: Option, inner: Mutex, } @@ -23,6 +25,7 @@ impl LatestContractTransactionsCache { address: &TonAddress, capacity: usize, soft_limit: bool, + tx_age_limit: Option, ) -> LatestContractTransactionsCache { let inner = Mutex::new(Inner { transactions: LinkedList::new(), @@ -33,6 +36,7 @@ impl LatestContractTransactionsCache { address: address.clone(), soft_limit, + tx_age_limit, inner, } } @@ -58,6 +62,7 @@ impl LatestContractTransactionsCache { self.soft_limit, self.capacity, &target_sync_tx_id, + self.tx_age_limit, ) .await?; } @@ -91,6 +96,7 @@ impl Inner { soft_limit: bool, capacity: usize, target_sync_tx: &InternalTransactionId, + tx_age_limit: Option, ) -> Result<(), TonContractError> { let synced_tx_id = self.get_latest_synced_tx_id(); let mut loaded = Vec::new(); @@ -107,6 +113,11 @@ impl Inner { return Ok(()); } + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_e| TonContractError::InternalError("Time went backwards!".to_string()))?; + let min_utime = tx_age_limit.map(|duration| current_time - duration); + while !finished && next_to_load.lt != 0 && next_to_load.lt > synced_tx_id.lt { let maybe_txs = contract_factory .clone() @@ -135,6 +146,14 @@ impl Inner { if loaded.len() >= capacity || tx.transaction_id.lt <= synced_tx_id.lt { finished = true; break; + } else if Inner::is_older_than(tx.utime, min_utime) { + finished = true; + log::trace!( + "Minimum loaded timestamp limit reached {:?} for transaction id {:?}", + tx_age_limit.map(|limit| current_time.sub(limit)), + tx.transaction_id.lt + ); + break; } loaded.push(Arc::new(tx)); } @@ -169,6 +188,13 @@ impl Inner { Ok(()) } + fn is_older_than(utime: i64, min_utime: Option) -> bool { + if let Some(min) = min_utime { + return Duration::from_secs(utime as u64) < min; + } + false + } + fn fill_txs(&self, limit: usize) -> Vec> { let mut res = Vec::with_capacity(limit); let txs = &self.transactions; diff --git a/tests/transactions_test.rs b/tests/transactions_test.rs index 9e4d09b9..c103eac2 100644 --- a/tests/transactions_test.rs +++ b/tests/transactions_test.rs @@ -1,10 +1,10 @@ +use anyhow::anyhow; +use futures::future::join_all; +use std::ops::Sub; use std::str::FromStr; use std::sync::Arc; -use std::time::Instant; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use std::{thread, time}; - -use anyhow::anyhow; -use futures::future::join_all; use tokio_test::assert_ok; use tonlib::address::TonAddress; use tonlib::contract::{LatestContractTransactionsCache, TonContractFactory}; @@ -20,7 +20,7 @@ async fn get_txs_for_frequent_works() { let client = common::new_mainnet_client().await; let factory = assert_ok!(TonContractFactory::builder(&client).build().await); - let trans = LatestContractTransactionsCache::new(&factory, validator, 100, true); + let trans = LatestContractTransactionsCache::new(&factory, validator, 100, true, None); let trs = assert_ok!(trans.get(4).await); log::info!( "Got {} transactions, first {}, last {}", @@ -64,7 +64,7 @@ async fn get_txs_for_rare_works() { let client = common::new_archive_mainnet_client().await; let factory = assert_ok!(TonContractFactory::builder(&client).build().await); - let trans = LatestContractTransactionsCache::new(&factory, addr, 100, true); + let trans = LatestContractTransactionsCache::new(&factory, addr, 100, true, None); let trs = assert_ok!(trans.get(4).await); if trs.is_empty() { @@ -97,7 +97,8 @@ async fn get_txs_for_rare_works() { let mut missing_hash = addr.hash_part; missing_hash[31] += 1; let missing_addr = TonAddress::new(addr.workchain, &missing_hash); - let missing_trans = LatestContractTransactionsCache::new(&factory, &missing_addr, 100, true); + let missing_trans = + LatestContractTransactionsCache::new(&factory, &missing_addr, 100, true, None); let missing_trs = assert_ok!(missing_trans.get(30).await); assert_eq!(missing_trs.len(), 0); @@ -110,7 +111,7 @@ async fn get_txs_for_empty_works() { let client = common::new_mainnet_client().await; let factory = assert_ok!(TonContractFactory::builder(&client).build().await); - let trans = LatestContractTransactionsCache::new(&factory, addr, 100, true); + let trans = LatestContractTransactionsCache::new(&factory, addr, 100, true, None); let trs = assert_ok!(trans.get(4).await); log::info!( "Got {} transactions, first {:?}, last {:?}", @@ -159,6 +160,7 @@ async fn latest_tx_data_cache_test() -> anyhow::Result<()> { &contract_address, capacity, soft_limit, + None, ); log::info!("Created cache"); for _ in 0..10 { @@ -186,3 +188,45 @@ async fn latest_tx_data_cache_test() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test] +async fn timestamp_limit_test() -> anyhow::Result<()> { + const ADDRESS: &str = "EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt"; + const TIMESTAMP_LIMIT_SEC: u64 = 60; + let time_limit = Duration::from_secs(TIMESTAMP_LIMIT_SEC); + + common::init_logging(); + let addr: &TonAddress = &assert_ok!(ADDRESS.parse()); + + let client = common::new_mainnet_client().await; + let factory = assert_ok!(TonContractFactory::builder(&client).build().await); + + let cache = LatestContractTransactionsCache::new( + &factory, + &addr, + 500, + true, + Some(Duration::from_secs(TIMESTAMP_LIMIT_SEC)), + ); + + let transactions = assert_ok!(cache.get(500).await); + if transactions.len() > 0 { + let last = transactions.last().unwrap(); + log::info!( + "Got {} transactions, first {}, last {}", + transactions.len(), + transactions.first().unwrap().transaction_id.lt, + last.transaction_id.lt, + ); + + let expected_min_utime = SystemTime::now() + .sub(time_limit) + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64; + + assert!(last.utime > expected_min_utime); + } + + Ok(()) +} From ca4ad69055f9c83bb98d65b3e9f8bd6bb192bbf1 Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Mon, 1 Jul 2024 17:51:12 +0000 Subject: [PATCH 09/29] NI: misc fixes after updating clippy to 1.79 --- src/cell/builder.rs | 12 +++++++----- src/contract/latest_transactions_cache.rs | 1 + tests/transactions_test.rs | 9 +++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/cell/builder.rs b/src/cell/builder.rs index 9e62f83d..9aaa3690 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -314,12 +314,14 @@ impl Default for CellBuilder { #[cfg(test)] mod tests { + use std::str::FromStr; + + use num_bigint::{BigInt, BigUint, Sign}; + use tokio_test::{assert_err, assert_ok}; + use crate::address::TonAddress; use crate::cell::builder::extend_and_invert_bits; use crate::cell::CellBuilder; - use num_bigint::{BigInt, BigUint, Sign}; - use std::str::FromStr; - use tokio_test::{assert_err, assert_ok}; #[test] fn test_extend_and_invert_bits() -> anyhow::Result<()> { @@ -483,7 +485,7 @@ mod tests { let value = BigUint::from_str("3")?; let mut writer = CellBuilder::new(); assert!(writer.store_uint(1, &value).is_err()); - let bits_for_tests = vec![256, 128, 64, 8]; + let bits_for_tests = [256, 128, 64, 8]; for bits_num in bits_for_tests.iter() { assert_ok!(writer.store_uint(*bits_num, &value)); @@ -502,7 +504,7 @@ mod tests { )?; let mut writer = CellBuilder::new(); assert!(writer.store_uint(255, &value).is_err()); - let bits_for_tests = vec![496, 264, 256]; + let bits_for_tests = [496, 264, 256]; for bits_num in bits_for_tests.iter() { assert_ok!(writer.store_uint(*bits_num, &value)); } diff --git a/src/contract/latest_transactions_cache.rs b/src/contract/latest_transactions_cache.rs index 358a3d87..e031b8ab 100644 --- a/src/contract/latest_transactions_cache.rs +++ b/src/contract/latest_transactions_cache.rs @@ -2,6 +2,7 @@ use std::collections::LinkedList; use std::ops::Sub; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; + use tokio::sync::Mutex; use crate::address::TonAddress; diff --git a/tests/transactions_test.rs b/tests/transactions_test.rs index c103eac2..a8811acd 100644 --- a/tests/transactions_test.rs +++ b/tests/transactions_test.rs @@ -1,10 +1,11 @@ -use anyhow::anyhow; -use futures::future::join_all; use std::ops::Sub; use std::str::FromStr; use std::sync::Arc; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use std::{thread, time}; + +use anyhow::anyhow; +use futures::future::join_all; use tokio_test::assert_ok; use tonlib::address::TonAddress; use tonlib::contract::{LatestContractTransactionsCache, TonContractFactory}; @@ -203,14 +204,14 @@ async fn timestamp_limit_test() -> anyhow::Result<()> { let cache = LatestContractTransactionsCache::new( &factory, - &addr, + addr, 500, true, Some(Duration::from_secs(TIMESTAMP_LIMIT_SEC)), ); let transactions = assert_ok!(cache.get(500).await); - if transactions.len() > 0 { + if !transactions.is_empty() { let last = transactions.last().unwrap(); log::info!( "Got {} transactions, first {}, last {}", From 6ed55d583e6f7b91d05c4c6837e68e11fc2aff3b Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Tue, 2 Jul 2024 10:20:18 +0000 Subject: [PATCH 10/29] Impl #147: parsers and some refactoring of jettons --- Cargo.toml | 2 +- src/cell.rs | 12 + src/message.rs | 2 + src/message/error.rs | 22 ++ src/message/jetton.rs | 115 +-------- src/message/jetton/burn.rs | 105 ++++++++ src/message/jetton/jetton_transfer.rs | 252 ++++++++++++++++++++ src/message/jetton/transfer_notification.rs | 120 ++++++++++ src/message/util.rs | 22 ++ 9 files changed, 543 insertions(+), 109 deletions(-) create mode 100644 src/message/jetton/burn.rs create mode 100644 src/message/jetton/jetton_transfer.rs create mode 100644 src/message/jetton/transfer_notification.rs create mode 100644 src/message/util.rs diff --git a/Cargo.toml b/Cargo.toml index d468238b..e47ccdf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.15.5-dev" +version = "0.16.0-dev" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" diff --git a/src/cell.rs b/src/cell.rs index 29e52b38..7d36b244 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -342,6 +342,18 @@ impl Cell { pub fn to_arc(self) -> ArcCell { Arc::new(self) } + + pub fn expect_reference_count(&self, expected_refs: usize) -> Result<(), TonCellError> { + let ref_count = self.references.len(); + if ref_count != expected_refs { + Err(TonCellError::CellParserError(format!( + "Cell should contain {} reference cells, actual: {}", + expected_refs, ref_count + ))) + } else { + Ok(()) + } + } } impl Debug for Cell { diff --git a/src/message.rs b/src/message.rs index 4e07c198..d9481a3d 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,10 +1,12 @@ pub use error::*; pub use jetton::*; pub use transfer::*; +pub use util::*; mod error; mod jetton; mod transfer; +mod util; use lazy_static::lazy_static; use num_bigint::BigUint; diff --git a/src/message/error.rs b/src/message/error.rs index ee7ff143..7a3b9063 100644 --- a/src/message/error.rs +++ b/src/message/error.rs @@ -1,3 +1,5 @@ +use core::fmt; + use thiserror::Error; use crate::cell::TonCellError; @@ -12,4 +14,24 @@ pub enum TonMessageError { #[error("TonCellError ({0})")] TonCellError(#[from] TonCellError), + + #[error("Invalid message ({0})")] + InvalidMessage(InvalidMessage), +} + +#[derive(Debug)] +pub struct InvalidMessage { + pub opcode: Option, + pub query_id: Option, + pub message: String, +} + +impl fmt::Display for InvalidMessage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "InvalidMessage {{ opcode: {:?}, query_id: {:?}, message: {} }}", + self.opcode, self.query_id, self.message + ) + } } diff --git a/src/message/jetton.rs b/src/message/jetton.rs index 7207f617..c4294526 100644 --- a/src/message/jetton.rs +++ b/src/message/jetton.rs @@ -1,12 +1,3 @@ -use std::sync::Arc; - -use num_bigint::BigUint; -use num_traits::Zero; - -use crate::address::TonAddress; -use crate::cell::{ArcCell, Cell, CellBuilder}; -use crate::message::{TonMessageError, ZERO_COINS}; - // Constants from jetton standart // https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md @@ -17,109 +8,17 @@ use crate::message::{TonMessageError, ZERO_COINS}; // crc32('internal_transfer query_id:uint64 amount:VarUInteger 16 from:MsgAddress response_address:MsgAddress forward_ton_amount:VarUInteger 16 forward_payload:Either Cell ^Cell = InternalMsgBody') = 0x978d4519 & 0x7fffffff = 0x178d4519 // crc32('burn_notification query_id:uint64 amount:VarUInteger 16 sender:MsgAddress response_destination:MsgAddress = InternalMsgBody') = 0x7bdd97de & 0x7fffffff = 0x7bdd97de -pub const JETTON_TRANSFER: u32 = 0xf8a7ea5; +pub const JETTON_TRANSFER: u32 = 0x0f8a7ea5; pub const JETTON_TRANSFER_NOTIFICATION: u32 = 0x7362d09c; pub const JETTON_INTERNAL_TRANSFER: u32 = 0x178d4519; pub const JETTON_EXCESSES: u32 = 0xd53276db; pub const JETTON_BURN: u32 = 0x595f07bc; pub const JETTON_BURN_NOTIFICATION: u32 = 0x7bdd97de; -/// Creates a body for jetton transfer according to TL-B schema: -/// -/// ```raw -/// transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress -/// response_destination:MsgAddress custom_payload:(Maybe ^Cell) -/// forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) -/// = InternalMsgBody; -/// ``` -pub struct JettonTransferMessage { - pub query_id: Option, - pub amount: BigUint, - pub destination: TonAddress, - pub response_destination: Option, - pub custom_payload: Option, - pub forward_ton_amount: BigUint, - pub forward_payload: Option, -} - -impl JettonTransferMessage { - pub fn new(destination: &TonAddress, amount: &BigUint) -> JettonTransferMessage { - JettonTransferMessage { - query_id: None, - amount: amount.clone(), - destination: destination.clone(), - response_destination: None, - custom_payload: None, - forward_ton_amount: ZERO_COINS.clone(), - forward_payload: None, - } - } - - pub fn with_query_id(&mut self, query_id: u64) -> &mut Self { - self.query_id = Some(query_id); - self - } - - pub fn with_response_destination(&mut self, response_destination: &TonAddress) -> &mut Self { - self.response_destination = Some(response_destination.clone()); - self - } - - pub fn with_custom_payload(&mut self, custom_payload: Cell) -> &mut Self { - self.with_custom_payload_ref(&Arc::new(custom_payload)) - } - - pub fn with_custom_payload_ref(&mut self, custom_payload_ref: &ArcCell) -> &mut Self { - self.custom_payload = Some(custom_payload_ref.clone()); - self - } - - pub fn with_forward( - &mut self, - forward_ton_amount: &BigUint, - forward_payload: Cell, - ) -> &mut Self { - self.with_forward_ref(forward_ton_amount, &Arc::new(forward_payload)) - } - - pub fn with_forward_ref( - &mut self, - forward_ton_amount: &BigUint, - forward_payload: &ArcCell, - ) -> &mut Self { - self.forward_ton_amount.clone_from(forward_ton_amount); - self.forward_payload = Some(forward_payload.clone()); - self - } - - pub fn build(&self) -> Result { - if self.forward_ton_amount.is_zero() && self.forward_payload.is_some() { - return Err(TonMessageError::ForwardTonAmountIsNegative); - } +mod burn; +mod jetton_transfer; +mod transfer_notification; - let mut message = CellBuilder::new(); - message.store_u32(32, JETTON_TRANSFER)?; - message.store_u64(64, self.query_id.unwrap_or_default())?; - message.store_coins(&self.amount)?; - message.store_address(&self.destination)?; - message.store_address( - self.response_destination - .as_ref() - .unwrap_or(&TonAddress::NULL), - )?; - if let Some(cp) = self.custom_payload.as_ref() { - message.store_bit(true)?; - message.store_reference(cp)?; - } else { - message.store_bit(false)?; - } - message.store_coins(&self.forward_ton_amount)?; - if let Some(fp) = self.forward_payload.as_ref() { - message.store_bit(true)?; - message.store_reference(fp)?; - } else { - message.store_bit(false)?; - } - Ok(message.build()?) - } -} +pub use burn::*; +pub use jetton_transfer::*; +pub use transfer_notification::*; diff --git a/src/message/jetton/burn.rs b/src/message/jetton/burn.rs new file mode 100644 index 00000000..bdba54cf --- /dev/null +++ b/src/message/jetton/burn.rs @@ -0,0 +1,105 @@ +use num_bigint::BigUint; + +use super::JETTON_BURN; +use crate::address::TonAddress; +use crate::cell::{ArcCell, Cell, CellBuilder}; +use crate::message::{InvalidMessage, RawMessageUtils, TonMessageError}; +use crate::tl::RawMessage; + +/// Creates a body for jetton burn according to TL-B schema: +/// +/// ```raw +/// burn#595f07bc query_id:uint64 amount:(VarUInteger 16) +/// response_destination:MsgAddress custom_payload:(Maybe ^Cell) +/// = InternalMsgBody; +/// ``` +pub struct JettonBurnMessage { + /// arbitrary request number. + pub query_id: u64, + /// amount of burned jettons + pub amount: BigUint, + /// address where to send a response with confirmation of a successful burn and the rest of the incoming message coins. + pub response_destination: TonAddress, + /// optional custom data (which is used by either sender or receiver jetton wallet for inner logic). + pub custom_payload: Option, +} + +impl JettonBurnMessage { + pub fn new(amount: &BigUint) -> Self { + JettonBurnMessage { + query_id: 0, + amount: amount.clone(), + response_destination: TonAddress::null(), + custom_payload: None, + } + } + + pub fn with_query_id(&mut self, query_id: u64) -> &mut Self { + self.query_id = query_id; + self + } + + pub fn with_response_destination(&mut self, response_destination: &TonAddress) -> &mut Self { + self.response_destination = response_destination.clone(); + self + } + + pub fn with_custom_payload(&mut self, custom_payload: T) -> &mut Self + where + T: AsRef, + { + self.custom_payload = Some(custom_payload.as_ref().clone()); + self + } + + pub fn build(&self) -> Result { + let mut message = CellBuilder::new(); + message.store_u32(32, JETTON_BURN)?; + message.store_u64(64, self.query_id)?; + message.store_coins(&self.amount)?; + message.store_address(&self.response_destination)?; + if let Some(cp) = self.custom_payload.as_ref() { + message.store_bit(true)?; + message.store_reference(cp)?; + } else { + message.store_bit(false)?; + } + Ok(message.build()?) + } + + pub fn parse(msg: &RawMessage) -> Result { + let cell = (&msg).get_raw_data_cell()?; + let mut parser = cell.parser(); + + let opcode: u32 = parser.load_u32(32)?; + let query_id = parser.load_u64(64)?; + if opcode != JETTON_BURN { + let invalid = InvalidMessage { + opcode: Some(opcode), + query_id: Some(query_id), + message: format!("Unexpected opcode. {0:08x} expected", JETTON_BURN), + }; + return Err(TonMessageError::InvalidMessage(invalid)); + } + let amount = parser.load_coins()?; + let response_destination = parser.load_address()?; + let has_custom_payload = parser.load_bit()?; + parser.ensure_empty()?; + + let custom_payload = if has_custom_payload { + cell.expect_reference_count(1)?; + Some(cell.reference(0)?.clone()) + } else { + cell.expect_reference_count(0)?; + None + }; + + let result = JettonBurnMessage { + query_id, + amount, + response_destination, + custom_payload, + }; + Ok(result) + } +} diff --git a/src/message/jetton/jetton_transfer.rs b/src/message/jetton/jetton_transfer.rs new file mode 100644 index 00000000..7bc5e545 --- /dev/null +++ b/src/message/jetton/jetton_transfer.rs @@ -0,0 +1,252 @@ +use num_bigint::BigUint; +use num_traits::Zero; + +use super::JETTON_TRANSFER; +use crate::address::TonAddress; +use crate::cell::{ArcCell, Cell, CellBuilder}; +use crate::message::{InvalidMessage, RawMessageUtils, TonMessageError, ZERO_COINS}; +use crate::tl::RawMessage; + +/// Creates a body for jetton transfer according to TL-B schema: +/// +/// ```raw +/// transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress +/// response_destination:MsgAddress custom_payload:(Maybe ^Cell) +/// forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) +/// = InternalMsgBody; +/// ``` +#[derive(Debug, PartialEq)] +pub struct JettonTransferMessage { + /// arbitrary request number. + pub query_id: u64, + /// amount of transferred jettons in elementary units. + pub amount: BigUint, + /// address of the new owner of the jettons. + pub destination: TonAddress, + /// address where to send a response with confirmation of a successful transfer and the rest of the incoming message Toncoins. + pub response_destination: TonAddress, + /// optional custom data (which is used by either sender or receiver jetton wallet for inner logic). + pub custom_payload: Option, + /// the amount of nanotons to be sent to the destination address. + pub forward_ton_amount: BigUint, + /// optional custom data that should be sent to the destination address. + pub forward_payload: Option, +} + +impl JettonTransferMessage { + pub fn new(destination: &TonAddress, amount: &BigUint) -> Self { + JettonTransferMessage { + query_id: 0, + amount: amount.clone(), + destination: destination.clone(), + response_destination: TonAddress::null(), + custom_payload: None, + forward_ton_amount: ZERO_COINS.clone(), + forward_payload: None, + } + } + + pub fn with_query_id(&mut self, query_id: u64) -> &mut Self { + self.query_id = query_id; + self + } + + pub fn with_response_destination(&mut self, response_destination: &TonAddress) -> &mut Self { + self.response_destination = response_destination.clone(); + self + } + + pub fn with_custom_payload(&mut self, custom_payload: T) -> &mut Self + where + T: AsRef, + { + self.custom_payload = Some(custom_payload.as_ref().clone()); + self + } + + pub fn with_forward_payload( + &mut self, + forward_ton_amount: &BigUint, + forward_payload: T, + ) -> &mut Self + where + T: AsRef, + { + self.forward_ton_amount.clone_from(forward_ton_amount); + self.forward_payload = Some(forward_payload.as_ref().clone()); + self + } + + pub fn build(&self) -> Result { + if self.forward_ton_amount.is_zero() && self.forward_payload.is_some() { + return Err(TonMessageError::ForwardTonAmountIsNegative); + } + + let mut message = CellBuilder::new(); + message.store_u32(32, JETTON_TRANSFER)?; + message.store_u64(64, self.query_id)?; + message.store_coins(&self.amount)?; + message.store_address(&self.destination)?; + message.store_address(&self.response_destination)?; + if let Some(cp) = self.custom_payload.as_ref() { + message.store_bit(true)?; + message.store_reference(cp)?; + } else { + message.store_bit(false)?; + } + message.store_coins(&self.forward_ton_amount)?; + if let Some(fp) = self.forward_payload.as_ref() { + message.store_bit(true)?; + message.store_reference(fp)?; + } else { + message.store_bit(false)?; + } + Ok(message.build()?) + } + + pub fn parse(msg: &RawMessage) -> Result { + let cell = (&msg).get_raw_data_cell()?; + let mut parser = cell.parser(); + + let opcode: u32 = parser.load_u32(32)?; + let query_id = parser.load_u64(64)?; + if opcode != JETTON_TRANSFER { + let invalid = InvalidMessage { + opcode: Some(opcode), + query_id: Some(query_id), + message: format!("Unexpected opcode. {0:08x} expected", JETTON_TRANSFER), + }; + return Err(TonMessageError::InvalidMessage(invalid)); + } + let amount = parser.load_coins()?; + let destination = parser.load_address()?; + let response_destination = parser.load_address()?; + let has_custom_payload = parser.load_bit()?; + let forward_ton_amount = parser.load_coins()?; + let has_forward_payload = parser.load_bit()?; + parser.ensure_empty()?; + + let (custom_payload, forward_payload) = match (has_custom_payload, has_forward_payload) { + (true, true) => { + cell.expect_reference_count(2)?; + ( + Some(cell.reference(0)?.clone()), + Some(cell.reference(1)?.clone()), + ) + } + (true, false) => { + cell.expect_reference_count(1)?; + (Some(cell.reference(0)?.clone()), None) + } + (false, true) => { + cell.expect_reference_count(1)?; + (None, Some(cell.reference(0)?.clone())) + } + (false, false) => { + cell.expect_reference_count(0)?; + (None, None) + } + }; + + let result = JettonTransferMessage { + query_id, + amount, + destination, + response_destination, + custom_payload, + forward_ton_amount, + forward_payload, + }; + + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use std::sync::Arc; + + use num_bigint::BigUint; + use tokio_test::assert_ok; + + use crate::address::TonAddress; + use crate::cell::{BagOfCells, Cell}; + use crate::message::JettonTransferMessage; + use crate::tl::{AccountAddress, MsgData, RawMessage}; + // message origin: https://tonviewer.com/transaction/2e250e3c9367d8092f15e09fb3c3d750749187c2a528a616bf0e88e5f36ca3f4 + const JETTON_TRANSFER_MSG : &str="b5ee9c720101020100a800016d0f8a7ea5001f5512dab844d643b9aca00800ef3b9902a271b2a01c8938a523cfe24e71847aaeb6a620001ed44a77ac0e709c1033428f030100d7259385618009dd924373a9aad41b28cec02da9384d67363af2034fc2a7ccc067e28d4110de86e66deb002365dfa32dfd419308ebdf35e0f6ba7c42534bbb5dab5e89e28ea3e0455cc2d2f00257a672371a90e149b7d25864dbfd44827cc1e8a30df1b1e0c4338502ade2ad96"; + const TRANSFER_PAYLOAD: &str = "259385618009DD924373A9AAD41B28CEC02DA9384D67363AF2034FC2A7CCC067E28D4110DE86E66DEB002365DFA32DFD419308EBDF35E0F6BA7C42534BBB5DAB5E89E28EA3E0455CC2D2F00257A672371A90E149B7D25864DBFD44827CC1E8A30DF1B1E0C4338502ADE2AD94"; + + #[test] + fn test_jetton_transfer_parser() { + let msg_data = hex::decode(JETTON_TRANSFER_MSG).unwrap(); + + let raw_msg = RawMessage { + source: AccountAddress { + account_address: String::new(), + }, + destination: AccountAddress { + account_address: String::new(), + }, + value: 0, + fwd_fee: 0, + ihr_fee: 0, + created_lt: 0, + body_hash: vec![], + msg_data: MsgData::Raw { + body: msg_data.clone(), + init_state: vec![], + }, + }; + + let result_jetton_transfer_msg = assert_ok!(JettonTransferMessage::parse(&raw_msg)); + + let expected_jetton_transfer_msg = JettonTransferMessage { + query_id: 8819263745311958, + amount: BigUint::from(1000000000u64), + destination: TonAddress::from_str("EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt") + .unwrap(), + response_destination: TonAddress::from_str( + "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", + ) + .unwrap(), + custom_payload: None, + forward_ton_amount: BigUint::from(215000000u64), + forward_payload: Some(Arc::new(Cell { + data: hex::decode(TRANSFER_PAYLOAD).unwrap(), + bit_len: 862, + references: vec![], + })), + }; + + assert_eq!(expected_jetton_transfer_msg, result_jetton_transfer_msg); + } + #[test] + fn test_jetton_transfer_builder() { + let jetton_transfer_msg = JettonTransferMessage { + query_id: 8819263745311958, + amount: BigUint::from(1000000000u64), + destination: TonAddress::from_str("EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt") + .unwrap(), + response_destination: TonAddress::from_str( + "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", + ) + .unwrap(), + custom_payload: None, + forward_ton_amount: BigUint::from(215000000u64), + forward_payload: Some(Arc::new(Cell { + data: hex::decode(TRANSFER_PAYLOAD).unwrap(), + bit_len: 862, + references: vec![], + })), + }; + + let result_cell = assert_ok!(jetton_transfer_msg.build()); + + let result_boc_serialized = BagOfCells::from_root(result_cell).serialize(false).unwrap(); + let expected_boc_serialized = hex::decode(JETTON_TRANSFER_MSG).unwrap(); + + assert_eq!(expected_boc_serialized, result_boc_serialized) + } +} diff --git a/src/message/jetton/transfer_notification.rs b/src/message/jetton/transfer_notification.rs new file mode 100644 index 00000000..2786e1c7 --- /dev/null +++ b/src/message/jetton/transfer_notification.rs @@ -0,0 +1,120 @@ +use num_bigint::BigUint; +use num_traits::Zero; + +use super::{JETTON_TRANSFER, JETTON_TRANSFER_NOTIFICATION}; +use crate::address::TonAddress; +use crate::cell::{ArcCell, Cell, CellBuilder}; +use crate::message::{InvalidMessage, RawMessageUtils, TonMessageError, ZERO_COINS}; +use crate::tl::RawMessage; + +/// Creates a body for jetton transfer notification according to TL-B schema: +/// +/// ```raw +///transfer_notification#7362d09c query_id:uint64 amount:(VarUInteger 16) +/// sender:MsgAddress forward_payload:(Either Cell ^Cell) +/// = InternalMsgBody; +/// ``` +#[derive(Debug, PartialEq)] +pub struct JettonTransferNotificationMessage { + /// should be equal with request's query_id. + pub query_id: u64, + /// amount of transferred jettons. + pub amount: BigUint, + /// is address of the previous owner of transferred jettons. + pub sender: TonAddress, + /// the amount of nanotons to be sent to the destination address. + pub forward_ton_amount: BigUint, + /// optional custom data that should be sent to the destination address. + pub forward_payload: Option, +} + +impl JettonTransferNotificationMessage { + pub fn new(sender: &TonAddress, amount: &BigUint) -> Self { + JettonTransferNotificationMessage { + query_id: 0, + amount: amount.clone(), + sender: sender.clone(), + forward_ton_amount: ZERO_COINS.clone(), + forward_payload: None, + } + } + + pub fn with_query_id(&mut self, query_id: u64) -> &mut Self { + self.query_id = query_id; + self + } + + pub fn with_forward_payload( + &mut self, + forward_ton_amount: &BigUint, + forward_payload: T, + ) -> &mut Self + where + T: AsRef, + { + self.forward_ton_amount.clone_from(forward_ton_amount); + self.forward_payload = Some(forward_payload.as_ref().clone()); + self + } + + pub fn build(&self) -> Result { + if self.forward_ton_amount.is_zero() && self.forward_payload.is_some() { + return Err(TonMessageError::ForwardTonAmountIsNegative); + } + let mut message = CellBuilder::new(); + message.store_u32(32, JETTON_TRANSFER_NOTIFICATION)?; + message.store_u64(64, self.query_id)?; + message.store_coins(&self.amount)?; + message.store_address(&self.sender)?; + message.store_coins(&self.forward_ton_amount)?; + if let Some(fp) = self.forward_payload.as_ref() { + message.store_bit(true)?; + message.store_reference(fp)?; + } else { + message.store_bit(false)?; + } + Ok(message.build()?) + } + + pub fn parse(msg: &RawMessage) -> Result { + let cell = (&msg).get_raw_data_cell()?; + let mut parser = cell.parser(); + + let opcode: u32 = parser.load_u32(32)?; + let query_id = parser.load_u64(64)?; + if opcode != JETTON_TRANSFER { + let invalid = InvalidMessage { + opcode: Some(opcode), + query_id: Some(query_id), + message: format!( + "Unexpected opcode. {0:08x} expected", + JETTON_TRANSFER_NOTIFICATION + ), + }; + return Err(TonMessageError::InvalidMessage(invalid)); + } + let amount = parser.load_coins()?; + let sender = parser.load_address()?; + let forward_ton_amount = parser.load_coins()?; + let has_forward_payload = parser.load_bit()?; + parser.ensure_empty()?; + + let forward_payload = if has_forward_payload { + cell.expect_reference_count(1)?; + Some(cell.reference(0)?.clone()) + } else { + cell.expect_reference_count(0)?; + None + }; + + let result = JettonTransferNotificationMessage { + query_id, + amount, + sender, + forward_ton_amount, + forward_payload, + }; + + Ok(result) + } +} diff --git a/src/message/util.rs b/src/message/util.rs new file mode 100644 index 00000000..320dc183 --- /dev/null +++ b/src/message/util.rs @@ -0,0 +1,22 @@ +use crate::cell::{ArcCell, BagOfCells, TonCellError}; +use crate::tl::{MsgData, RawMessage}; + +pub trait RawMessageUtils { + fn get_raw_data_cell(&self) -> Result; + // fn is_bounced(&self) -> bool; +} + +impl RawMessageUtils for &RawMessage { + fn get_raw_data_cell(&self) -> Result { + let msg_data = match &self.msg_data { + MsgData::Raw { body, .. } => Ok(body.as_slice()), + _ => Err(TonCellError::CellParserError( + "Unsupported MsgData".to_string(), + )), + }?; + + let boc = BagOfCells::parse(msg_data)?; + let cell = boc.single_root()?.clone(); + Ok(cell) + } +} From 455230930fb698956e66d99e66d645ee4819a4a7 Mon Sep 17 00:00:00 2001 From: Andrey Vasiliev Date: Thu, 4 Jul 2024 12:31:20 +0000 Subject: [PATCH 11/29] Impl #149: exotic cells support --- src/cell.rs | 352 ++++++++++++++++++++++++-------- src/cell/bag_of_cells.rs | 149 ++++---------- src/cell/builder.rs | 16 +- src/cell/cell_type.rs | 377 +++++++++++++++++++++++++++++++++++ src/cell/error.rs | 6 + src/cell/level_mask.rs | 48 +++++ src/cell/raw.rs | 94 +++++---- src/cell/raw_boc_from_boc.rs | 151 ++++++++++++++ src/cell/slice.rs | 9 +- src/cell/state_init.rs | 6 +- src/types/tvm_stack_entry.rs | 12 +- src/wallet.rs | 2 +- tests/boc.rs | 131 ++++++++++++ 13 files changed, 1101 insertions(+), 252 deletions(-) create mode 100644 src/cell/cell_type.rs create mode 100644 src/cell/level_mask.rs create mode 100644 src/cell/raw_boc_from_boc.rs create mode 100644 tests/boc.rs diff --git a/src/cell.rs b/src/cell.rs index 7d36b244..55797644 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; -use std::fmt; use std::fmt::{Debug, Formatter}; use std::hash::Hash; use std::io::Cursor; use std::ops::Deref; use std::sync::Arc; +use std::{fmt, io}; +use crate::cell::cell_type::CellType; +use crate::cell::level_mask::LevelMask; pub use bag_of_cells::*; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; @@ -14,11 +16,12 @@ use bitstream_io::{BigEndian, BitReader, BitWrite, BitWriter}; pub use builder::*; pub use dict_loader::*; pub use error::*; +use hmac::digest::Digest; use num_bigint::BigUint; use num_traits::{One, ToPrimitive}; pub use parser::*; pub use raw::*; -use sha2::{Digest, Sha256}; +use sha2::Sha256; pub use slice::*; pub use state_init::*; pub use util::*; @@ -26,26 +29,68 @@ pub use util::*; mod bag_of_cells; mod bit_string; mod builder; +mod cell_type; mod dict_loader; mod error; +mod level_mask; mod parser; mod raw; +mod raw_boc_from_boc; mod slice; mod state_init; mod util; +const HASH_BYTES: usize = 32; +const DEPTH_BYTES: usize = 2; +const MAX_LEVEL: u8 = 3; + +pub type CellHash = [u8; HASH_BYTES]; pub type ArcCell = Arc; -pub type SnakeFormattedDict = HashMap<[u8; 32], Vec>; +pub type SnakeFormattedDict = HashMap>; #[derive(PartialEq, Eq, Clone, Hash)] pub struct Cell { - pub data: Vec, - pub bit_len: usize, - pub references: Vec, + data: Vec, + bit_len: usize, + references: Vec, + cell_type: CellType, + level_mask: LevelMask, + hashes: [CellHash; 4], + depths: [u16; 4], } impl Cell { + pub fn new( + data: Vec, + bit_len: usize, + references: Vec, + is_exotic: bool, + ) -> Result { + let cell_type = if is_exotic { + CellType::determine_exotic_cell_type(&data)? + } else { + CellType::Ordinary + }; + + cell_type.validate(&data, bit_len, &references)?; + let level_mask = cell_type.level_mask(&data, bit_len, &references)?; + let (hashes, depths) = + calculate_hashes_and_depths(cell_type, &data, bit_len, &references, level_mask)?; + + let result = Self { + data, + bit_len, + references, + level_mask, + cell_type, + hashes, + depths, + }; + + Ok(result) + } + pub fn parser(&self) -> CellParser { let bit_len = self.bit_len; let cursor = Cursor::new(&self.data); @@ -85,93 +130,44 @@ impl Cell { }) } - pub fn get_max_level(&self) -> u8 { - //TODO level calculation differ for exotic cells - let mut max_level = 0; - for k in &self.references { - let level = k.get_max_level(); - if level > max_level { - max_level = level; - } - } - max_level + pub fn data(&self) -> &[u8] { + self.data.as_slice() } - fn get_max_depth(&self) -> usize { - let mut max_depth = 0; - if !self.references.is_empty() { - for k in &self.references { - let depth = k.get_max_depth(); - if depth > max_depth { - max_depth = depth; - } - } - max_depth += 1; - } - max_depth + pub fn bit_len(&self) -> usize { + self.bit_len } - fn get_refs_descriptor(&self) -> u8 { - self.references.len() as u8 + self.get_max_level() * 32 + pub fn references(&self) -> &[ArcCell] { + self.references.as_slice() } - fn get_bits_descriptor(&self) -> u8 { - let rest_bits = self.bit_len % 8; - let full_bytes = rest_bits == 0; - self.data.len() as u8 * 2 - if full_bytes { 0 } else { 1 } //subtract 1 if the last byte is not full + pub(crate) fn get_level_mask(&self) -> u32 { + self.level_mask.mask() } - pub fn get_repr(&self) -> Result, TonCellError> { - let data_len = self.data.len(); - let rest_bits = self.bit_len % 8; - let full_bytes = rest_bits == 0; - let mut writer = BitWriter::endian(Vec::new(), BigEndian); - let val = self.get_refs_descriptor(); - writer.write(8, val).map_boc_serialization_error()?; - writer - .write(8, self.get_bits_descriptor()) - .map_boc_serialization_error()?; - if !full_bytes { - writer - .write_bytes(&self.data[..data_len - 1]) - .map_boc_serialization_error()?; - let last_byte = self.data[data_len - 1]; - let l = last_byte | 1 << (8 - rest_bits - 1); - writer.write(8, l).map_boc_serialization_error()?; - } else { - writer - .write_bytes(&self.data) - .map_boc_serialization_error()?; - } + pub fn cell_depth(&self) -> u16 { + self.get_depth(MAX_LEVEL) + } - for r in &self.references { - writer - .write(8, (r.get_max_depth() / 256) as u8) - .map_boc_serialization_error()?; - writer - .write(8, (r.get_max_depth() % 256) as u8) - .map_boc_serialization_error()?; - } - for r in &self.references { - writer - .write_bytes(&r.cell_hash()?) - .map_boc_serialization_error()?; - } - let result = writer - .writer() - .ok_or_else(|| TonCellError::cell_builder_error("Stream is not byte-aligned")) - .map(|b| b.to_vec()); - result + pub fn get_depth(&self, level: u8) -> u16 { + self.depths[level.min(3) as usize] + } + + pub fn cell_hash(&self) -> CellHash { + self.get_hash(MAX_LEVEL) + } + + pub fn get_hash(&self, level: u8) -> CellHash { + self.hashes[level.min(3) as usize] } - pub fn cell_hash(&self) -> Result, TonCellError> { - let mut hasher: Sha256 = Sha256::new(); - hasher.update(self.get_repr()?.as_slice()); - Ok(hasher.finalize()[..].to_vec()) + pub fn is_exotic(&self) -> bool { + self.cell_type != CellType::Ordinary } - pub fn cell_hash_base64(&self) -> Result { - Ok(URL_SAFE_NO_PAD.encode(self.cell_hash()?)) + pub fn cell_hash_base64(&self) -> String { + URL_SAFE_NO_PAD.encode(self.cell_hash()) } ///Snake format when we store part of the data in a cell and the rest of the data in the first child cell (and so recursively). @@ -219,7 +215,7 @@ impl Cell { } fn parse_snake_data(&self, buffer: &mut Vec) -> Result<(), TonCellError> { - let mut cell: &Cell = self; + let mut cell = self; let mut first_cell = true; loop { let mut parser = cell.parser(); @@ -358,9 +354,15 @@ impl Cell { impl Debug for Cell { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let t = match self.cell_type { + CellType::Ordinary | CellType::Library => 'x', + CellType::PrunedBranch | CellType::MerkleProof => 'p', + CellType::MerkleUpdate => 'u', + }; writeln!( f, - "Cell{{ data: [{}], bit_len: {}, references: [\n", + "Cell {}{{ data: [{}], bit_len: {}, references: [\n", + t, self.data .iter() .map(|&byte| format!("{:02X}", byte)) @@ -380,3 +382,189 @@ impl Debug for Cell { write!(f, "] }}") } } + +fn get_repr_for_data( + (original_data, original_data_bit_len): (&[u8], usize), + (data, data_bit_len): (&[u8], usize), + refs: &[ArcCell], + level_mask: LevelMask, + level: u8, + cell_type: CellType, +) -> Result, TonCellError> { + // Allocate + let data_len = data.len(); + // descriptors + data + (hash + depth) * refs_count + let buffer_len = 2 + data_len + (32 + 2) * refs.len(); + + let mut writer = BitWriter::endian(Vec::with_capacity(buffer_len), BigEndian); + let d1 = get_refs_descriptor(cell_type, refs, level_mask.apply(level).mask()); + let d2 = get_bits_descriptor(original_data, original_data_bit_len); + + // Write descriptors + writer.write(8, d1).map_cell_parser_error()?; + writer.write(8, d2).map_cell_parser_error()?; + // Write main data + write_data(&mut writer, data, data_bit_len).map_cell_parser_error()?; + // Write ref data + write_ref_depths(&mut writer, refs, cell_type, level)?; + write_ref_hashes(&mut writer, refs, cell_type, level)?; + + let result = writer + .writer() + .ok_or_else(|| TonCellError::cell_builder_error("Stream for cell repr is not byte-aligned")) + .map(|b| b.to_vec()); + + result +} + +/// This function replicates unknown logic of resolving cell data +/// https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/vm/cells/DataCell.cpp#L214 +fn calculate_hashes_and_depths( + cell_type: CellType, + data: &[u8], + bit_len: usize, + references: &[ArcCell], + level_mask: LevelMask, +) -> Result<([CellHash; 4], [u16; 4]), TonCellError> { + let hash_count = if cell_type == CellType::PrunedBranch { + 1 + } else { + level_mask.hash_count() + }; + + let total_hash_count = level_mask.hash_count(); + let hash_i_offset = total_hash_count - hash_count; + + let mut depths: Vec = Vec::with_capacity(hash_count); + let mut hashes: Vec = Vec::with_capacity(hash_count); + + // Iterate through significant levels + for (hash_i, level_i) in (0..=level_mask.level()) + .filter(|&i| level_mask.is_significant(i)) + .enumerate() + { + if hash_i < hash_i_offset { + continue; + } + + let (current_data, current_bit_len) = if hash_i == hash_i_offset { + (data, bit_len) + } else { + let previous_hash = hashes + .get(hash_i - hash_i_offset - 1) + .ok_or_else(|| TonCellError::InternalError("Can't get right hash".to_owned()))?; + (previous_hash.as_slice(), 256) + }; + + // Calculate Depth + let depth = if references.is_empty() { + 0 + } else { + let max_ref_depth = references.iter().fold(0, |max_depth, reference| { + let child_depth = cell_type.child_depth(reference, level_i); + max_depth.max(child_depth) + }); + + max_ref_depth + 1 + }; + + // Calculate Hash + let repr = get_repr_for_data( + (data, bit_len), + (current_data, current_bit_len), + references, + level_mask, + level_i, + cell_type, + )?; + let hash = Sha256::new_with_prefix(repr).finalize()[..] + .try_into() + .map_err(|error| { + TonCellError::InternalError(format!( + "Can't get [u8; 32] from finalized hash with error: {error}" + )) + })?; + + depths.push(depth); + hashes.push(hash); + } + + cell_type.resolve_hashes_and_depths(hashes, depths, data, bit_len, level_mask) +} + +fn get_refs_descriptor(cell_type: CellType, references: &[ArcCell], level_mask: u32) -> u8 { + let cell_type_var = (cell_type != CellType::Ordinary) as u8; + references.len() as u8 + 8 * cell_type_var + level_mask as u8 * 32 +} + +fn get_bits_descriptor(data: &[u8], bit_len: usize) -> u8 { + let rest_bits = bit_len % 8; + let full_bytes = rest_bits == 0; + data.len() as u8 * 2 - !full_bytes as u8 // subtract 1 if the last byte is not full +} + +fn write_data( + writer: &mut BitWriter, BigEndian>, + data: &[u8], + bit_len: usize, +) -> Result<(), io::Error> { + let data_len = data.len(); + let rest_bits = bit_len % 8; + let full_bytes = rest_bits == 0; + + if !full_bytes { + writer.write_bytes(&data[..data_len - 1])?; + let last_byte = data[data_len - 1]; + let l = last_byte | 1 << (8 - rest_bits - 1); + writer.write(8, l)?; + } else { + writer.write_bytes(data)?; + } + + Ok(()) +} + +fn write_ref_depths( + writer: &mut BitWriter, BigEndian>, + refs: &[ArcCell], + parent_cell_type: CellType, + level: u8, +) -> Result<(), TonCellError> { + for reference in refs { + let child_depth = if matches!( + parent_cell_type, + CellType::MerkleProof | CellType::MerkleUpdate + ) { + reference.get_depth(level + 1) + } else { + reference.get_depth(level) + }; + + writer.write(8, child_depth / 256).map_cell_parser_error()?; + writer.write(8, child_depth % 256).map_cell_parser_error()?; + } + + Ok(()) +} + +fn write_ref_hashes( + writer: &mut BitWriter, BigEndian>, + refs: &[ArcCell], + parent_cell_type: CellType, + level: u8, +) -> Result<(), TonCellError> { + for reference in refs { + let child_hash = if matches!( + parent_cell_type, + CellType::MerkleProof | CellType::MerkleUpdate + ) { + reference.get_hash(level + 1) + } else { + reference.get_hash(level) + }; + + writer.write_bytes(&child_hash).map_cell_parser_error()?; + } + + Ok(()) +} diff --git a/src/cell/bag_of_cells.rs b/src/cell/bag_of_cells.rs index bb643743..84b2c38b 100644 --- a/src/cell/bag_of_cells.rs +++ b/src/cell/bag_of_cells.rs @@ -1,8 +1,8 @@ -use std::collections::{HashMap, HashSet}; use std::sync::Arc; use base64::engine::general_purpose::STANDARD; +use crate::cell::raw_boc_from_boc::convert_to_raw_boc; use crate::cell::*; #[derive(PartialEq, Eq, Debug, Clone, Hash)] @@ -56,29 +56,36 @@ impl BagOfCells { pub fn parse(serial: &[u8]) -> Result { let raw = RawBagOfCells::parse(serial)?; let num_cells = raw.cells.len(); - let mut cells: Vec = Vec::new(); - for i in (0..num_cells).rev() { - let raw_cell = &raw.cells[i]; - let mut cell = Cell { - data: raw_cell.data.clone(), - bit_len: raw_cell.bit_len, - references: Vec::new(), - }; - for r in &raw_cell.references { - if *r <= i { + let mut cells: Vec = Vec::with_capacity(num_cells); + + for (cell_index, raw_cell) in raw.cells.into_iter().enumerate().rev() { + let mut references = Vec::with_capacity(raw_cell.references.len()); + for ref_index in &raw_cell.references { + if *ref_index <= cell_index { return Err(TonCellError::boc_deserialization_error( "References to previous cells are not supported", )); } - cell.references.push(cells[num_cells - 1 - r].clone()); + references.push(cells[num_cells - 1 - ref_index].clone()); } - cells.push(Arc::new(cell)); + + let cell = Cell::new( + raw_cell.data, + raw_cell.bit_len, + references, + raw_cell.is_exotic, + ) + .map_boc_deserialization_error()?; + cells.push(cell.to_arc()); } - let roots: Vec = raw + + let roots = raw .roots - .iter() - .map(|r| cells[num_cells - 1 - r].clone()) + .into_iter() + .map(|r| &cells[num_cells - 1 - r]) + .map(Arc::clone) .collect(); + Ok(BagOfCells { roots }) } @@ -94,98 +101,9 @@ impl BagOfCells { } pub fn serialize(&self, has_crc32: bool) -> Result, TonCellError> { - let raw = self.to_raw()?; + let raw = convert_to_raw_boc(self)?; raw.serialize(has_crc32) } - - /// Traverses all cells, fills all_cells set and inbound references map. - fn traverse_cell_tree( - cell: &ArcCell, - all_cells: &mut HashSet, - in_refs: &mut HashMap>, - ) -> Result<(), TonCellError> { - if !all_cells.contains(cell) { - all_cells.insert(cell.clone()); - for r in &cell.references { - if r == cell { - return Err(TonCellError::BagOfCellsDeserializationError( - "Cell must not reference itself".to_string(), - )); - } - let maybe_refs = in_refs.get_mut(&r.clone()); - match maybe_refs { - Some(refs) => { - refs.insert(cell.clone()); - } - None => { - let mut refs: HashSet = HashSet::new(); - refs.insert(cell.clone()); - in_refs.insert(r.clone(), refs); - } - } - Self::traverse_cell_tree(r, all_cells, in_refs)?; - } - } - Ok(()) - } - - /// Constructs raw representation of BagOfCells - pub(crate) fn to_raw(&self) -> Result { - let mut all_cells: HashSet = HashSet::new(); - let mut in_refs: HashMap> = HashMap::new(); - for r in &self.roots { - Self::traverse_cell_tree(r, &mut all_cells, &mut in_refs)?; - } - let mut no_in_refs: HashSet = HashSet::new(); - for c in &all_cells { - if !in_refs.contains_key(c) { - no_in_refs.insert(c.clone()); - } - } - let mut ordered_cells: Vec = Vec::new(); - let mut indices: HashMap = HashMap::new(); - while !no_in_refs.is_empty() { - let cell = no_in_refs.iter().next().unwrap().clone(); - ordered_cells.push(cell.clone()); - indices.insert(cell.clone(), indices.len()); - for child in &cell.references { - if let Some(refs) = in_refs.get_mut(child) { - refs.remove(&cell); - if refs.is_empty() { - no_in_refs.insert(child.clone()); - in_refs.remove(child); - } - } - } - no_in_refs.remove(&cell); - } - if !in_refs.is_empty() { - return Err(TonCellError::CellBuilderError( - "Can't construct topological ordering: cycle detected".to_string(), - )); - } - let mut cells: Vec = Vec::new(); - for cell in &ordered_cells { - let refs: Vec = cell - .references - .iter() - .map(|c| *indices.get(c).unwrap()) - .collect(); - let raw = RawCell { - data: cell.data.clone(), - bit_len: cell.bit_len, - references: refs, - max_level: cell.get_max_level(), - }; - cells.push(raw); - } - let roots: Vec = self - .roots - .iter() - .map(|c| *indices.get(c).unwrap()) - .collect(); - Ok(RawBagOfCells { cells, roots }) - } } #[cfg(test)] @@ -193,11 +111,12 @@ mod tests { use std::sync::Arc; use std::time::Instant; + use crate::cell::raw_boc_from_boc::convert_to_raw_boc; use crate::cell::{BagOfCells, CellBuilder, TonCellError}; use crate::message::ZERO_COINS; #[test] - fn cell_repr_works() -> anyhow::Result<()> { + fn cell_hash_works() -> anyhow::Result<()> { let hole_address = "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c".parse()?; let contract = "EQDwHr48oKCFD5od9u_TnsCOhe7tGZIei-5ESWfzhlWLRYvW".parse()?; let token0 = "EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR".parse()?; @@ -325,10 +244,10 @@ mod tests { .store_reference(&Arc::new(data))? .build()?; + let hash = hex::encode(state.cell_hash()); assert_eq!( - hex::encode(state.get_repr()?), - "0201340009000838eee530fd07306581470adf04f707ca92198672c6e4186c331954d4a82151\ - d553f1bdeac386cb209570c7d74fac7b2b938896147530e3fb4459f46f7b0a18a0" + hash, + "e557059d5395a79f714ddb966e8419d4681f0ce4aa966cf6088db610841c204a" ); Ok(()) @@ -341,21 +260,21 @@ mod tests { let boc = BagOfCells::parse_base64(raw)?; println!( "wallet_v3_code code_hash{:?}", - boc.single_root()?.cell_hash_base64()? + boc.single_root()?.cell_hash_base64() ); let raw = include_str!("../../resources/wallet/wallet_v3r2.code"); let boc = BagOfCells::parse_base64(raw)?; println!( "wallet_v3r2_code code_hash{:?}", - boc.single_root()?.cell_hash_base64()? + boc.single_root()?.cell_hash_base64() ); let raw = include_str!("../../resources/wallet/wallet_v4r2.code"); let boc = BagOfCells::parse_base64(raw)?; println!( "wallet_v4r2_code code_hash{:?}", - boc.single_root()?.cell_hash_base64()? + boc.single_root()?.cell_hash_base64() ); Ok(()) } @@ -365,7 +284,7 @@ mod tests { fn benchmark_cell_repr() -> anyhow::Result<()> { let now = Instant::now(); for _ in 1..10000 { - let result = cell_repr_works(); + let result = cell_hash_works(); match result { Ok(_) => {} Err(e) => return Err(e), @@ -389,7 +308,7 @@ mod tests { .store_child(inter)? .build()?; let boc = BagOfCells::from_root(root); - let _raw = boc.to_raw()?; + let _raw = convert_to_raw_boc(&boc)?; Ok(()) } } diff --git a/src/cell/builder.rs b/src/cell/builder.rs index 9aaa3690..607640bc 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -15,6 +15,7 @@ const MAX_CELL_REFERENCES: usize = 4; pub struct CellBuilder { bit_writer: BitWriter, BigEndian>, references: Vec, + is_cell_exotic: bool, } impl CellBuilder { @@ -23,9 +24,14 @@ impl CellBuilder { CellBuilder { bit_writer, references: Vec::new(), + is_cell_exotic: false, } } + pub fn set_cell_is_exotic(&mut self, val: bool) { + self.is_cell_exotic = val; + } + pub fn store_bit(&mut self, val: bool) -> Result<&mut Self, TonCellError> { self.bit_writer.write_bit(val).map_cell_builder_error()?; Ok(self) @@ -269,11 +275,13 @@ impl CellBuilder { ref_count ))); } - Ok(Cell { - data: vec.clone(), + + Cell::new( + vec.clone(), bit_len, - references: self.references.clone(), - }) + self.references.clone(), + self.is_cell_exotic, + ) } else { Err(TonCellError::CellBuilderError( "Stream is not byte-aligned".to_string(), diff --git a/src/cell/cell_type.rs b/src/cell/cell_type.rs new file mode 100644 index 00000000..dcc16a16 --- /dev/null +++ b/src/cell/cell_type.rs @@ -0,0 +1,377 @@ +use crate::cell::level_mask::LevelMask; +use crate::cell::{ + ArcCell, Cell, CellHash, MapTonCellError, TonCellError, DEPTH_BYTES, HASH_BYTES, MAX_LEVEL, +}; +use bitstream_io::{BigEndian, ByteRead, ByteReader}; +use std::cmp::PartialEq; +use std::io; +use std::io::Cursor; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) enum CellType { + Ordinary, + PrunedBranch, + Library, + MerkleProof, + MerkleUpdate, +} + +#[derive(Debug, Clone)] +struct Pruned { + hash: CellHash, + depth: u16, +} + +impl CellType { + pub(crate) fn determine_exotic_cell_type(data: &[u8]) -> Result { + let Some(type_byte) = data.first() else { + return Err(TonCellError::InvalidExoticCellData( + "Not enough data for an exotic cell".to_owned(), + )); + }; + + let cell_type = match type_byte { + 1 => CellType::PrunedBranch, + 2 => CellType::Library, + 3 => CellType::MerkleProof, + 4 => CellType::MerkleUpdate, + cell_type => { + return Err(TonCellError::InvalidExoticCellData(format!( + "Invalid first byte in exotic cell data: {}", + cell_type + ))) + } + }; + Ok(cell_type) + } + + pub(crate) fn validate( + &self, + data: &[u8], + bit_len: usize, + references: impl AsRef<[ArcCell]>, + ) -> Result<(), TonCellError> { + match self { + CellType::Ordinary => Ok(()), + CellType::PrunedBranch => self.validate_exotic_pruned(data, bit_len, references), + CellType::Library => self.validate_library(bit_len), + CellType::MerkleProof => self.validate_merkle_proof(data, bit_len, references), + CellType::MerkleUpdate => self.validate_merkle_update(data, bit_len, references), + } + } + + pub(crate) fn level_mask( + &self, + cell_data: &[u8], + cell_data_bit_len: usize, + references: &[ArcCell], + ) -> Result { + let result = match self { + CellType::Ordinary => references + .iter() + .fold(LevelMask::new(0), |level_mask, reference| { + level_mask.apply_or(reference.level_mask) + }), + CellType::PrunedBranch => self.pruned_level_mask(cell_data, cell_data_bit_len)?, + CellType::Library => LevelMask::new(0), + CellType::MerkleProof => references[0].level_mask.shift_right(), + CellType::MerkleUpdate => references[0] + .level_mask + .apply_or(references[1].level_mask) + .shift_right(), + }; + + Ok(result) + } + + pub(crate) fn child_depth(&self, child: &Cell, level: u8) -> u16 { + if matches!(self, CellType::MerkleProof | CellType::MerkleUpdate) { + child.get_depth(level + 1) + } else { + child.get_depth(level) + } + } + + pub(crate) fn resolve_hashes_and_depths( + &self, + hashes: Vec, + depths: Vec, + data: &[u8], + bit_len: usize, + level_mask: LevelMask, + ) -> Result<([CellHash; 4], [u16; 4]), TonCellError> { + let mut resolved_hashes = [[0; 32]; 4]; + let mut resolved_depths = [0; 4]; + + for i in 0..4 { + let hash_index = level_mask.apply(i).hash_index(); + + let (hash, depth) = if self == &CellType::PrunedBranch { + let this_hash_index = level_mask.hash_index(); + if hash_index != this_hash_index { + let pruned = self + .pruned(data, bit_len, level_mask) + .map_cell_builder_error()?; + (pruned[hash_index].hash, pruned[hash_index].depth) + } else { + (hashes[0], depths[0]) + } + } else { + (hashes[hash_index], depths[hash_index]) + }; + + resolved_hashes[i as usize] = hash; + resolved_depths[i as usize] = depth; + } + + Ok((resolved_hashes, resolved_depths)) + } + + fn validate_exotic_pruned( + &self, + data: &[u8], + bit_len: usize, + references: impl AsRef<[ArcCell]>, + ) -> Result<(), TonCellError> { + if !references.as_ref().is_empty() { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned Branch cell can't have refs, got {}", + references.as_ref().len() + ))); + } + + if bit_len < 16 { + return Err(TonCellError::InvalidExoticCellData( + "Not enough data for a PrunnedBranch special cell".to_owned(), + )); + } + + if !self.is_config_proof(bit_len) { + let level_mask = self.pruned_level_mask(data, bit_len)?; + let level = level_mask.level(); + + if level == 0 || level > MAX_LEVEL { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned Branch cell level must be >= 1 and <= 3, got {}/{}", + level_mask.level(), + level_mask.mask() + ))); + } + + let expected_size: usize = + (2 + level_mask.apply(level - 1).hash_count() * (HASH_BYTES + DEPTH_BYTES)) * 8; + + if bit_len != expected_size { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned branch cell must have exactly {expected_size} bits, got {bit_len}" + ))); + } + } + + Ok(()) + } + + fn validate_library(&self, bit_len: usize) -> Result<(), TonCellError> { + const SIZE: usize = (1 + HASH_BYTES) * 8; + + if bit_len != SIZE { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned branch cell must have exactly {SIZE} bits, got {bit_len}" + ))); + } + + Ok(()) + } + + fn validate_merkle_proof( + &self, + data: &[u8], + bit_len: usize, + references: impl AsRef<[ArcCell]>, + ) -> Result<(), TonCellError> { + let references = references.as_ref(); + // type + hash + depth + const SIZE: usize = (1 + HASH_BYTES + DEPTH_BYTES) * 8; + + if bit_len != SIZE { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell must have exactly (8 + 256 + 16) bits, got {bit_len}" + ))); + } + + if references.as_ref().len() != 1 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell must have exactly 1 ref, got {}", + references.as_ref().len() + ))); + } + + let proof_hash: [u8; HASH_BYTES] = data[1..(1 + HASH_BYTES)].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof hash bytes from cell data, {}", + err + )) + })?; + let proof_depth_bytes = data[(1 + HASH_BYTES)..(1 + HASH_BYTES + 2)] + .try_into() + .map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof depth bytes from cell data, {}", + err + )) + })?; + let proof_depth = u16::from_be_bytes(proof_depth_bytes); + let ref_hash = references[0].get_hash(0); + let ref_depth = references[0].get_depth(0); + + if proof_depth != ref_depth { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref depth must be exactly {proof_depth}, got {ref_depth}" + ))); + } + + if proof_hash != ref_hash { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref hash must be exactly {proof_hash:?}, got {ref_hash:?}" + ))); + } + + Ok(()) + } + + fn validate_merkle_update( + &self, + data: &[u8], + bit_len: usize, + references: impl AsRef<[ArcCell]>, + ) -> Result<(), TonCellError> { + let references = references.as_ref(); + // type + hash + hash + depth + depth + const SIZE: usize = 8 + (2 * (256 + 16)); + + if bit_len != SIZE { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Update cell must have exactly (8 + 256 + 16) bits, got {bit_len}" + ))); + } + + if references.len() != 2 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Update cell must have exactly 2 refs, got {}", + references.len() + ))); + } + + let proof_hash1: [u8; 32] = data[1..33].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof hash bytes 1 from cell data, {}", + err + )) + })?; + let proof_hash2: [u8; 32] = data[33..65].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof hash bytes 2 from cell data, {}", + err + )) + })?; + let proof_depth_bytes1 = data[65..67].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof depth bytes 1 from cell data, {}", + err + )) + })?; + let proof_depth_bytes2 = data[67..69].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof depth bytes 2 from cell data, {}", + err + )) + })?; + let proof_depth1 = u16::from_be_bytes(proof_depth_bytes1); + let proof_depth2 = u16::from_be_bytes(proof_depth_bytes2); + + let ref_hash1 = references[0].get_hash(0); + let ref_depth1 = references[0].get_depth(0); + let ref_hash2 = references[1].get_hash(0); + let ref_depth2 = references[1].get_depth(0); + + if proof_depth1 != ref_depth1 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref depth 1 must be exactly {proof_depth1}, got {ref_depth1}" + ))); + } + + if proof_hash1 != ref_hash1 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref hash 1 must be exactly {proof_hash1:?}, got {ref_hash1:?}" + ))); + } + + if proof_depth2 != ref_depth2 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref depth 2 must be exactly {proof_depth2}, got {ref_depth2}" + ))); + } + + if proof_hash2 != ref_hash2 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Merkle Proof cell ref hash 2 must be exactly {proof_hash2:?}, got {ref_hash2:?}" + ))); + } + + Ok(()) + } + + fn pruned_level_mask(&self, data: &[u8], bit_len: usize) -> Result { + if data.len() < 5 { + return Err(TonCellError::InvalidExoticCellData(format!( + "Pruned Branch cell date can't be shorter than 5 bytes, got {}", + data.len() + ))); + } + + let level_mask = if self.is_config_proof(bit_len) { + LevelMask::new(1) + } else { + let mask_byte = data[1]; + LevelMask::new(mask_byte as u32) + }; + + Ok(level_mask) + } + + fn pruned( + &self, + data: &[u8], + bit_len: usize, + level_mask: LevelMask, + ) -> Result, io::Error> { + let current_index = if self.is_config_proof(bit_len) { 1 } else { 2 }; + + let cursor = Cursor::new(&data[current_index..]); + let mut reader = ByteReader::endian(cursor, BigEndian); + + let level = level_mask.level() as usize; + let hashes = (0..level) + .map(|_| reader.read::()) + .collect::, _>>()?; + let depths = (0..level) + .map(|_| reader.read::()) + .collect::, _>>()?; + + let result = hashes + .into_iter() + .zip(depths) + .map(|(hash, depth)| Pruned { depth, hash }) + .collect(); + + Ok(result) + } + + /// Special case for config proof + /// This test proof is generated in the moment of voting for a slashing + /// it seems that tools generate it incorrectly and therefore doesn't have mask in it + /// so we need to hardcode it equal to 1 in this case + fn is_config_proof(&self, bit_len: usize) -> bool { + self == &CellType::PrunedBranch && bit_len == 280 + } +} diff --git a/src/cell/error.rs b/src/cell/error.rs index 1a96ac76..3ba40acb 100644 --- a/src/cell/error.rs +++ b/src/cell/error.rs @@ -23,6 +23,12 @@ pub enum TonCellError { #[error("Invalid address type (Type: {0})")] InvalidAddressType(u8), + #[error("Invalid cell type for exotic cell (Type: {0:?})")] + InvalidExoticCellType(Option), + + #[error("Bad data ({0})")] + InvalidExoticCellData(String), + #[error("Non-empty reader (Remaining bits: {0})")] NonEmptyReader(usize), } diff --git a/src/cell/level_mask.rs b/src/cell/level_mask.rs new file mode 100644 index 00000000..e4af2e91 --- /dev/null +++ b/src/cell/level_mask.rs @@ -0,0 +1,48 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LevelMask { + mask: u32, +} + +impl LevelMask { + pub fn new(new_mask: u32) -> Self { + Self { mask: new_mask } + } + + pub fn mask(&self) -> u32 { + self.mask + } + + pub fn level(&self) -> u8 { + 32 - self.mask.leading_zeros() as u8 + } + + pub fn hash_index(&self) -> usize { + self.mask.count_ones() as usize + } + + pub fn hash_count(&self) -> usize { + self.hash_index() + 1 + } + + pub fn apply(&self, level: u8) -> Self { + LevelMask { + mask: self.mask & ((1u32 << level) - 1), + } + } + + pub fn apply_or(&self, other: Self) -> Self { + LevelMask { + mask: self.mask | other.mask, + } + } + + pub fn shift_right(&self) -> Self { + LevelMask { + mask: self.mask >> 1, + } + } + + pub fn is_significant(&self, level: u8) -> bool { + level == 0 || ((self.mask >> (level - 1)) % 2 != 0) + } +} diff --git a/src/cell/raw.rs b/src/cell/raw.rs index 6c4e4ae0..f7bc4a48 100644 --- a/src/cell/raw.rs +++ b/src/cell/raw.rs @@ -4,6 +4,7 @@ use bitstream_io::{BigEndian, BitWrite, BitWriter, ByteRead, ByteReader}; use crc::Crc; use lazy_static::lazy_static; +use crate::cell::level_mask::LevelMask; use crate::cell::{MapTonCellError, TonCellError}; lazy_static! { @@ -18,7 +19,26 @@ pub(crate) struct RawCell { pub(crate) data: Vec, pub(crate) bit_len: usize, pub(crate) references: Vec, - pub(crate) max_level: u8, + pub(crate) is_exotic: bool, + level_mask: u32, +} + +impl RawCell { + pub(crate) fn new( + data: Vec, + bit_len: usize, + references: Vec, + level_mask: u32, + is_exotic: bool, + ) -> Self { + Self { + data, + bit_len, + references, + level_mask: level_mask & 7, + is_exotic, + } + } } /// Raw representation of BagOfCells. @@ -109,34 +129,36 @@ impl RawBagOfCells { //Based on https://github.com/toncenter/tonweb/blob/c2d5d0fc23d2aec55a0412940ce6e580344a288c/src/boc/Cell.js#L198 let root_count = self.roots.len(); - if root_count > 1 { - return Err(TonCellError::boc_serialization_error(format!( - "Single root expected, got {}", - root_count - ))); - } - let num_ref_bits = 32 - (self.cells.len() as u32).leading_zeros(); let num_ref_bytes = (num_ref_bits + 7) / 8; + let has_idx = false; let mut full_size = 0u32; - let mut index = Vec::::with_capacity(self.cells.len()); + for cell in &self.cells { - index.push(full_size); full_size += raw_cell_size(cell, num_ref_bytes); } let num_offset_bits = 32 - full_size.leading_zeros(); let num_offset_bytes = (num_offset_bits + 7) / 8; - let mut writer = BitWriter::endian(Vec::new(), BigEndian); + let total_size = 4 + // magic + 1 + // flags and s_bytes + 1 + // offset_bytes + 3 * num_ref_bytes + // cells_num, roots, complete + num_offset_bytes + // full_size + num_ref_bytes + // root_idx + (if has_idx { self.cells.len() as u32 * num_offset_bytes } else { 0 }) + + full_size + + (if has_crc32 { 4 } else { 0 }); + + let mut writer = BitWriter::endian(Vec::with_capacity(total_size as usize), BigEndian); writer .write(32, GENERIC_BOC_MAGIC) .map_boc_serialization_error()?; //write flags byte - let has_idx = false; let has_cache_bits = false; let flags: u8 = 0; writer.write_bit(has_idx).map_boc_serialization_error()?; @@ -155,17 +177,19 @@ impl RawBagOfCells { .write(8 * num_ref_bytes, self.cells.len() as u32) .map_boc_serialization_error()?; writer - .write(8 * num_ref_bytes, 1) - .map_boc_serialization_error()?; // One root for now + .write(8 * num_ref_bytes, root_count as u32) + .map_boc_serialization_error()?; writer .write(8 * num_ref_bytes, 0) .map_boc_serialization_error()?; // Complete BOCs only writer .write(8 * num_offset_bytes, full_size) .map_boc_serialization_error()?; - writer - .write(8 * num_ref_bytes, 0) - .map_boc_serialization_error()?; // Root should have index 0 + for &root in &self.roots { + writer + .write(8 * num_ref_bytes, root as u32) + .map_boc_serialization_error()?; + } for cell in &self.cells { write_raw_cell(&mut writer, cell, num_ref_bytes)?; @@ -195,12 +219,23 @@ fn read_cell( let d1 = reader.read::().map_boc_deserialization_error()?; let d2 = reader.read::().map_boc_deserialization_error()?; - let max_level = d1 >> 5; - let _is_exotic = (d1 & 8) != 0; - let ref_num = d1 & 0x07; + let ref_num = d1 & 0b111; + let is_exotic = (d1 & 0b1000) != 0; + let has_hashes = (d1 & 0b10000) != 0; + let level_mask = (d1 >> 5) as u32; let data_size = ((d2 >> 1) + (d2 & 1)).into(); let full_bytes = (d2 & 0x01) == 0; + if has_hashes { + let hash_count = LevelMask::new(level_mask).hash_count(); + let skip_size = hash_count * (32 + 2); + + // TODO: check depth and hashes + reader + .skip(skip_size as u32) + .map_boc_deserialization_error()?; + } + let mut data = reader .read_to_vec(data_size) .map_boc_deserialization_error()?; @@ -225,12 +260,7 @@ fn read_cell( for _ in 0..ref_num { references.push(read_var_size(reader, size)?); } - let cell = RawCell { - data, - bit_len, - references, - max_level, - }; + let cell = RawCell::new(data, bit_len, references, level_mask, is_exotic); Ok(cell) } @@ -244,8 +274,8 @@ fn write_raw_cell( cell: &RawCell, ref_size_bytes: u32, ) -> Result<(), TonCellError> { - let level = 0u32; // TODO: Support - let is_exotic = 0u32; // TODO: Support + let level = cell.level_mask; + let is_exotic = cell.is_exotic as u32; let num_refs = cell.references.len() as u32; let d1 = num_refs + is_exotic * 8 + level * 32; @@ -273,7 +303,6 @@ fn write_raw_cell( writer .write(8 * ref_size_bytes, *r as u32) .map_boc_serialization_error()?; - // One root for now } Ok(()) @@ -303,12 +332,7 @@ mod tests { #[test] fn test_raw_cell_serialize() { - let raw_cell = RawCell { - data: vec![1; 128], - bit_len: 1023, - references: vec![], - max_level: 255, - }; + let raw_cell = RawCell::new(vec![1; 128], 1023, vec![], 255, false); let raw_bag = RawBagOfCells { cells: vec![raw_cell], roots: vec![0], diff --git a/src/cell/raw_boc_from_boc.rs b/src/cell/raw_boc_from_boc.rs new file mode 100644 index 00000000..5b5bb939 --- /dev/null +++ b/src/cell/raw_boc_from_boc.rs @@ -0,0 +1,151 @@ +use crate::cell::{ArcCell, BagOfCells, Cell, CellHash, RawBagOfCells, RawCell, TonCellError}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::sync::Arc; + +#[derive(Debug, Clone)] +struct IndexedCell { + index: usize, + cell: ArcCell, +} + +pub(crate) fn convert_to_raw_boc(boc: &BagOfCells) -> Result { + let cells_by_hash = build_and_verify_index(&boc.roots); + + // Sort indexed cells by their index value. + let mut index_slice: Vec<_> = cells_by_hash.values().collect(); + index_slice.sort_unstable_by(|a, b| a.borrow().index.cmp(&b.borrow().index)); + + // Remove gaps in indices. + index_slice + .iter() + .enumerate() + .for_each(|(real_index, indexed_cell)| indexed_cell.borrow_mut().index = real_index); + + let cells_iter = index_slice + .into_iter() + .map(|indexed_cell| indexed_cell.borrow().cell.clone()); + let raw_cells = raw_cells_from_cells(cells_iter, &cells_by_hash)?; + let root_indices = root_indices(&boc.roots, &cells_by_hash)?; + + Ok(RawBagOfCells { + cells: raw_cells, + roots: root_indices, + }) +} + +fn build_and_verify_index(roots: &[ArcCell]) -> HashMap> { + let mut current_cells: Vec<_> = roots.iter().map(Arc::clone).collect(); + let mut new_hash_index = 0; + let mut cells_by_hash = HashMap::new(); + + // Process cells to build the initial index. + while !current_cells.is_empty() { + let mut next_cells = Vec::with_capacity(current_cells.len() * 4); + for cell in current_cells.iter() { + let hash = cell.cell_hash(); + + if cells_by_hash.contains_key(&hash) { + continue; // Skip if already indexed. + } + + cells_by_hash.insert( + hash, + RefCell::new(IndexedCell { + cell: Arc::clone(cell), + index: new_hash_index, + }), + ); + + new_hash_index += 1; + next_cells.extend(cell.references.clone()); // Add referenced cells for the next iteration. + } + + current_cells = next_cells; + } + + // Ensure indices are in the correct order based on cell references. + let mut verify_order = true; + while verify_order { + verify_order = false; + + for index_cell in cells_by_hash.values() { + for reference in index_cell.borrow().cell.references.iter() { + let ref_hash = reference.cell_hash(); + if let Some(id_ref) = cells_by_hash.get(&ref_hash) { + if id_ref.borrow().index < index_cell.borrow().index { + id_ref.borrow_mut().index = new_hash_index; + new_hash_index += 1; + verify_order = true; // Reverify if an index was updated. + } + } + } + } + } + + cells_by_hash +} + +fn root_indices( + roots: &[ArcCell], + cells_dict: &HashMap>, +) -> Result, TonCellError> { + roots + .iter() + .map(|root_cell| root_cell.cell_hash()) + .map(|root_cell_hash| { + cells_dict + .get(&root_cell_hash) + .map(|index_record| index_record.borrow().index) + .ok_or_else(|| { + TonCellError::BagOfCellsSerializationError(format!( + "Couldn't find cell with hash {root_cell_hash:?} while searching for roots" + )) + }) + }) + .collect() +} + +fn raw_cells_from_cells( + cells: impl Iterator, + cells_by_hash: &HashMap>, +) -> Result, TonCellError> { + cells + .map(|cell| raw_cell_from_cell(&cell, cells_by_hash)) + .collect() +} + +fn raw_cell_from_cell( + cell: &Cell, + cells_by_hash: &HashMap>, +) -> Result { + raw_cell_reference_indices(cell, cells_by_hash).map(|reference_indices| { + RawCell::new( + cell.data.clone(), + cell.bit_len, + reference_indices, + cell.get_level_mask(), + cell.is_exotic(), + ) + }) +} + +fn raw_cell_reference_indices( + cell: &Cell, + cells_by_hash: &HashMap>, +) -> Result, TonCellError> { + cell.references + .iter() + .map(|cell| { + cells_by_hash + .get(&cell.cell_hash()) + .ok_or_else(|| { + TonCellError::BagOfCellsSerializationError(format!( + "Couldn't find cell with hash {:?} while searching for references", + cell.cell_hash() + )) + }) + .map(|cell| cell.borrow().index) + }) + .collect() +} diff --git a/src/cell/slice.rs b/src/cell/slice.rs index 7bda8568..74037aaf 100644 --- a/src/cell/slice.rs +++ b/src/cell/slice.rs @@ -142,11 +142,12 @@ impl CellSlice { .skip(self.start_bit as u32) .map_cell_parser_error()?; bit_reader.read_bits(bit_len, data.as_mut_slice())?; - let cell = Cell { + + Cell::new( data, bit_len, - references: self.cell.references[self.start_ref..self.end_ref].to_vec(), - }; - Ok(cell) + self.cell.references[self.start_ref..self.end_ref].to_vec(), + false, + ) } } diff --git a/src/cell/state_init.rs b/src/cell/state_init.rs index 83ecd3c9..73dc5a58 100644 --- a/src/cell/state_init.rs +++ b/src/cell/state_init.rs @@ -1,4 +1,4 @@ -use super::ArcCell; +use super::{ArcCell, CellHash}; use crate::cell::{Cell, CellBuilder, TonCellError}; pub struct StateInitBuilder { @@ -58,8 +58,8 @@ impl StateInitBuilder { } impl StateInit { - pub fn create_account_id(code: &ArcCell, data: &ArcCell) -> Result, TonCellError> { - StateInitBuilder::new(code, data).build()?.cell_hash() + pub fn create_account_id(code: &ArcCell, data: &ArcCell) -> Result { + Ok(StateInitBuilder::new(code, data).build()?.cell_hash()) } } diff --git a/src/types/tvm_stack_entry.rs b/src/types/tvm_stack_entry.rs index 699e684f..baf5ca2c 100644 --- a/src/types/tvm_stack_entry.rs +++ b/src/types/tvm_stack_entry.rs @@ -111,8 +111,8 @@ impl TvmStackEntry { pub fn get_string(&self) -> Result { match self { TvmStackEntry::Slice(slice) => { - let data = &slice.cell.data; - let value = String::from_utf8(data.clone())?; + let data = slice.cell.data(); + let value = String::from_utf8(data.to_vec())?; Ok(value) } @@ -189,11 +189,7 @@ impl TryFrom<&String> for TvmStackEntry { let bytes = value.as_bytes().to_vec(); let bit_len = bytes.len() * 8; // todo: support reference and snake format - let cell = Cell { - data: bytes, - bit_len, - references: vec![], - }; + let cell = Cell::new(bytes, bit_len, vec![], false)?; Ok(TvmStackEntry::Slice(CellSlice::full_cell(cell)?)) } } @@ -247,7 +243,7 @@ impl TryFrom<&TlTvmStackEntry> for TvmStackEntry { cell: cell.clone(), start_bit: 0, start_ref: 0, - end_bit: cell.bit_len, + end_bit: cell.bit_len(), end_ref: 0, }; TvmStackEntry::Slice(cell_slice) diff --git a/src/wallet.rs b/src/wallet.rs index 854ba618..0c70573c 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -265,7 +265,7 @@ impl TonWallet { } pub fn sign_external_body(&self, external_body: &Cell) -> Result { - let message_hash = external_body.cell_hash()?; + let message_hash = external_body.cell_hash(); let sig = signature(message_hash.as_slice(), self.key_pair.secret_key.as_slice()) .map_err(|e| TonMessageError::NaclCryptographicError(e.message))?; let mut body_builder = CellBuilder::new(); diff --git a/tests/boc.rs b/tests/boc.rs new file mode 100644 index 00000000..901ff316 --- /dev/null +++ b/tests/boc.rs @@ -0,0 +1,131 @@ +use base64::prelude::*; +use num_bigint::BigUint; +use num_traits::Zero; +use std::collections::HashSet; +use std::str::FromStr; +use std::sync::Arc; +use tonlib::address::TonAddress; +use tonlib::cell::{BagOfCells, CellBuilder, StateInit}; + +#[test] +fn account_proof_cell() { + let entry = "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"; + let bytes_entry = BASE64_STANDARD.decode(entry).unwrap(); + let boc = BagOfCells::parse(&bytes_entry).unwrap(); + let first_root_hash = hex::encode(boc.roots[0].cell_hash()); + let second_root_hash = hex::encode(boc.roots[1].cell_hash()); + let expected_hashes = HashSet::from([ + "ceb74a112c1d4e53e4bbab30fe1a0153b10ffeaa33a828818dd052eb58004d4a", + "1b8709beb7f8fe24f17fec2f477bb77fac399920b0228794a519f9e3961db29c", + ]); + let real_hashes = HashSet::from([first_root_hash.as_str(), second_root_hash.as_str()]); + // if hashes of two roots are correct, we can be sure that the whole trees of cells are correct + assert_eq!(real_hashes, expected_hashes); + + // then we can serialize again + let bytes_entry_again = boc.serialize(false).unwrap(); + let boc_again = BagOfCells::parse(&bytes_entry_again).unwrap(); + let first_root_hash = hex::encode(boc_again.roots[0].cell_hash()); + let second_root_hash = hex::encode(boc_again.roots[1].cell_hash()); + let real_hashes = HashSet::from([first_root_hash.as_str(), second_root_hash.as_str()]); + assert_eq!(real_hashes, expected_hashes); +} + +#[test] +fn account_state() { + let entry = "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IAMSAxMCASAJiwdpAgEgB4cDEQAJGKw/GKACASAH3gf9AgEgB90JbgIBIAMWAxcCASADJAMlAgEgAxgDGQIBIAMeAx8CASADGgMbAgEgAxwDHQIBIAdWCSgCASAEWQf9AgEgCVAHbQIBIAmfCZACASADIAMhAgEgAyIDIwIBIAkaCM0CASADPwnAAgEgCeEHbQIBIAlQB9ACASADJgMnAgEgAy0DLgIBIAMoAykCASADKgMrAgEgB/UJEwIBIAWyCAUCASAH9QfxAgEgA68DLAAJGKw/OiACASADLwMwAgEgAzIDMwIBIAVnBzQCASAHbQMxAAkYrD+Z4AIBIARYCYUCASADpQmKAgEgAzYDNwIBIANHA0gCASADOAM5AgEgA0ADQQIBIAM6AzsCASADPQM+AgEgAzwGjgIBIAgGCZAACRisP41gAgEgCNgJNwIBIAk2Az8ACRisP6VgAgEgA0IDQwIBIANEA0UCASAH0Ac0AgEgBZAJRwIBIAnaA0YCASAGZAmBAAkYrD8MoAIBIANJA0oCASADTwNQAgEgA0sDTAIBIANNA04CASAJngf1AgEgCcwHigIBIAl/B/0CASAH/QkxAgEgA1EDUgIBIANUA1UCASADUwZxAgEgB00IEAAJGKw/buACASAICwnaAgEgCU4J2wIBIANYA1kCASADZANlAgEgA1oDWwIBIANeA18CASADXANdAgFYBzcFpgAJVisULfgACUYrFDgoAgEgA2ADYQIBIANiA2MCASAJhAdNAAlGKw/ciAIBIAfdB2kCASAI+gRZAgHUCAsH3gIBIANmA2cCASADaANpAgEgA2sDbAAJVisP4VgCASADagOKAAkYrD9boAAJVisPx9gCASAH/QevAgEgA28DcAIBIAN8A30CASADcQNyAgEgA3cDeAIBIANzA3QCASADdQN2AgEgCY0JMAIBIAOKBoYCASAFsgfdAgEgB/EJiwIBIAN5B5ICASADegN7AgEgCTkJCAIBIAadCbQACUYrD9GYAgEgA34DfwIBIAOGA4cCASADgAOBAgEgA4QDhQAJVisQpPgCASADggODAAkYrEK6IAAJGKxC3qACASAGZAmAAgEgCWkJ4AIBIAOIA4kCASADjAONAgEgA4oJnwIBIAfdA4sACRisPv/gAAkYrEnc4AIBIAkoCdsCASAFpgkaAgEgA5AD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"; + let expected_hash = "38ca07263352adebf3b8de4a36b6b3898e1de5953991f7356b0160bb0fb15ef7"; + typical_boc_test(entry, expected_hash); +} + +#[test] +fn account_state_test() { + let entry = "te6ccgECFgEAAzwAAnHAC2sf/Hy34aMM7n9f9/V+ThHDehjH71LWBETy/JrTirPCLIWQQx1iCWAAABo03x9sGW4gl8XD00ABAgEU/wD0pBP0vPLICwMAUQAAKwIpqaMXw+Q7b1IiPXMEBAINhh9rwzJYtGNen/6gFDXD2gd0GaxAAgEgBAUCAUgGBwT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xITFBUC5tAB0NMDIXGwkl8E4CLXScEgkl8E4ALTHyGCEHBsdWe9IoIQZHN0cr2wkl8F4AP6QDAg+kQByMoHy//J0O1E0IEBQNch9AQwXIEBCPQKb6Exs5JfB+AF0z/IJYIQcGx1Z7qSODDjDQOCEGRzdHK6kl8G4w0ICQIBIAoLAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAwNAFm9JCtvaiaECAoGuQ+gIYRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQCAVgODwARuMl+1E0NcLH4AD2ynftRNCBAUDXIfQEMALIygfL/8nQAYEBCPQKb6ExgAgEgEBEAGa3OdqJoQCBrkOuF/8AAGa8d9qJoQBBrkOuFj8AAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1U"; + let expected_hash = "c8af6e3c2dc6d04920ac0c3e516f6ed62e14466224c4186fae0a1800017a0d1c"; + typical_boc_test(entry, expected_hash); +} + +#[test] +fn account_state_pruned() { + let entry = "te6ccgEBBAEArwAJRgPIr248LcbQSSCsDD5Rb27WLhRGYiTEGG+uChgAAXoNHAAIASJxwAtrH/x8t+GjDO5/X/f1fk4Rw3oYx+9S1gRE8vya04qzwiyFkEMdYglgAAAaNN8fbBluIJfFw9NAAgMoSAEB/rX/aCDi/w2Ug+fg1iyBfYRniftK5YDIeIZtlZ2r1cAAByhIAQEg0z54hgTX/ohMEnHs6qluCydagWgxQoxSyLwK8qfAOQAA"; + let expected_hash = "a6f4b8afa43a9ee61f6d89050d665d164c94c5eca658ddb6c2ab34b4118ab34c"; + typical_boc_test(entry, expected_hash); +} + +#[test] +fn block() { + let entry = "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"; + let expected_hash = "84753a60efefc7169959fdf34ea21f3fa9f5a85c3a8690db77b1f141e0ff47ee"; + typical_boc_test(entry, expected_hash); +} + +#[test] +fn block_2() { + let entry = "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"; + let expected_hash = "25e19f8c4574804a8cabade6bab736a27a67f4f6696a8a0feb93b3dfbfab7fcf"; + typical_boc_test(entry, expected_hash); +} + +#[test] +// Discussable case, ts and golang libraries produce different results, developer chose ts option because it looks like a corner case in their code. +fn config_proof() { + let entry = "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"; + let expected_hash = "03c57e9e91dbdbeaa0b781f80324941d1c549c688699568880f496bc80995fe5"; + typical_boc_test(entry, expected_hash); +} + +#[test] +fn big_boc() { + let entry = "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"; + let expected_hash = "4cbb7e3b0a637d60390662e75c1822547fdfbcbfa1c1a249ee23cd6a12eb0290"; + typical_boc_test(entry, expected_hash); +} + +#[test] +fn many_cells() { + let entry = "te6cckICAgEAAQAAFCUAAAFRdHC9hAQehoKvMTl/ZwfFRSf00qzBIL9bZ0FuhYGbVdUAAAF8/OQnXsAAAQQAAAIAAgACAAIEAAADAAMAAwADBAAABAAEAAQABAQAAAUABQAFAAUEAAAGAAYABgAGBAAABwAHAAcABwQAAAgACAAIAAgEAAAJAAkACQAJBAAACgAKAAoACgQAAAsACwALAAsEAAAMAAwADAAMBAAADQANAA0ADQQAAA4ADgAOAA4EAAAPAA8ADwAPBAAAEAAQABAAEAQAABEAEQARABEEAAASABIAEgASBAAAEwATABMAEwQAABQAFAAUABQEAAAVABUAFQAVBAAAFgAWABYAFgQAABcAFwAXABcEAAAYABgAGAAYBAAAGQAZABkAGQQAABoAGgAaABoEAAAbABsAGwAbBAAAHAAcABwAHAQAAB0AHQAdAB0EAAAeAB4AHgAeBAAAHwAfAB8AHwQAACAAIAAgACAEAAAhACEAIQAhBAAAIgAiACIAIgQAACMAIwAjACMEAAAkACQAJAAkBAAAJQAlACUAJQQAACYAJgAmACYEAAAnACcAJwAnBAAAKAAoACgAKAQAACkAKQApACkEAAAqACoAKgAqBAAAKwArACsAKwQAACwALAAsACwEAAAtAC0ALQAtBAAALgAuAC4ALgQAAC8ALwAvAC8EAAAwADAAMAAwBAAAMQAxADEAMQQAADIAMgAyADIEAAAzADMAMwAzBAAANAA0ADQANAQAADUANQA1ADUEAAA2ADYANgA2BAAANwA3ADcANwQAADgAOAA4ADgEAAA5ADkAOQA5BAAAOgA6ADoAOgQAADsAOwA7ADsEAAA8ADwAPAA8BAAAPQA9AD0APQQAAD4APgA+AD4EAAA/AD8APwA/BAAAQABAAEAAQAQAAEEAQQBBAEEEAABCAEIAQgBCBAAAQwBDAEMAQwQAAEQARABEAEQEAABFAEUARQBFBAAARgBGAEYARgQAAEcARwBHAEcEAABIAEgASABIBAAASQBJAEkASQQAAEoASgBKAEoEAABLAEsASwBLBAAATABMAEwATAQAAE0ATQBNAE0EAABOAE4ATgBOBAAATwBPAE8ATwQAAFAAUABQAFAEAABRAFEAUQBRBAAAUgBSAFIAUgQAAFMAUwBTAFMEAABUAFQAVABUBAAAVQBVAFUAVQQAAFYAVgBWAFYEAABXAFcAVwBXBAAAWABYAFgAWAQAAFkAWQBZAFkEAABaAFoAWgBaBAAAWwBbAFsAWwQAAFwAXABcAFwEAABdAF0AXQBdBAAAXgBeAF4AXgQAAF8AXwBfAF8EAABgAGAAYABgBAAAYQBhAGEAYQQAAGIAYgBiAGIEAABjAGMAYwBjBAAAZABkAGQAZAQAAGUAZQBlAGUEAABmAGYAZgBmBAAAZwBnAGcAZwQAAGgAaABoAGgEAABpAGkAaQBpBAAAagBqAGoAagQAAGsAawBrAGsEAABsAGwAbABsBAAAbQBtAG0AbQQAAG4AbgBuAG4EAABvAG8AbwBvBAAAcABwAHAAcAQAAHEAcQBxAHEEAAByAHIAcgByBAAAcwBzAHMAcwQAAHQAdAB0AHQEAAB1AHUAdQB1BAAAdgB2AHYAdgQAAHcAdwB3AHcEAAB4AHgAeAB4BAAAeQB5AHkAeQQAAHoAegB6AHoEAAB7AHsAewB7BAAAfAB8AHwAfAQAAH0AfQB9AH0EAAB+AH4AfgB+BAAAfwB/AH8AfwQAAIAAgACAAIAEAACBAIEAgQCBBAAAggCCAIIAggQAAIMAgwCDAIMEAACEAIQAhACEBAAAhQCFAIUAhQQAAIYAhgCGAIYEAACHAIcAhwCHBAAAiACIAIgAiAQAAIkAiQCJAIkEAACKAIoAigCKBAAAiwCLAIsAiwQAAIwAjACMAIwEAACNAI0AjQCNBAAAjgCOAI4AjgQAAI8AjwCPAI8EAACQAJAAkACQBAAAkQCRAJEAkQQAAJIAkgCSAJIEAACTAJMAkwCTBAAAlACUAJQAlAQAAJUAlQCVAJUEAACWAJYAlgCWBAAAlwCXAJcAlwQAAJgAmACYAJgEAACZAJkAmQCZBAAAmgCaAJoAmgQAAJsAmwCbAJsEAACcAJwAnACcBAAAnQCdAJ0AnQQAAJ4AngCeAJ4EAACfAJ8AnwCfBAAAoACgAKAAoAQAAKEAoQChAKEEAACiAKIAogCiBAAAowCjAKMAowQAAKQApACkAKQEAAClAKUApQClBAAApgCmAKYApgQAAKcApwCnAKcEAACoAKgAqACoBAAAqQCpAKkAqQQAAKoAqgCqAKoEAACrAKsAqwCrBAAArACsAKwArAQAAK0ArQCtAK0EAACuAK4ArgCuBAAArwCvAK8ArwQAALAAsACwALAEAACxALEAsQCxBAAAsgCyALIAsgQAALMAswCzALMEAAC0ALQAtAC0BAAAtQC1ALUAtQQAALYAtgC2ALYEAAC3ALcAtwC3BAAAuAC4ALgAuAQAALkAuQC5ALkEAAC6ALoAugC6BAAAuwC7ALsAuwQAALwAvAC8ALwEAAC9AL0AvQC9BAAAvgC+AL4AvgQAAL8AvwC/AL8EAADAAMAAwADABAAAwQDBAMEAwQQAAMIAwgDCAMIEAADDAMMAwwDDBAAAxADEAMQAxAQAAMUAxQDFAMUEAADGAMYAxgDGBAAAxwDHAMcAxwQAAMgAyADIAMgEAADJAMkAyQDJBAAAygDKAMoAygQAAMsAywDLAMsEAADMAMwAzADMBAAAzQDNAM0AzQQAAM4AzgDOAM4EAADPAM8AzwDPBAAA0ADQANAA0AQAANEA0QDRANEEAADSANIA0gDSBAAA0wDTANMA0wQAANQA1ADUANQEAADVANUA1QDVBAAA1gDWANYA1gQAANcA1wDXANcEAADYANgA2ADYBAAA2QDZANkA2QQAANoA2gDaANoEAADbANsA2wDbBAAA3ADcANwA3AQAAN0A3QDdAN0EAADeAN4A3gDeBAAA3wDfAN8A3wQAAOAA4ADgAOAEAADhAOEA4QDhBAAA4gDiAOIA4gQAAOMA4wDjAOMEAADkAOQA5ADkBAAA5QDlAOUA5QQAAOYA5gDmAOYEAADnAOcA5wDnBAAA6ADoAOgA6AQAAOkA6QDpAOkEAADqAOoA6gDqBAAA6wDrAOsA6wQAAOwA7ADsAOwEAADtAO0A7QDtBAAA7gDuAO4A7gQAAO8A7wDvAO8EAADwAPAA8ADwBAAA8QDxAPEA8QQAAPIA8gDyAPIEAADzAPMA8wDzBAAA9AD0APQA9AQAAPUA9QD1APUEAAD2APYA9gD2BAAA9wD3APcA9wQAAPgA+AD4APgEAAD5APkA+QD5BAAA+gD6APoA+gQAAPsA+wD7APsEAAD8APwA/AD8BAAA/QD9AP0A/QQAAP4A/gD+AP4EAAD/AP8A/wD/BAABAAEAAQABAAQAAQEBAQEBAQEEAAECAQIBAgECBAABAwEDAQMBAwQAAQQBBAEEAQQEAAEFAQUBBQEFBAABBgEGAQYBBgQAAQcBBwEHAQcEAAEIAQgBCAEIBAABCQEJAQkBCQQAAQoBCgEKAQoEAAELAQsBCwELBAABDAEMAQwBDAQAAQ0BDQENAQ0EAAEOAQ4BDgEOBAABDwEPAQ8BDwQAARABEAEQARAEAAERAREBEQERBAABEgESARIBEgQAARMBEwETARMEAAEUARQBFAEUBAABFQEVARUBFQQAARYBFgEWARYEAAEXARcBFwEXBAABGAEYARgBGAQAARkBGQEZARkEAAEaARoBGgEaBAABGwEbARsBGwQAARwBHAEcARwEAAEdAR0BHQEdBAABHgEeAR4BHgQAAR8BHwEfAR8EAAEgASABIAEgBAABIQEhASEBIQQAASIBIgEiASIEAAEjASMBIwEjBAABJAEkASQBJAQAASUBJQElASUEAAEmASYBJgEmBAABJwEnAScBJwQAASgBKAEoASgEAAEpASkBKQEpBAABKgEqASoBKgQAASsBKwErASsEAAEsASwBLAEsBAABLQEtAS0BLQQAAS4BLgEuAS4EAAEvAS8BLwEvBAABMAEwATABMAQAATEBMQExATEEAAEyATIBMgEyBAABMwEzATMBMwQAATQBNAE0ATQEAAE1ATUBNQE1BAABNgE2ATYBNgQAATcBNwE3ATcEAAE4ATgBOAE4BAABOQE5ATkBOQQAAToBOgE6AToEAAE7ATsBOwE7BAABPAE8ATwBPAQAAT0BPQE9AT0EAAE+AT4BPgE+BAABPwE/AT8BPwQAAUABQAFAAUAEAAFBAUEBQQFBBAABQgFCAUIBQgQAAUMBQwFDAUMEAAFEAUQBRAFEBAABRQFFAUUBRQQAAUYBRgFGAUYEAAFHAUcBRwFHBAABSAFIAUgBSAQAAUkBSQFJAUkEAAFKAUoBSgFKBAABSwFLAUsBSwQAAUwBTAFMAUwEAAFNAU0BTQFNBAABTgFOAU4BTgQAAU8BTwFPAU8EAAFQAVABUAFQBAABUQFRAVEBUQQAAVIBUgFSAVIEAAFTAVMBUwFTBAABVAFUAVQBVAQAAVUBVQFVAVUEAAFWAVYBVgFWBAABVwFXAVcBVwQAAVgBWAFYAVgEAAFZAVkBWQFZBAABWgFaAVoBWgQAAVsBWwFbAVsEAAFcAVwBXAFcBAABXQFdAV0BXQQAAV4BXgFeAV4EAAFfAV8BXwFfBAABYAFgAWABYAQAAWEBYQFhAWEEAAFiAWIBYgFiBAABYwFjAWMBYwQAAWQBZAFkAWQEAAFlAWUBZQFlBAABZgFmAWYBZgQAAWcBZwFnAWcEAAFoAWgBaAFoBAABaQFpAWkBaQQAAWoBagFqAWoEAAFrAWsBawFrBAABbAFsAWwBbAQAAW0BbQFtAW0EAAFuAW4BbgFuBAABbwFvAW8BbwQAAXABcAFwAXAEAAFxAXEBcQFxBAABcgFyAXIBcgQAAXMBcwFzAXMEAAF0AXQBdAF0BAABdQF1AXUBdQQAAXYBdgF2AXYEAAF3AXcBdwF3BAABeAF4AXgBeAQAAXkBeQF5AXkEAAF6AXoBegF6BAABewF7AXsBewQAAXwBfAF8AXwEAAF9AX0BfQF9BAABfgF+AX4BfgQAAX8BfwF/AX8EAAGAAYABgAGABAABgQGBAYEBgQQAAYIBggGCAYIEAAGDAYMBgwGDBAABhAGEAYQBhAQAAYUBhQGFAYUEAAGGAYYBhgGGBAABhwGHAYcBhwQAAYgBiAGIAYgEAAGJAYkBiQGJBAABigGKAYoBigQAAYsBiwGLAYsEAAGMAYwBjAGMBAABjQGNAY0BjQQAAY4BjgGOAY4EAAGPAY8BjwGPBAABkAGQAZABkAQAAZEBkQGRAZEEAAGSAZIBkgGSBAABkwGTAZMBkwQAAZQBlAGUAZQEAAGVAZUBlQGVBAABlgGWAZYBlgQAAZcBlwGXAZcEAAGYAZgBmAGYBAABmQGZAZkBmQQAAZoBmgGaAZoEAAGbAZsBmwGbBAABnAGcAZwBnAQAAZ0BnQGdAZ0EAAGeAZ4BngGeBAABnwGfAZ8BnwQAAaABoAGgAaAEAAGhAaEBoQGhBAABogGiAaIBogQAAaMBowGjAaMEAAGkAaQBpAGkBAABpQGlAaUBpQQAAaYBpgGmAaYEAAGnAacBpwGnBAABqAGoAagBqAQAAakBqQGpAakEAAGqAaoBqgGqBAABqwGrAasBqwQAAawBrAGsAawEAAGtAa0BrQGtBAABrgGuAa4BrgQAAa8BrwGvAa8EAAGwAbABsAGwBAABsQGxAbEBsQQAAbIBsgGyAbIEAAGzAbMBswGzBAABtAG0AbQBtAQAAbUBtQG1AbUEAAG2AbYBtgG2BAABtwG3AbcBtwQAAbgBuAG4AbgEAAG5AbkBuQG5BAABugG6AboBugQAAbsBuwG7AbsEAAG8AbwBvAG8BAABvQG9Ab0BvQQAAb4BvgG+Ab4EAAG/Ab8BvwG/BAABwAHAAcABwAQAAcEBwQHBAcEEAAHCAcIBwgHCBAABwwHDAcMBwwQAAcQBxAHEAcQEAAHFAcUBxQHFBAABxgHGAcYBxgQAAccBxwHHAccEAAHIAcgByAHIBAAByQHJAckByQQAAcoBygHKAcoEAAHLAcsBywHLBAABzAHMAcwBzAQAAc0BzQHNAc0EAAHOAc4BzgHOBAABzwHPAc8BzwQAAdAB0AHQAdAEAAHRAdEB0QHRBAAB0gHSAdIB0gQAAdMB0wHTAdMEAAHUAdQB1AHUBAAB1QHVAdUB1QQAAdYB1gHWAdYEAAHXAdcB1wHXBAAB2AHYAdgB2AQAAdkB2QHZAdkEAAHaAdoB2gHaBAAB2wHbAdsB2wQAAdwB3AHcAdwEAAHdAd0B3QHdBAAB3gHeAd4B3gQAAd8B3wHfAd8EAAHgAeAB4AHgBAAB4QHhAeEB4QQAAeIB4gHiAeIEAAHjAeMB4wHjBAAB5AHkAeQB5AQAAeUB5QHlAeUEAAHmAeYB5gHmBAAB5wHnAecB5wQAAegB6AHoAegEAAHpAekB6QHpBAAB6gHqAeoB6gQAAesB6wHrAesEAAHsAewB7AHsBAAB7QHtAe0B7QQAAe4B7gHuAe4EAAHvAe8B7wHvBAAB8AHwAfAB8AQAAfEB8QHxAfEEAAHyAfIB8gHyBAAB8wHzAfMB8wQAAfQB9AH0AfQEAAH1AfUB9QH1BAAB9gH2AfYB9gQAAfcB9wH3AfcEAAH4AfgB+AH4BAAB+QH5AfkB+QQAAfoB+gH6AfoEAAH7AfsB+wH7BAAB/AH8AfwB/AQAAf0B/QH9Af0EAAH+Af4B/gH+BAAB/wH/Af8B/wQAAgACAAIAAgAAAHRW3Eg="; + let expected_hash = "2890a8caa438b2982b125c7ba6316674874a246c565134f8fe0982ff048c1a23"; + typical_boc_test(entry, expected_hash); +} + +fn typical_boc_test(entry: &str, expected_hash: &str) { + let bytes_entry = BASE64_STANDARD.decode(entry).unwrap(); + let boc = BagOfCells::parse(&bytes_entry).unwrap(); + assert_eq!(boc.roots.len(), 1); + let hash = hex::encode(boc.roots[0].cell_hash()); + assert_eq!(hash, expected_hash); + let bytes_entry_again = boc.serialize(false).unwrap(); + let boc_again = BagOfCells::parse(&bytes_entry_again).unwrap(); + let hash_again = hex::encode(boc_again.roots[0].cell_hash()); + assert_eq!(hash_again, expected_hash); +} + +#[test] +fn library_cell() { + let entry = "b5ee9c7201010201002d00010eff0088d0ed1ed801084202e70a306c00272796243f569ce0c928ea4cfc9f1b65c5b0066e382159f5e80df5"; + let user_address = "UQAO9JsDEbOjnb8AZRyxNHiODjVeAvgR2n03T0utYgkpx-K0" + .parse() + .unwrap(); + let pool_address = + TonAddress::from_str("EQDMk-2P8ziShAYGcnYq-z_U33zA_Ynt88iav4PwkSGRru2B").unwrap(); + let boc_bytes = hex::decode(entry).unwrap(); + let boc = BagOfCells::parse(&boc_bytes).unwrap(); + let data_cell = CellBuilder::new() + .store_address(&user_address) + .unwrap() + .store_address(&pool_address) + .unwrap() + .store_coins(&BigUint::zero()) + .unwrap() + .store_coins(&BigUint::zero()) + .unwrap() + .build() + .unwrap(); + let state_init_hash = + StateInit::create_account_id(boc.single_root().unwrap(), &Arc::new(data_cell)).unwrap(); + let addr = TonAddress::new(0, &state_init_hash); + assert_eq!( + addr, + TonAddress::from_str("EQBWxdw3leOoaHqcK3ATf0T7ae5M8XS6jiP_Din4mh7o7gj2").unwrap() + ); +} From 19d6c8a679414e90d90d9b4b13f1d6044c382ab7 Mon Sep 17 00:00:00 2001 From: Andrey Vasiliev Date: Thu, 4 Jul 2024 13:02:57 +0000 Subject: [PATCH 12/29] Resolve "Implement exotic cells support" --- src/message/jetton/jetton_transfer.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/message/jetton/jetton_transfer.rs b/src/message/jetton/jetton_transfer.rs index 7bc5e545..cf8fb27e 100644 --- a/src/message/jetton/jetton_transfer.rs +++ b/src/message/jetton/jetton_transfer.rs @@ -213,11 +213,9 @@ mod tests { .unwrap(), custom_payload: None, forward_ton_amount: BigUint::from(215000000u64), - forward_payload: Some(Arc::new(Cell { - data: hex::decode(TRANSFER_PAYLOAD).unwrap(), - bit_len: 862, - references: vec![], - })), + forward_payload: Some(Arc::new( + Cell::new(hex::decode(TRANSFER_PAYLOAD).unwrap(), 862, vec![], false).unwrap(), + )), }; assert_eq!(expected_jetton_transfer_msg, result_jetton_transfer_msg); @@ -235,11 +233,9 @@ mod tests { .unwrap(), custom_payload: None, forward_ton_amount: BigUint::from(215000000u64), - forward_payload: Some(Arc::new(Cell { - data: hex::decode(TRANSFER_PAYLOAD).unwrap(), - bit_len: 862, - references: vec![], - })), + forward_payload: Some(Arc::new( + Cell::new(hex::decode(TRANSFER_PAYLOAD).unwrap(), 862, vec![], false).unwrap(), + )), }; let result_cell = assert_ok!(jetton_transfer_msg.build()); From 40f29ba4f209f1a5d6ba6fe414aa745dc5935b52 Mon Sep 17 00:00:00 2001 From: Slavik Baranov Date: Mon, 8 Jul 2024 19:20:13 +0200 Subject: [PATCH 13/29] Impl #156: Remove AsRef --- src/message/jetton/burn.rs | 7 ++----- src/message/jetton/jetton_transfer.rs | 18 ++++++------------ src/message/jetton/transfer_notification.rs | 11 ++++------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/message/jetton/burn.rs b/src/message/jetton/burn.rs index bdba54cf..4580f499 100644 --- a/src/message/jetton/burn.rs +++ b/src/message/jetton/burn.rs @@ -44,11 +44,8 @@ impl JettonBurnMessage { self } - pub fn with_custom_payload(&mut self, custom_payload: T) -> &mut Self - where - T: AsRef, - { - self.custom_payload = Some(custom_payload.as_ref().clone()); + pub fn with_custom_payload(&mut self, custom_payload: &ArcCell) -> &mut Self { + self.custom_payload = Some(custom_payload.clone()); self } diff --git a/src/message/jetton/jetton_transfer.rs b/src/message/jetton/jetton_transfer.rs index cf8fb27e..59107520 100644 --- a/src/message/jetton/jetton_transfer.rs +++ b/src/message/jetton/jetton_transfer.rs @@ -56,24 +56,18 @@ impl JettonTransferMessage { self } - pub fn with_custom_payload(&mut self, custom_payload: T) -> &mut Self - where - T: AsRef, - { - self.custom_payload = Some(custom_payload.as_ref().clone()); + pub fn with_custom_payload(&mut self, custom_payload: &ArcCell) -> &mut Self { + self.custom_payload = Some(custom_payload.clone()); self } - pub fn with_forward_payload( + pub fn with_forward_payload( &mut self, forward_ton_amount: &BigUint, - forward_payload: T, - ) -> &mut Self - where - T: AsRef, - { + forward_payload: &ArcCell, + ) -> &mut Self { self.forward_ton_amount.clone_from(forward_ton_amount); - self.forward_payload = Some(forward_payload.as_ref().clone()); + self.forward_payload = Some(forward_payload.clone()); self } diff --git a/src/message/jetton/transfer_notification.rs b/src/message/jetton/transfer_notification.rs index 2786e1c7..0c144d00 100644 --- a/src/message/jetton/transfer_notification.rs +++ b/src/message/jetton/transfer_notification.rs @@ -44,16 +44,13 @@ impl JettonTransferNotificationMessage { self } - pub fn with_forward_payload( + pub fn with_forward_payload( &mut self, forward_ton_amount: &BigUint, - forward_payload: T, - ) -> &mut Self - where - T: AsRef, - { + forward_payload: &ArcCell, + ) -> &mut Self { self.forward_ton_amount.clone_from(forward_ton_amount); - self.forward_payload = Some(forward_payload.as_ref().clone()); + self.forward_payload = Some(forward_payload.clone()); self } From 7788f64b2b807b10efb8765e84c4c0445a64e6fa Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Thu, 11 Jul 2024 09:08:06 +0000 Subject: [PATCH 14/29] NI: Downstream 0.16 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e47ccdf8..508bccdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.16.0-dev" +version = "0.16.1-dev" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" From 351769d3475e90b58d7dc1558c9d6ee04b2d9263 Mon Sep 17 00:00:00 2001 From: sathembite Date: Thu, 11 Jul 2024 12:30:53 +0200 Subject: [PATCH 15/29] NI: fix transfer_notification parser/builder, added tests --- src/cell.rs | 5 +- src/cell/cell_type.rs | 10 +- src/cell/raw_boc_from_boc.rs | 3 +- src/message/jetton/transfer_notification.rs | 113 ++++++++++++++++---- tests/boc.rs | 7 +- 5 files changed, 109 insertions(+), 29 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index 55797644..486f2576 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -6,8 +6,6 @@ use std::ops::Deref; use std::sync::Arc; use std::{fmt, io}; -use crate::cell::cell_type::CellType; -use crate::cell::level_mask::LevelMask; pub use bag_of_cells::*; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; @@ -26,6 +24,9 @@ pub use slice::*; pub use state_init::*; pub use util::*; +use crate::cell::cell_type::CellType; +use crate::cell::level_mask::LevelMask; + mod bag_of_cells; mod bit_string; mod builder; diff --git a/src/cell/cell_type.rs b/src/cell/cell_type.rs index dcc16a16..1364344c 100644 --- a/src/cell/cell_type.rs +++ b/src/cell/cell_type.rs @@ -1,11 +1,13 @@ +use std::cmp::PartialEq; +use std::io; +use std::io::Cursor; + +use bitstream_io::{BigEndian, ByteRead, ByteReader}; + use crate::cell::level_mask::LevelMask; use crate::cell::{ ArcCell, Cell, CellHash, MapTonCellError, TonCellError, DEPTH_BYTES, HASH_BYTES, MAX_LEVEL, }; -use bitstream_io::{BigEndian, ByteRead, ByteReader}; -use std::cmp::PartialEq; -use std::io; -use std::io::Cursor; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) enum CellType { diff --git a/src/cell/raw_boc_from_boc.rs b/src/cell/raw_boc_from_boc.rs index 5b5bb939..fed5795d 100644 --- a/src/cell/raw_boc_from_boc.rs +++ b/src/cell/raw_boc_from_boc.rs @@ -1,8 +1,9 @@ -use crate::cell::{ArcCell, BagOfCells, Cell, CellHash, RawBagOfCells, RawCell, TonCellError}; use std::cell::RefCell; use std::collections::HashMap; use std::sync::Arc; +use crate::cell::{ArcCell, BagOfCells, Cell, CellHash, RawBagOfCells, RawCell, TonCellError}; + #[derive(Debug, Clone)] struct IndexedCell { index: usize, diff --git a/src/message/jetton/transfer_notification.rs b/src/message/jetton/transfer_notification.rs index 0c144d00..faeedbd3 100644 --- a/src/message/jetton/transfer_notification.rs +++ b/src/message/jetton/transfer_notification.rs @@ -1,10 +1,9 @@ use num_bigint::BigUint; -use num_traits::Zero; -use super::{JETTON_TRANSFER, JETTON_TRANSFER_NOTIFICATION}; +use super::JETTON_TRANSFER_NOTIFICATION; use crate::address::TonAddress; use crate::cell::{ArcCell, Cell, CellBuilder}; -use crate::message::{InvalidMessage, RawMessageUtils, TonMessageError, ZERO_COINS}; +use crate::message::{InvalidMessage, RawMessageUtils, TonMessageError}; use crate::tl::RawMessage; /// Creates a body for jetton transfer notification according to TL-B schema: @@ -22,8 +21,6 @@ pub struct JettonTransferNotificationMessage { pub amount: BigUint, /// is address of the previous owner of transferred jettons. pub sender: TonAddress, - /// the amount of nanotons to be sent to the destination address. - pub forward_ton_amount: BigUint, /// optional custom data that should be sent to the destination address. pub forward_payload: Option, } @@ -34,7 +31,6 @@ impl JettonTransferNotificationMessage { query_id: 0, amount: amount.clone(), sender: sender.clone(), - forward_ton_amount: ZERO_COINS.clone(), forward_payload: None, } } @@ -44,26 +40,17 @@ impl JettonTransferNotificationMessage { self } - pub fn with_forward_payload( - &mut self, - forward_ton_amount: &BigUint, - forward_payload: &ArcCell, - ) -> &mut Self { - self.forward_ton_amount.clone_from(forward_ton_amount); + pub fn with_forward_payload(&mut self, forward_payload: &ArcCell) -> &mut Self { self.forward_payload = Some(forward_payload.clone()); self } pub fn build(&self) -> Result { - if self.forward_ton_amount.is_zero() && self.forward_payload.is_some() { - return Err(TonMessageError::ForwardTonAmountIsNegative); - } let mut message = CellBuilder::new(); message.store_u32(32, JETTON_TRANSFER_NOTIFICATION)?; message.store_u64(64, self.query_id)?; message.store_coins(&self.amount)?; message.store_address(&self.sender)?; - message.store_coins(&self.forward_ton_amount)?; if let Some(fp) = self.forward_payload.as_ref() { message.store_bit(true)?; message.store_reference(fp)?; @@ -79,7 +66,7 @@ impl JettonTransferNotificationMessage { let opcode: u32 = parser.load_u32(32)?; let query_id = parser.load_u64(64)?; - if opcode != JETTON_TRANSFER { + if opcode != JETTON_TRANSFER_NOTIFICATION { let invalid = InvalidMessage { opcode: Some(opcode), query_id: Some(query_id), @@ -92,7 +79,6 @@ impl JettonTransferNotificationMessage { } let amount = parser.load_coins()?; let sender = parser.load_address()?; - let forward_ton_amount = parser.load_coins()?; let has_forward_payload = parser.load_bit()?; parser.ensure_empty()?; @@ -108,10 +94,99 @@ impl JettonTransferNotificationMessage { query_id, amount, sender, - forward_ton_amount, forward_payload, }; Ok(result) } } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use std::sync::Arc; + + use num_bigint::BigUint; + use tokio_test::assert_ok; + + use crate::address::TonAddress; + use crate::cell::{BagOfCells, Cell}; + use crate::message::JettonTransferNotificationMessage; + use crate::tl::{AccountAddress, MsgData, RawMessage}; + + // message origin: https://tonviewer.com/transaction/1b19a1ea5fdefd93ffc6051f67a8e89e02a5ead168a70c6ccd38f6d2e3f0e1d5 + const JETTON_TRANSFER_NOTIFICATION_MSG: &str = "b5ee9c720101020100a60001647362d09c000000d2c7ceef23401312d008003be20895401cd8539741eb7815d5e63b3429014018d7e5f7800de16a984f27730100dd25938561800f2465b65c76b1b562f32423676970b431319419d5f45ffd2eeb2155ce6ab7eacc78ee0250ef0300077c4112a8039b0a72e83d6f02babcc766852028031afcbef001bc2d5309e4ee700257a672371a90e149b7d25864dbfd44827cc1e8a30df1b1e0c4338502ade2ad96"; + const TRANSFER_NOTIFICATION_PAYLOAD: &str = "25938561800f2465b65c76b1b562f32423676970b431319419d5f45ffd2eeb2155ce6ab7eacc78ee0250ef0300077c4112a8039b0a72e83d6f02babcc766852028031afcbef001bc2d5309e4ee700257a672371a90e149b7d25864dbfd44827cc1e8a30df1b1e0c4338502ade2ad94"; + + #[test] + fn test_jetton_transfer_notification_parser() { + let msg_data = hex::decode(JETTON_TRANSFER_NOTIFICATION_MSG).unwrap(); + + let raw_msg = RawMessage { + source: AccountAddress { + account_address: String::new(), + }, + destination: AccountAddress { + account_address: String::new(), + }, + value: 0, + fwd_fee: 0, + ihr_fee: 0, + created_lt: 0, + body_hash: vec![], + msg_data: MsgData::Raw { + body: msg_data.clone(), + init_state: vec![], + }, + }; + + let expected_jetton_transfer_notification_msg = JettonTransferNotificationMessage { + query_id: 905295359779, + amount: BigUint::from(20000000u64), + sender: TonAddress::from_str("EQAd8QRKoA5sKcug9bwK6vMdmhSAoAxr8vvABvC1TCeTude5") + .unwrap(), + forward_payload: Some(Arc::new( + Cell::new( + hex::decode(TRANSFER_NOTIFICATION_PAYLOAD).unwrap(), + 886, + vec![], + false, + ) + .unwrap(), + )), + }; + let result_jetton_transfer_msg = + assert_ok!(JettonTransferNotificationMessage::parse(&raw_msg)); + + assert_eq!( + expected_jetton_transfer_notification_msg, + result_jetton_transfer_msg + ) + } + + #[test] + fn test_jetton_transfer_notification_builder() { + let jetton_transfer_notification_msg = JettonTransferNotificationMessage { + query_id: 905295359779, + amount: BigUint::from(20000000u64), + sender: TonAddress::from_str("EQAd8QRKoA5sKcug9bwK6vMdmhSAoAxr8vvABvC1TCeTude5") + .unwrap(), + forward_payload: Some(Arc::new( + Cell::new( + hex::decode(TRANSFER_NOTIFICATION_PAYLOAD).unwrap(), + 886, + vec![], + false, + ) + .unwrap(), + )), + }; + + let result_cell = assert_ok!(jetton_transfer_notification_msg.build()); + + let expected_boc_serialized = hex::decode(JETTON_TRANSFER_NOTIFICATION_MSG).unwrap(); + let result_boc_serialized = BagOfCells::from_root(result_cell).serialize(false).unwrap(); + + assert_eq!(expected_boc_serialized, result_boc_serialized) + } +} diff --git a/tests/boc.rs b/tests/boc.rs index 901ff316..72553187 100644 --- a/tests/boc.rs +++ b/tests/boc.rs @@ -1,9 +1,10 @@ -use base64::prelude::*; -use num_bigint::BigUint; -use num_traits::Zero; use std::collections::HashSet; use std::str::FromStr; use std::sync::Arc; + +use base64::prelude::*; +use num_bigint::BigUint; +use num_traits::Zero; use tonlib::address::TonAddress; use tonlib::cell::{BagOfCells, CellBuilder, StateInit}; From f5345b6ee10fe82e0b10db1f4defcff0180f2f27 Mon Sep 17 00:00:00 2001 From: sathembite Date: Thu, 11 Jul 2024 14:08:08 +0200 Subject: [PATCH 16/29] NI: fix tests --- src/message/jetton/jetton_transfer.rs | 30 ++++----------------- src/message/jetton/transfer_notification.rs | 30 ++++----------------- 2 files changed, 10 insertions(+), 50 deletions(-) diff --git a/src/message/jetton/jetton_transfer.rs b/src/message/jetton/jetton_transfer.rs index 59107520..e310daf5 100644 --- a/src/message/jetton/jetton_transfer.rs +++ b/src/message/jetton/jetton_transfer.rs @@ -4,8 +4,7 @@ use num_traits::Zero; use super::JETTON_TRANSFER; use crate::address::TonAddress; use crate::cell::{ArcCell, Cell, CellBuilder}; -use crate::message::{InvalidMessage, RawMessageUtils, TonMessageError, ZERO_COINS}; -use crate::tl::RawMessage; +use crate::message::{InvalidMessage, TonMessageError, ZERO_COINS}; /// Creates a body for jetton transfer according to TL-B schema: /// @@ -98,8 +97,7 @@ impl JettonTransferMessage { Ok(message.build()?) } - pub fn parse(msg: &RawMessage) -> Result { - let cell = (&msg).get_raw_data_cell()?; + pub fn parse(cell: &Cell) -> Result { let mut parser = cell.parser(); let opcode: u32 = parser.load_u32(32)?; @@ -167,34 +165,16 @@ mod tests { use crate::address::TonAddress; use crate::cell::{BagOfCells, Cell}; use crate::message::JettonTransferMessage; - use crate::tl::{AccountAddress, MsgData, RawMessage}; // message origin: https://tonviewer.com/transaction/2e250e3c9367d8092f15e09fb3c3d750749187c2a528a616bf0e88e5f36ca3f4 const JETTON_TRANSFER_MSG : &str="b5ee9c720101020100a800016d0f8a7ea5001f5512dab844d643b9aca00800ef3b9902a271b2a01c8938a523cfe24e71847aaeb6a620001ed44a77ac0e709c1033428f030100d7259385618009dd924373a9aad41b28cec02da9384d67363af2034fc2a7ccc067e28d4110de86e66deb002365dfa32dfd419308ebdf35e0f6ba7c42534bbb5dab5e89e28ea3e0455cc2d2f00257a672371a90e149b7d25864dbfd44827cc1e8a30df1b1e0c4338502ade2ad96"; const TRANSFER_PAYLOAD: &str = "259385618009DD924373A9AAD41B28CEC02DA9384D67363AF2034FC2A7CCC067E28D4110DE86E66DEB002365DFA32DFD419308EBDF35E0F6BA7C42534BBB5DAB5E89E28EA3E0455CC2D2F00257A672371A90E149B7D25864DBFD44827CC1E8A30DF1B1E0C4338502ADE2AD94"; #[test] fn test_jetton_transfer_parser() { - let msg_data = hex::decode(JETTON_TRANSFER_MSG).unwrap(); - - let raw_msg = RawMessage { - source: AccountAddress { - account_address: String::new(), - }, - destination: AccountAddress { - account_address: String::new(), - }, - value: 0, - fwd_fee: 0, - ihr_fee: 0, - created_lt: 0, - body_hash: vec![], - msg_data: MsgData::Raw { - body: msg_data.clone(), - init_state: vec![], - }, - }; + let boc = BagOfCells::parse_hex(JETTON_TRANSFER_MSG).unwrap(); + let cell = boc.single_root().unwrap(); - let result_jetton_transfer_msg = assert_ok!(JettonTransferMessage::parse(&raw_msg)); + let result_jetton_transfer_msg = assert_ok!(JettonTransferMessage::parse(&cell)); let expected_jetton_transfer_msg = JettonTransferMessage { query_id: 8819263745311958, diff --git a/src/message/jetton/transfer_notification.rs b/src/message/jetton/transfer_notification.rs index faeedbd3..293becf9 100644 --- a/src/message/jetton/transfer_notification.rs +++ b/src/message/jetton/transfer_notification.rs @@ -3,8 +3,7 @@ use num_bigint::BigUint; use super::JETTON_TRANSFER_NOTIFICATION; use crate::address::TonAddress; use crate::cell::{ArcCell, Cell, CellBuilder}; -use crate::message::{InvalidMessage, RawMessageUtils, TonMessageError}; -use crate::tl::RawMessage; +use crate::message::{InvalidMessage, TonMessageError}; /// Creates a body for jetton transfer notification according to TL-B schema: /// @@ -60,8 +59,7 @@ impl JettonTransferNotificationMessage { Ok(message.build()?) } - pub fn parse(msg: &RawMessage) -> Result { - let cell = (&msg).get_raw_data_cell()?; + pub fn parse(cell: &Cell) -> Result { let mut parser = cell.parser(); let opcode: u32 = parser.load_u32(32)?; @@ -112,7 +110,6 @@ mod tests { use crate::address::TonAddress; use crate::cell::{BagOfCells, Cell}; use crate::message::JettonTransferNotificationMessage; - use crate::tl::{AccountAddress, MsgData, RawMessage}; // message origin: https://tonviewer.com/transaction/1b19a1ea5fdefd93ffc6051f67a8e89e02a5ead168a70c6ccd38f6d2e3f0e1d5 const JETTON_TRANSFER_NOTIFICATION_MSG: &str = "b5ee9c720101020100a60001647362d09c000000d2c7ceef23401312d008003be20895401cd8539741eb7815d5e63b3429014018d7e5f7800de16a984f27730100dd25938561800f2465b65c76b1b562f32423676970b431319419d5f45ffd2eeb2155ce6ab7eacc78ee0250ef0300077c4112a8039b0a72e83d6f02babcc766852028031afcbef001bc2d5309e4ee700257a672371a90e149b7d25864dbfd44827cc1e8a30df1b1e0c4338502ade2ad96"; @@ -120,25 +117,8 @@ mod tests { #[test] fn test_jetton_transfer_notification_parser() { - let msg_data = hex::decode(JETTON_TRANSFER_NOTIFICATION_MSG).unwrap(); - - let raw_msg = RawMessage { - source: AccountAddress { - account_address: String::new(), - }, - destination: AccountAddress { - account_address: String::new(), - }, - value: 0, - fwd_fee: 0, - ihr_fee: 0, - created_lt: 0, - body_hash: vec![], - msg_data: MsgData::Raw { - body: msg_data.clone(), - init_state: vec![], - }, - }; + let boc = BagOfCells::parse_hex(JETTON_TRANSFER_NOTIFICATION_MSG).unwrap(); + let cell = boc.single_root().unwrap(); let expected_jetton_transfer_notification_msg = JettonTransferNotificationMessage { query_id: 905295359779, @@ -156,7 +136,7 @@ mod tests { )), }; let result_jetton_transfer_msg = - assert_ok!(JettonTransferNotificationMessage::parse(&raw_msg)); + assert_ok!(JettonTransferNotificationMessage::parse(&cell)); assert_eq!( expected_jetton_transfer_notification_msg, From 170251f851eee826216a89dbec5355b17e2c8a7c Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Fri, 12 Jul 2024 08:29:24 +0000 Subject: [PATCH 17/29] NI: downstram v0.17.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 508bccdd..e08b48c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.16.1-dev" +version = "0.17.0" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" From 8379e3e29c1ab955348f2d9c3b56bb57d993a7ce Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Wed, 7 Aug 2024 14:12:57 +0000 Subject: [PATCH 18/29] NI: CellBuilder and CellParser modifications torn out from 0.18.0-dev --- src/cell.rs | 90 ++++- src/cell/builder.rs | 98 +++-- src/cell/cell_type.rs | 43 +- src/cell/error.rs | 9 +- src/cell/level_mask.rs | 2 +- src/cell/parser.rs | 412 ++++++++++++++++++-- src/cell/raw_boc_from_boc.rs | 13 +- src/cell/slice.rs | 28 +- src/cell/state_init.rs | 9 +- src/message/jetton.rs | 2 +- src/message/jetton/burn.rs | 120 ++++-- src/message/jetton/jetton_transfer.rs | 90 ++--- src/message/jetton/transfer_notification.rs | 69 ++-- src/types.rs | 9 + tests/error_test.rs | 8 +- 15 files changed, 741 insertions(+), 261 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index 486f2576..797bd0d2 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::hash::Hash; -use std::io::Cursor; use std::ops::Deref; use std::sync::Arc; use std::{fmt, io}; @@ -10,11 +9,12 @@ pub use bag_of_cells::*; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; use bit_string::*; -use bitstream_io::{BigEndian, BitReader, BitWrite, BitWriter}; +use bitstream_io::{BigEndian, BitWrite, BitWriter}; pub use builder::*; pub use dict_loader::*; pub use error::*; use hmac::digest::Digest; +use lazy_static::lazy_static; use num_bigint::BigUint; use num_traits::{One, ToPrimitive}; pub use parser::*; @@ -26,10 +26,12 @@ pub use util::*; use crate::cell::cell_type::CellType; use crate::cell::level_mask::LevelMask; +use crate::types::{TonHash, DEFAULT_CELL_HASH}; mod bag_of_cells; mod bit_string; mod builder; + mod cell_type; mod dict_loader; mod error; @@ -41,14 +43,17 @@ mod slice; mod state_init; mod util; -const HASH_BYTES: usize = 32; const DEPTH_BYTES: usize = 2; const MAX_LEVEL: u8 = 3; -pub type CellHash = [u8; HASH_BYTES]; pub type ArcCell = Arc; -pub type SnakeFormattedDict = HashMap>; +pub type SnakeFormattedDict = HashMap>; + +lazy_static! { + pub static ref EMPTY_CELL: Cell = Cell::default(); + pub static ref EMPTY_ARC_CELL: ArcCell = Arc::new(Cell::default()); +} #[derive(PartialEq, Eq, Clone, Hash)] pub struct Cell { @@ -57,7 +62,7 @@ pub struct Cell { references: Vec, cell_type: CellType, level_mask: LevelMask, - hashes: [CellHash; 4], + hashes: [TonHash; 4], depths: [u16; 4], } @@ -93,15 +98,7 @@ impl Cell { } pub fn parser(&self) -> CellParser { - let bit_len = self.bit_len; - let cursor = Cursor::new(&self.data); - let bit_reader: BitReader>, BigEndian> = - BitReader::endian(cursor, BigEndian); - - CellParser { - bit_len, - bit_reader, - } + CellParser::new(self.bit_len, &self.data, &self.references) } #[allow(clippy::let_and_return)] @@ -155,11 +152,11 @@ impl Cell { self.depths[level.min(3) as usize] } - pub fn cell_hash(&self) -> CellHash { + pub fn cell_hash(&self) -> TonHash { self.get_hash(MAX_LEVEL) } - pub fn get_hash(&self, level: u8) -> CellHash { + pub fn get_hash(&self, level: u8) -> TonHash { self.hashes[level.min(3) as usize] } @@ -340,6 +337,8 @@ impl Cell { Arc::new(self) } + /// It is recommended to use CellParser::next_reference() instead + #[deprecated] pub fn expect_reference_count(&self, expected_refs: usize) -> Result<(), TonCellError> { let ref_count = self.references.len(); if ref_count != expected_refs { @@ -360,15 +359,22 @@ impl Debug for Cell { CellType::PrunedBranch | CellType::MerkleProof => 'p', CellType::MerkleUpdate => 'u', }; + + // Our completion tag ONLY shows that the last byte is incomplete + // It does not correspond to real completion tag defined in + // p1.0.2 of https://docs.ton.org/tvm.pdf for details + // Null termination of bit-string defined in that document is omitted for clarity + let completion_tag = if self.bit_len % 8 != 0 { "_" } else { "" }; writeln!( f, - "Cell {}{{ data: [{}], bit_len: {}, references: [\n", + "Cell {}{{ data: [{}{}]\n, bit_len: {}\n, references: [", t, self.data .iter() .map(|&byte| format!("{:02X}", byte)) .collect::>() .join(""), + completion_tag, self.bit_len, )?; @@ -380,7 +386,35 @@ impl Debug for Cell { )?; } - write!(f, "] }}") + write!( + f, + "]\n cell_type: {:?}\n level_mask: {:?}\n hashes {:?}\n depths {:?}\n }}", + self.cell_type, + self.level_mask, + self.hashes + .iter() + .map(|h| h + .iter() + .map(|&byte| format!("{:02X}", byte)) + .collect::>() + .join("")) + .collect::>(), + self.depths + ) + } +} + +impl Default for Cell { + fn default() -> Self { + Self { + data: Default::default(), + bit_len: Default::default(), + references: Default::default(), + cell_type: Default::default(), + level_mask: Default::default(), + hashes: [DEFAULT_CELL_HASH; 4], + depths: Default::default(), + } } } @@ -426,7 +460,7 @@ fn calculate_hashes_and_depths( bit_len: usize, references: &[ArcCell], level_mask: LevelMask, -) -> Result<([CellHash; 4], [u16; 4]), TonCellError> { +) -> Result<([TonHash; 4], [u16; 4]), TonCellError> { let hash_count = if cell_type == CellType::PrunedBranch { 1 } else { @@ -437,7 +471,7 @@ fn calculate_hashes_and_depths( let hash_i_offset = total_hash_count - hash_count; let mut depths: Vec = Vec::with_capacity(hash_count); - let mut hashes: Vec = Vec::with_capacity(hash_count); + let mut hashes: Vec = Vec::with_capacity(hash_count); // Iterate through significant levels for (hash_i, level_i) in (0..=level_mask.level()) @@ -569,3 +603,17 @@ fn write_ref_hashes( Ok(()) } + +#[cfg(test)] +mod test { + use super::Cell; + + #[test] + fn default_cell() { + let result = Cell::default(); + + let expected = Cell::new(vec![], 0, vec![], false).unwrap(); + + assert_eq!(result, expected) + } +} diff --git a/src/cell/builder.rs b/src/cell/builder.rs index 607640bc..288752cf 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -14,6 +14,7 @@ const MAX_CELL_REFERENCES: usize = 4; pub struct CellBuilder { bit_writer: BitWriter, BigEndian>, + bits_to_write: usize, references: Vec, is_cell_exotic: bool, } @@ -23,6 +24,7 @@ impl CellBuilder { let bit_writer = BitWriter::endian(Vec::new(), BigEndian); CellBuilder { bit_writer, + bits_to_write: 0, references: Vec::new(), is_cell_exotic: false, } @@ -34,6 +36,7 @@ impl CellBuilder { pub fn store_bit(&mut self, val: bool) -> Result<&mut Self, TonCellError> { self.bit_writer.write_bit(val).map_cell_builder_error()?; + self.bits_to_write += 1; Ok(self) } @@ -41,6 +44,7 @@ impl CellBuilder { self.bit_writer .write(bit_len as u32, val) .map_cell_builder_error()?; + self.bits_to_write += bit_len; Ok(self) } @@ -48,6 +52,7 @@ impl CellBuilder { self.bit_writer .write(bit_len as u32, val) .map_cell_builder_error()?; + self.bits_to_write += bit_len; Ok(self) } @@ -55,6 +60,7 @@ impl CellBuilder { self.bit_writer .write(bit_len as u32, val) .map_cell_builder_error()?; + self.bits_to_write += bit_len; Ok(self) } @@ -62,6 +68,7 @@ impl CellBuilder { self.bit_writer .write(bit_len as u32, val) .map_cell_builder_error()?; + self.bits_to_write += bit_len; Ok(self) } @@ -69,6 +76,7 @@ impl CellBuilder { self.bit_writer .write(bit_len as u32, val) .map_cell_builder_error()?; + self.bits_to_write += bit_len; Ok(self) } @@ -76,6 +84,7 @@ impl CellBuilder { self.bit_writer .write(bit_len as u32, val) .map_cell_builder_error()?; + self.bits_to_write += bit_len; Ok(self) } @@ -253,6 +262,41 @@ impl CellBuilder { Ok(self) } + // https://docs.ton.org/develop/data-formats/tl-b-types#either + pub fn store_either_cell_or_cell_ref( + &mut self, + cell: &ArcCell, + ) -> Result<&mut Self, TonCellError> { + if cell.bit_len() < self.remaining_bits() { + self.store_bit(false)?; + self.store_cell(cell)?; + } else { + self.store_bit(true)?; + self.store_reference(cell)?; + } + + Ok(self) + } + + // https://docs.ton.org/develop/data-formats/tl-b-types#maybe + pub fn store_maybe_cell_ref( + &mut self, + maybe_cell: &Option, + ) -> Result<&mut Self, TonCellError> { + if let Some(cell) = maybe_cell { + self.store_bit(true)?; + self.store_reference(cell)?; + } else { + self.store_bit(false)?; + } + + Ok(self) + } + + pub fn remaining_bits(&self) -> usize { + MAX_CELL_BITS - self.bits_to_write + } + pub fn build(&mut self) -> Result { let mut trailing_zeros = 0; while !self.bit_writer.byte_aligned() { @@ -325,14 +369,13 @@ mod tests { use std::str::FromStr; use num_bigint::{BigInt, BigUint, Sign}; - use tokio_test::{assert_err, assert_ok}; use crate::address::TonAddress; use crate::cell::builder::extend_and_invert_bits; - use crate::cell::CellBuilder; + use crate::cell::{CellBuilder, TonCellError}; #[test] - fn test_extend_and_invert_bits() -> anyhow::Result<()> { + fn test_extend_and_invert_bits() -> Result<(), TonCellError> { let a = BigUint::from(1u8); let b = extend_and_invert_bits(8, &a)?; println!("a: {:0x}", a); @@ -351,12 +394,12 @@ mod tests { let b = extend_and_invert_bits(9, &a)?; assert_eq!(b, BigUint::from_slice(&[0x1ffu32])); - assert_err!(extend_and_invert_bits(3, &BigUint::from(10u32))); + assert!(extend_and_invert_bits(3, &BigUint::from(10u32)).is_err()); Ok(()) } #[test] - fn write_bit() -> anyhow::Result<()> { + fn write_bit() -> Result<(), TonCellError> { let mut writer = CellBuilder::new(); let cell = writer.store_bit(true)?.build()?; assert_eq!(cell.data, [0b1000_0000]); @@ -368,7 +411,7 @@ mod tests { } #[test] - fn write_u8() -> anyhow::Result<()> { + fn write_u8() -> Result<(), TonCellError> { let value = 234u8; let mut writer = CellBuilder::new(); let cell = writer.store_u8(8, value)?.build()?; @@ -381,7 +424,7 @@ mod tests { } #[test] - fn write_u32() -> anyhow::Result<()> { + fn write_u32() -> Result<(), TonCellError> { let value = 0xFAD45AADu32; let mut writer = CellBuilder::new(); let cell = writer.store_u32(32, value)?.build()?; @@ -394,7 +437,7 @@ mod tests { } #[test] - fn write_u64() -> anyhow::Result<()> { + fn write_u64() -> Result<(), TonCellError> { let value = 0xFAD45AADAA12FF45; let mut writer = CellBuilder::new(); let cell = writer.store_u64(64, value)?.build()?; @@ -407,7 +450,7 @@ mod tests { } #[test] - fn write_slice() -> anyhow::Result<()> { + fn write_slice() -> Result<(), TonCellError> { let value = [0xFA, 0xD4, 0x5A, 0xAD, 0xAA, 0x12, 0xFF, 0x45]; let mut writer = CellBuilder::new(); let cell = writer.store_slice(&value)?.build()?; @@ -420,7 +463,7 @@ mod tests { } #[test] - fn write_str() -> anyhow::Result<()> { + fn write_str() -> Result<(), TonCellError> { let texts = ["hello", "Русский текст", "中华人民共和国", "\u{263A}😃"]; for text in texts { let mut writer = CellBuilder::new(); @@ -437,8 +480,9 @@ mod tests { } #[test] - fn write_address() -> anyhow::Result<()> { - let addr = TonAddress::from_base64_url("EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR")?; + fn write_address() -> Result<(), TonCellError> { + let addr = TonAddress::from_base64_url("EQDk2VTvn04SUKJrW7rXahzdF8_Qi6utb0wj43InCu9vdjrR") + .unwrap(); let mut writer = CellBuilder::new(); let cell = writer.store_address(&addr)?.build()?; @@ -457,10 +501,10 @@ mod tests { } #[test] - fn write_big_int() -> anyhow::Result<()> { - let value = BigInt::from_str("3")?; + fn write_big_int() -> Result<(), TonCellError> { + let value = BigInt::from_str("3").unwrap(); let mut writer = CellBuilder::new(); - assert_ok!(writer.store_int(33, &value)); + writer.store_int(33, &value)?; let cell = writer.build()?; println!("cell: {:?}", cell); let written = BigInt::from_bytes_be(Sign::Plus, &cell.data); @@ -469,17 +513,18 @@ mod tests { // 256 bits (+ sign) let value = BigInt::from_str( "97887266651548624282413032824435501549503168134499591480902563623927645013201", - )?; + ) + .unwrap(); let mut writer = CellBuilder::new(); - assert_ok!(writer.store_int(257, &value)); + writer.store_int(257, &value)?; let cell = writer.build()?; println!("cell: {:?}", cell); let written = BigInt::from_bytes_be(Sign::Plus, &cell.data); assert_eq!(written, value); - let value = BigInt::from_str("-5")?; + let value = BigInt::from_str("-5").unwrap(); let mut writer = CellBuilder::new(); - assert_ok!(writer.store_int(5, &value)); + writer.store_int(5, &value)?; let cell = writer.build()?; println!("cell: {:?}", cell); let written = BigInt::from_bytes_be(Sign::Plus, &cell.data[1..]); @@ -489,38 +534,39 @@ mod tests { } #[test] - fn write_load_big_uint() -> anyhow::Result<()> { - let value = BigUint::from_str("3")?; + fn write_load_big_uint() -> Result<(), TonCellError> { + let value = BigUint::from_str("3").unwrap(); let mut writer = CellBuilder::new(); assert!(writer.store_uint(1, &value).is_err()); let bits_for_tests = [256, 128, 64, 8]; for bits_num in bits_for_tests.iter() { - assert_ok!(writer.store_uint(*bits_num, &value)); + writer.store_uint(*bits_num, &value)?; } let cell = writer.build()?; println!("cell: {:?}", cell); let mut cell_parser = cell.parser(); for bits_num in bits_for_tests.iter() { - let written_value = assert_ok!(cell_parser.load_uint(*bits_num)); + let written_value = cell_parser.load_uint(*bits_num)?; assert_eq!(written_value, value); } // 256 bit let value = BigUint::from_str( "97887266651548624282413032824435501549503168134499591480902563623927645013201", - )?; + ) + .unwrap(); let mut writer = CellBuilder::new(); assert!(writer.store_uint(255, &value).is_err()); let bits_for_tests = [496, 264, 256]; for bits_num in bits_for_tests.iter() { - assert_ok!(writer.store_uint(*bits_num, &value)); + writer.store_uint(*bits_num, &value)?; } let cell = writer.build()?; let mut cell_parser = cell.parser(); println!("cell: {:?}", cell); for bits_num in bits_for_tests.iter() { - let written_value = assert_ok!(cell_parser.load_uint(*bits_num)); + let written_value = cell_parser.load_uint(*bits_num)?; assert_eq!(written_value, value); } diff --git a/src/cell/cell_type.rs b/src/cell/cell_type.rs index 1364344c..a7159062 100644 --- a/src/cell/cell_type.rs +++ b/src/cell/cell_type.rs @@ -5,12 +5,12 @@ use std::io::Cursor; use bitstream_io::{BigEndian, ByteRead, ByteReader}; use crate::cell::level_mask::LevelMask; -use crate::cell::{ - ArcCell, Cell, CellHash, MapTonCellError, TonCellError, DEPTH_BYTES, HASH_BYTES, MAX_LEVEL, -}; +use crate::cell::{ArcCell, Cell, MapTonCellError, TonCellError, DEPTH_BYTES, MAX_LEVEL}; +use crate::types::{TonHash, TON_HASH_BYTES, ZERO_HASH}; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub(crate) enum CellType { + #[default] Ordinary, PrunedBranch, Library, @@ -20,7 +20,7 @@ pub(crate) enum CellType { #[derive(Debug, Clone)] struct Pruned { - hash: CellHash, + hash: TonHash, depth: u16, } @@ -96,13 +96,13 @@ impl CellType { pub(crate) fn resolve_hashes_and_depths( &self, - hashes: Vec, + hashes: Vec, depths: Vec, data: &[u8], bit_len: usize, level_mask: LevelMask, - ) -> Result<([CellHash; 4], [u16; 4]), TonCellError> { - let mut resolved_hashes = [[0; 32]; 4]; + ) -> Result<([TonHash; 4], [u16; 4]), TonCellError> { + let mut resolved_hashes = [ZERO_HASH; 4]; let mut resolved_depths = [0; 4]; for i in 0..4 { @@ -161,7 +161,7 @@ impl CellType { } let expected_size: usize = - (2 + level_mask.apply(level - 1).hash_count() * (HASH_BYTES + DEPTH_BYTES)) * 8; + (2 + level_mask.apply(level - 1).hash_count() * (TON_HASH_BYTES + DEPTH_BYTES)) * 8; if bit_len != expected_size { return Err(TonCellError::InvalidExoticCellData(format!( @@ -174,7 +174,7 @@ impl CellType { } fn validate_library(&self, bit_len: usize) -> Result<(), TonCellError> { - const SIZE: usize = (1 + HASH_BYTES) * 8; + const SIZE: usize = (1 + TON_HASH_BYTES) * 8; if bit_len != SIZE { return Err(TonCellError::InvalidExoticCellData(format!( @@ -193,7 +193,7 @@ impl CellType { ) -> Result<(), TonCellError> { let references = references.as_ref(); // type + hash + depth - const SIZE: usize = (1 + HASH_BYTES + DEPTH_BYTES) * 8; + const SIZE: usize = (1 + TON_HASH_BYTES + DEPTH_BYTES) * 8; if bit_len != SIZE { return Err(TonCellError::InvalidExoticCellData(format!( @@ -208,13 +208,14 @@ impl CellType { ))); } - let proof_hash: [u8; HASH_BYTES] = data[1..(1 + HASH_BYTES)].try_into().map_err(|err| { - TonCellError::InvalidExoticCellData(format!( - "Can't get proof hash bytes from cell data, {}", - err - )) - })?; - let proof_depth_bytes = data[(1 + HASH_BYTES)..(1 + HASH_BYTES + 2)] + let proof_hash: [u8; TON_HASH_BYTES] = + data[1..(1 + TON_HASH_BYTES)].try_into().map_err(|err| { + TonCellError::InvalidExoticCellData(format!( + "Can't get proof hash bytes from cell data, {}", + err + )) + })?; + let proof_depth_bytes = data[(1 + TON_HASH_BYTES)..(1 + TON_HASH_BYTES + 2)] .try_into() .map_err(|err| { TonCellError::InvalidExoticCellData(format!( @@ -264,13 +265,13 @@ impl CellType { ))); } - let proof_hash1: [u8; 32] = data[1..33].try_into().map_err(|err| { + let proof_hash1: TonHash = data[1..33].try_into().map_err(|err| { TonCellError::InvalidExoticCellData(format!( "Can't get proof hash bytes 1 from cell data, {}", err )) })?; - let proof_hash2: [u8; 32] = data[33..65].try_into().map_err(|err| { + let proof_hash2: TonHash = data[33..65].try_into().map_err(|err| { TonCellError::InvalidExoticCellData(format!( "Can't get proof hash bytes 2 from cell data, {}", err @@ -354,7 +355,7 @@ impl CellType { let level = level_mask.level() as usize; let hashes = (0..level) - .map(|_| reader.read::()) + .map(|_| reader.read::()) .collect::, _>>()?; let depths = (0..level) .map(|_| reader.read::()) diff --git a/src/cell/error.rs b/src/cell/error.rs index 3ba40acb..c7776476 100644 --- a/src/cell/error.rs +++ b/src/cell/error.rs @@ -29,8 +29,13 @@ pub enum TonCellError { #[error("Bad data ({0})")] InvalidExoticCellData(String), - #[error("Non-empty reader (Remaining bits: {0})")] - NonEmptyReader(usize), + #[error( + "Non-empty reader (Remaining bits: {remaining_bits}, Remaining refs: {remaining_refs})" + )] + NonEmptyReader { + remaining_bits: usize, + remaining_refs: usize, + }, } pub trait MapTonCellError diff --git a/src/cell/level_mask.rs b/src/cell/level_mask.rs index e4af2e91..e4f7f4c3 100644 --- a/src/cell/level_mask.rs +++ b/src/cell/level_mask.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct LevelMask { mask: u32, } diff --git a/src/cell/parser.rs b/src/cell/parser.rs index da79c2dd..02195b0f 100644 --- a/src/cell/parser.rs +++ b/src/cell/parser.rs @@ -1,19 +1,34 @@ use std::io::Cursor; +use std::sync::Arc; -use bitstream_io::{BigEndian, BitRead, BitReader}; +use bitstream_io::{BigEndian, BitRead, BitReader, Numeric}; use num_bigint::{BigInt, BigUint, Sign}; use num_traits::identities::Zero; +use super::{ArcCell, Cell}; use crate::address::TonAddress; use crate::cell::util::*; use crate::cell::{MapTonCellError, TonCellError}; pub struct CellParser<'a> { pub(crate) bit_len: usize, - pub(crate) bit_reader: BitReader>, BigEndian>, + pub(crate) bit_reader: BitReader, BigEndian>, + pub(crate) references: &'a [ArcCell], + next_ref: usize, } -impl CellParser<'_> { +impl<'a> CellParser<'a> { + pub fn new(bit_len: usize, data: &'a [u8], references: &'a [ArcCell]) -> Self { + let cursor = Cursor::new(data); + let bit_reader = BitReader::endian(cursor, BigEndian); + CellParser { + bit_len, + bit_reader, + references, + next_ref: 0, + } + } + pub fn remaining_bits(&mut self) -> usize { let pos = self.bit_reader.position_in_bits().unwrap_or_default() as usize; if self.bit_len > pos { @@ -29,58 +44,44 @@ impl CellParser<'_> { } pub fn load_bit(&mut self) -> Result { + self.ensure_enough_bits(1)?; self.bit_reader.read_bit().map_cell_parser_error() } pub fn load_u8(&mut self, bit_len: usize) -> Result { - self.bit_reader - .read::(bit_len as u32) - .map_cell_parser_error() + self.load_number(bit_len) } pub fn load_i8(&mut self, bit_len: usize) -> Result { - self.bit_reader - .read::(bit_len as u32) - .map_cell_parser_error() + self.load_number(bit_len) } pub fn load_u16(&mut self, bit_len: usize) -> Result { - self.bit_reader - .read::(bit_len as u32) - .map_cell_parser_error() + self.load_number(bit_len) } pub fn load_i16(&mut self, bit_len: usize) -> Result { - self.bit_reader - .read::(bit_len as u32) - .map_cell_parser_error() + self.load_number(bit_len) } pub fn load_u32(&mut self, bit_len: usize) -> Result { - self.bit_reader - .read::(bit_len as u32) - .map_cell_parser_error() + self.load_number(bit_len) } pub fn load_i32(&mut self, bit_len: usize) -> Result { - self.bit_reader - .read::(bit_len as u32) - .map_cell_parser_error() + self.load_number(bit_len) } pub fn load_u64(&mut self, bit_len: usize) -> Result { - self.bit_reader - .read::(bit_len as u32) - .map_cell_parser_error() + self.load_number(bit_len) } pub fn load_i64(&mut self, bit_len: usize) -> Result { - self.bit_reader - .read::(bit_len as u32) - .map_cell_parser_error() + self.load_number(bit_len) } pub fn load_uint(&mut self, bit_len: usize) -> Result { + self.ensure_enough_bits(bit_len)?; let num_words = (bit_len + 31) / 32; let high_word_bits = if bit_len % 32 == 0 { 32 } else { bit_len % 32 }; let mut words: Vec = vec![0_u32; num_words]; @@ -95,6 +96,7 @@ impl CellParser<'_> { } pub fn load_int(&mut self, bit_len: usize) -> Result { + self.ensure_enough_bits(bit_len)?; let num_words = (bit_len + 31) / 32; let high_word_bits = if bit_len % 32 == 0 { 32 } else { bit_len % 32 }; let mut words: Vec = vec![0_u32; num_words]; @@ -118,6 +120,7 @@ impl CellParser<'_> { } pub fn load_slice(&mut self, slice: &mut [u8]) -> Result<(), TonCellError> { + self.ensure_enough_bits(slice.len() * 8)?; self.bit_reader.read_bytes(slice).map_cell_parser_error() } @@ -132,6 +135,7 @@ impl CellParser<'_> { num_bits: usize, slice: &mut [u8], ) -> Result<(), TonCellError> { + self.ensure_enough_bits(num_bits)?; self.bit_reader.read_bits(num_bits, slice)?; Ok(()) } @@ -148,25 +152,22 @@ impl CellParser<'_> { String::from_utf8(bytes).map_cell_parser_error() } - pub fn load_utf8_lossy(&mut self, num_bytes: usize) -> Result { - let bytes = self.load_bytes(num_bytes)?; - Ok(String::from_utf8_lossy(&bytes).to_string()) - } - pub fn load_coins(&mut self) -> Result { let num_bytes = self.load_u8(4)?; if num_bytes == 0 { Ok(BigUint::zero()) } else { - self.load_uint((num_bytes * 8) as usize) + self.load_uint(num_bytes as usize * 8) } } pub fn load_address(&mut self) -> Result { + self.ensure_enough_bits(2)?; let tp = self.bit_reader.read::(2).map_cell_parser_error()?; match tp { 0 => Ok(TonAddress::null()), 2 => { + self.ensure_enough_bits(1 + 8 + 32 * 8)?; let _res1 = self.bit_reader.read::(1).map_cell_parser_error()?; let wc = self.bit_reader.read::(8).map_cell_parser_error()?; let mut hash_part = [0_u8; 32]; @@ -190,16 +191,357 @@ impl CellParser<'_> { pub fn ensure_empty(&mut self) -> Result<(), TonCellError> { let remaining_bits = self.remaining_bits(); - if remaining_bits == 0 { + let remaining_refs = self.references.len() - self.next_ref; + if remaining_bits == 0 && remaining_refs == 0 { Ok(()) } else { - Err(TonCellError::NonEmptyReader(remaining_bits)) + Err(TonCellError::NonEmptyReader { + remaining_bits, + remaining_refs, + }) } } pub fn skip_bits(&mut self, num_bits: usize) -> Result<(), TonCellError> { + self.ensure_enough_bits(num_bits)?; self.bit_reader .skip(num_bits as u32) .map_cell_parser_error() } + + fn load_number(&mut self, bit_len: usize) -> Result { + self.ensure_enough_bits(bit_len)?; + + self.bit_reader + .read::(bit_len as u32) + .map_cell_parser_error() + } + + fn ensure_enough_bits(&mut self, bit_len: usize) -> Result<(), TonCellError> { + if self.remaining_bits() < bit_len { + return Err(TonCellError::CellParserError( + "Not enough bits to read".to_owned(), + )); + } + Ok(()) + } + + pub fn next_reference(&mut self) -> Result { + if self.next_ref < self.references.len() { + let reference = self.references[self.next_ref].clone(); + self.next_ref += 1; + + Ok(reference) + } else { + Err(TonCellError::CellParserError( + "Not enough references to read".to_owned(), + )) + } + } + // https://docs.ton.org/develop/data-formats/tl-b-types#eiher + pub fn load_either_cell_or_cell_ref(&mut self) -> Result { + // TODO: think about how we can make it generic + let is_ref = self.load_bit()?; + if is_ref { + Ok(self.next_reference()?) + } else { + let remaining_bits = self.remaining_bits(); + let data = self.load_bits(remaining_bits)?; + let result = Arc::new(Cell::new(data, remaining_bits, vec![], false)?); + Ok(result) + } + } + // https://docs.ton.org/develop/data-formats/tl-b-types#maybe + pub fn load_maybe_cell_ref(&mut self) -> Result, TonCellError> { + let is_some = self.load_bit()?; + if is_some { + Ok(Some(self.next_reference()?)) + } else { + Ok(None) + } + } +} + +#[cfg(test)] +mod tests { + + use num_bigint::{BigInt, BigUint}; + + use crate::address::TonAddress; + use crate::cell::Cell; + + #[test] + fn test_load_bit() { + let cell = Cell::new([0b10101010].to_vec(), 4, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert!(parser.load_bit().unwrap()); + assert!(!parser.load_bit().unwrap()); + assert!(parser.load_bit().unwrap()); + assert!(!parser.load_bit().unwrap()); + assert!(parser.load_bit().is_err()); + } + + #[test] + fn test_load_u8() { + let cell = Cell::new([0b10101010].to_vec(), 4, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_u8(4).unwrap(), 0b1010); + assert!(parser.load_u8(1).is_err()); + } + + #[test] + fn test_load_i8() { + let cell = Cell::new([0b10101010].to_vec(), 4, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_i8(4).unwrap(), 0b1010); + assert!(parser.load_i8(2).is_err()); + + let cell = Cell::new([0b10100110, 0b10101010].to_vec(), 13, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_i8(4).unwrap(), 0b1010); + assert_eq!(parser.load_i8(8).unwrap(), 0b01101010); + assert!(parser.load_i8(2).is_err()); + } + + #[test] + fn test_load_u16() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 12, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_u16(8).unwrap(), 0b10101010); + assert!(parser.load_u16(8).is_err()); + } + + #[test] + fn test_load_i16() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 12, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_i16(9).unwrap(), 0b101010100); + assert!(parser.load_i16(4).is_err()); + } + + #[test] + fn test_load_u32() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 13, vec![], false).unwrap(); + let mut parser = cell.parser(); + + assert_eq!(parser.load_u32(8).unwrap(), 0b10101010); + assert!(parser.load_u32(8).is_err()); + } + + #[test] + fn test_load_i32() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 14, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_i32(10).unwrap(), 0b1010101001); + assert!(parser.load_i32(5).is_err()); + } + + #[test] + fn test_load_u64() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 13, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_u64(8).unwrap(), 0b10101010); + assert!(parser.load_u64(8).is_err()); + } + + #[test] + fn test_load_i64() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 14, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_i64(10).unwrap(), 0b1010101001); + assert!(parser.load_i64(5).is_err()); + } + + #[test] + fn test_load_int() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 14, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_int(10).unwrap(), BigInt::from(0b1010101001)); + assert!(parser.load_int(5).is_err()); + } + + #[test] + fn test_load_uint() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 14, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!( + parser.load_uint(10).unwrap(), + BigUint::from(0b1010101001u64) + ); + assert!(parser.load_uint(5).is_err()); + } + + #[test] + fn test_load_byte() { + let cell = Cell::new([0b10101010, 0b01010101].to_vec(), 15, vec![], false).unwrap(); + let mut parser = cell.parser(); + parser.load_bit().unwrap(); + assert_eq!(parser.load_byte().unwrap(), 0b01010100u8); + assert!(parser.load_byte().is_err()); + } + + #[test] + fn test_load_slice() { + let cell = Cell::new( + [0b10101010, 0b01010101, 0b10101010, 0b10101010, 0b10101010].to_vec(), + 32, + vec![], + false, + ) + .unwrap(); + let mut parser = cell.parser(); + parser.load_bit().unwrap(); + let mut slice = [0; 2]; + parser.load_slice(&mut slice).unwrap(); + assert_eq!(slice, [0b01010100, 0b10101011]); + assert!(parser.load_slice(&mut slice).is_err()); + } + + #[test] + fn test_load_bytes() { + let cell = Cell::new( + [0b10101010, 0b01010101, 0b10101010, 0b10101010, 0b10101010].to_vec(), + 32, + vec![], + false, + ) + .unwrap(); + let mut parser = cell.parser(); + parser.load_bit().unwrap(); + let slice = parser.load_bytes(2).unwrap(); + assert_eq!(slice, [0b01010100, 0b10101011]); + assert!(parser.load_bytes(2).is_err()); + } + + #[test] + fn test_load_bits_to_slice() { + let cell = Cell::new( + [0b10101010, 0b01010101, 0b10101010, 0b10101010, 0b10101010].to_vec(), + 22, + vec![], + false, + ) + .unwrap(); + let mut parser = cell.parser(); + parser.load_bit().unwrap(); + let mut slice = [0; 2]; + parser.load_bits_to_slice(12, &mut slice).unwrap(); + assert_eq!(slice, [0b01010100, 0b10100000]); + assert!(parser.load_bits_to_slice(10, &mut slice).is_err()); + } + + #[test] + fn test_load_bits() { + let cell = Cell::new( + [0b10101010, 0b01010101, 0b10101010, 0b10101010, 0b10101010].to_vec(), + 25, + vec![], + false, + ) + .unwrap(); + let mut parser = cell.parser(); + parser.load_bit().unwrap(); + let slice = parser.load_bits(5).unwrap(); + assert_eq!(slice, [0b01010000]); + let slice = parser.load_bits(15).unwrap(); + assert_eq!(slice, [0b10010101, 0b01101010]); + assert!(parser.load_bits(5).is_err()); + } + + #[test] + fn test_load_utf8() { + let cell = Cell::new("a1j\0".as_bytes().to_vec(), 31, vec![], false).unwrap(); + let mut parser = cell.parser(); + let string = parser.load_utf8(2).unwrap(); + assert_eq!(string, "a1"); + let string = parser.load_utf8(1).unwrap(); + assert_eq!(string, "j"); + assert!(parser.load_utf8(1).is_err()); + } + + #[test] + fn test_load_coins() { + let cell = Cell::new( + [ + 0b00011111, 0b11110011, 0b11110011, 0b11110011, 0b11110011, 0b00011111, 0b11110011, + ] + .to_vec(), + 48, + vec![], + false, + ) + .unwrap(); + let mut parser = cell.parser(); + + assert_eq!(parser.load_coins().unwrap(), BigUint::from(0b11111111u64)); + assert_eq!( + parser.load_coins().unwrap(), + BigUint::from(0b111100111111001111110011u64) + ); + assert!(parser.load_coins().is_err()); + } + + #[test] + fn test_load_address() { + let cell = Cell::new([0].to_vec(), 3, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_address().unwrap(), TonAddress::null()); + assert!(parser.load_address().is_err()); + + // with full addresses + let cell = Cell::new( + [ + 0b10000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0b00010000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0b00000010, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ] + .to_vec(), + (3 + 8 + 32 * 8) * 3 - 1, + vec![], + false, + ) + .unwrap(); + let mut parser = cell.parser(); + assert_eq!(parser.load_address().unwrap(), TonAddress::null()); + assert_eq!(parser.load_address().unwrap(), TonAddress::null()); + assert!(parser.load_address().is_err()); + } + + #[test] + fn test_ensure_empty() { + let cell = Cell::new([0b10101010].to_vec(), 7, vec![], false).unwrap(); + let mut parser = cell.parser(); + parser.load_u8(4).unwrap(); + assert!(parser.ensure_empty().is_err()); + parser.load_u8(3).unwrap(); + assert!(parser.ensure_empty().is_ok()); + } + + #[test] + fn test_skip_bits_not_enough_bits() { + let cell = Cell::new([0b11111001, 0b00001010].to_vec(), 12, vec![], false).unwrap(); + let mut parser = cell.parser(); + assert!(parser.skip_bits(5).is_ok()); + assert_eq!(parser.load_bits(5).unwrap(), [0b00100000]); + assert!(parser.skip_bits(3).is_err()); + } + + #[test] + fn test_parser_with_refs() { + let ref1 = Cell::new([0b11111001, 0b00001010].to_vec(), 12, vec![], false).unwrap(); + let ref2 = Cell::new([0b11111001, 0b00001010].to_vec(), 12, vec![], false).unwrap(); + let cell = Cell::new( + [0b11111001, 0b00001010].to_vec(), + 12, + vec![ref1.into(), ref2.into()], + false, + ) + .unwrap(); + let mut parser = cell.parser(); + + assert!(parser.next_reference().is_ok()); + assert!(parser.next_reference().is_ok()); + assert!(parser.next_reference().is_err()); + } } diff --git a/src/cell/raw_boc_from_boc.rs b/src/cell/raw_boc_from_boc.rs index fed5795d..4b75dc24 100644 --- a/src/cell/raw_boc_from_boc.rs +++ b/src/cell/raw_boc_from_boc.rs @@ -2,7 +2,8 @@ use std::cell::RefCell; use std::collections::HashMap; use std::sync::Arc; -use crate::cell::{ArcCell, BagOfCells, Cell, CellHash, RawBagOfCells, RawCell, TonCellError}; +use crate::cell::{ArcCell, BagOfCells, Cell, RawBagOfCells, RawCell, TonCellError}; +use crate::types::TonHash; #[derive(Debug, Clone)] struct IndexedCell { @@ -35,7 +36,7 @@ pub(crate) fn convert_to_raw_boc(boc: &BagOfCells) -> Result HashMap> { +fn build_and_verify_index(roots: &[ArcCell]) -> HashMap> { let mut current_cells: Vec<_> = roots.iter().map(Arc::clone).collect(); let mut new_hash_index = 0; let mut cells_by_hash = HashMap::new(); @@ -89,7 +90,7 @@ fn build_and_verify_index(roots: &[ArcCell]) -> HashMap>, + cells_dict: &HashMap>, ) -> Result, TonCellError> { roots .iter() @@ -109,7 +110,7 @@ fn root_indices( fn raw_cells_from_cells( cells: impl Iterator, - cells_by_hash: &HashMap>, + cells_by_hash: &HashMap>, ) -> Result, TonCellError> { cells .map(|cell| raw_cell_from_cell(&cell, cells_by_hash)) @@ -118,7 +119,7 @@ fn raw_cells_from_cells( fn raw_cell_from_cell( cell: &Cell, - cells_by_hash: &HashMap>, + cells_by_hash: &HashMap>, ) -> Result { raw_cell_reference_indices(cell, cells_by_hash).map(|reference_indices| { RawCell::new( @@ -133,7 +134,7 @@ fn raw_cell_from_cell( fn raw_cell_reference_indices( cell: &Cell, - cells_by_hash: &HashMap>, + cells_by_hash: &HashMap>, ) -> Result, TonCellError> { cell.references .iter() diff --git a/src/cell/slice.rs b/src/cell/slice.rs index 74037aaf..55ac6f8b 100644 --- a/src/cell/slice.rs +++ b/src/cell/slice.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use bitstream_io::{BigEndian, BitRead, BitReader}; use crate::cell::util::BitReadExt; -use crate::cell::{ArcCell, Cell, CellBuilder, CellParser, MapTonCellError, TonCellError}; +use crate::cell::{ArcCell, Cell, CellParser, MapTonCellError, TonCellError}; #[derive(Debug, Clone, PartialEq)] pub struct CellSlice { @@ -70,17 +70,11 @@ impl CellSlice { pub fn parser(&self) -> Result { let bit_len = self.end_bit - self.start_bit; - let cursor = Cursor::new(&self.cell.data); - let mut bit_reader: BitReader>, BigEndian> = - BitReader::endian(cursor, BigEndian); - bit_reader - .skip(self.start_bit as u32) - .map_cell_parser_error()?; - - Ok(CellParser { + Ok(CellParser::new( bit_len, - bit_reader, - }) + &self.cell.data, + &self.cell.references, + )) } #[allow(clippy::let_and_return)] @@ -104,16 +98,6 @@ impl CellSlice { res } - pub fn into_cell(&self) -> Result { - let mut reader = self.parser()?; - let significant_bits = self.end_bit - self.start_bit; - let slice = reader.load_bits(significant_bits); - CellBuilder::new() - .store_bits(significant_bits, slice?.as_slice())? - .store_references(&self.cell.references)? - .build() - } - pub fn reference(&self, idx: usize) -> Result<&ArcCell, TonCellError> { if idx > self.end_ref - self.start_ref { return Err(TonCellError::InvalidIndex { @@ -131,7 +115,7 @@ impl CellSlice { } /// Converts the slice to full `Cell` dropping references to original cell. - pub fn to_cell(&self) -> Result { + pub fn into_cell(&self) -> Result { let bit_len = self.end_bit - self.start_bit; let total_bytes = (bit_len + 7) / 8; let mut data = vec![0u8; total_bytes]; diff --git a/src/cell/state_init.rs b/src/cell/state_init.rs index 73dc5a58..8adc3786 100644 --- a/src/cell/state_init.rs +++ b/src/cell/state_init.rs @@ -1,5 +1,6 @@ -use super::{ArcCell, CellHash}; +use super::ArcCell; use crate::cell::{Cell, CellBuilder, TonCellError}; +use crate::types::TonHash; pub struct StateInitBuilder { code: Option, @@ -58,7 +59,7 @@ impl StateInitBuilder { } impl StateInit { - pub fn create_account_id(code: &ArcCell, data: &ArcCell) -> Result { + pub fn create_account_id(code: &ArcCell, data: &ArcCell) -> Result { Ok(StateInitBuilder::new(code, data).build()?.cell_hash()) } } @@ -68,10 +69,10 @@ mod tests { use std::sync::Arc; use super::StateInitBuilder; - use crate::cell::CellBuilder; + use crate::cell::{CellBuilder, TonCellError}; #[test] - fn test_state_init() -> anyhow::Result<()> { + fn test_state_init() -> Result<(), TonCellError> { let code = Arc::new(CellBuilder::new().store_string("code")?.build()?); let data = Arc::new(CellBuilder::new().store_string("data")?.build()?); let state_init = StateInitBuilder::new(&code, &data) diff --git a/src/message/jetton.rs b/src/message/jetton.rs index c4294526..7d6c61ce 100644 --- a/src/message/jetton.rs +++ b/src/message/jetton.rs @@ -1,4 +1,4 @@ -// Constants from jetton standart +// Constants from jetton standard // https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md // crc32('transfer query_id:uint64 amount:VarUInteger 16 destination:MsgAddress response_destination:MsgAddress custom_payload:Maybe ^Cell forward_ton_amount:VarUInteger 16 forward_payload:Either Cell ^Cell = InternalMsgBody') = 0x8f8a7ea5 & 0x7fffffff = 0xf8a7ea5 diff --git a/src/message/jetton/burn.rs b/src/message/jetton/burn.rs index 4580f499..5894f683 100644 --- a/src/message/jetton/burn.rs +++ b/src/message/jetton/burn.rs @@ -3,9 +3,7 @@ use num_bigint::BigUint; use super::JETTON_BURN; use crate::address::TonAddress; use crate::cell::{ArcCell, Cell, CellBuilder}; -use crate::message::{InvalidMessage, RawMessageUtils, TonMessageError}; -use crate::tl::RawMessage; - +use crate::message::{InvalidMessage, TonMessageError}; /// Creates a body for jetton burn according to TL-B schema: /// /// ```raw @@ -13,6 +11,7 @@ use crate::tl::RawMessage; /// response_destination:MsgAddress custom_payload:(Maybe ^Cell) /// = InternalMsgBody; /// ``` +#[derive(Clone, Debug, PartialEq)] pub struct JettonBurnMessage { /// arbitrary request number. pub query_id: u64, @@ -44,8 +43,8 @@ impl JettonBurnMessage { self } - pub fn with_custom_payload(&mut self, custom_payload: &ArcCell) -> &mut Self { - self.custom_payload = Some(custom_payload.clone()); + pub fn with_custom_payload(&mut self, custom_payload: ArcCell) -> &mut Self { + self.custom_payload = Some(custom_payload); self } @@ -55,17 +54,12 @@ impl JettonBurnMessage { message.store_u64(64, self.query_id)?; message.store_coins(&self.amount)?; message.store_address(&self.response_destination)?; - if let Some(cp) = self.custom_payload.as_ref() { - message.store_bit(true)?; - message.store_reference(cp)?; - } else { - message.store_bit(false)?; - } + message.store_maybe_cell_ref(&self.custom_payload)?; + Ok(message.build()?) } - pub fn parse(msg: &RawMessage) -> Result { - let cell = (&msg).get_raw_data_cell()?; + pub fn parse(cell: &Cell) -> Result { let mut parser = cell.parser(); let opcode: u32 = parser.load_u32(32)?; @@ -80,17 +74,9 @@ impl JettonBurnMessage { } let amount = parser.load_coins()?; let response_destination = parser.load_address()?; - let has_custom_payload = parser.load_bit()?; + let custom_payload = parser.load_maybe_cell_ref()?; parser.ensure_empty()?; - let custom_payload = if has_custom_payload { - cell.expect_reference_count(1)?; - Some(cell.reference(0)?.clone()) - } else { - cell.expect_reference_count(0)?; - None - }; - let result = JettonBurnMessage { query_id, amount, @@ -100,3 +86,93 @@ impl JettonBurnMessage { Ok(result) } } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use num_bigint::BigUint; + + use crate::address::TonAddress; + use crate::cell::BagOfCells; + use crate::message::{JettonBurnMessage, TonMessageError}; + + const JETTON_BURN_WITH_CUSTOM_PAYLOAD_INDICATOR_MSG: &str = "b5ee9c72010101010033000062595f07bc0000009b5946deef3080f21800b026e71919f2c839f639f078d9ee6bc9d7592ebde557edf03661141c7c5f2ea2"; + const NOT_BURN: &str = "b5ee9c72010101010035000066595f07bc0000000000000001545d964b800800cd324c114b03f846373734c74b3c3287e1a8c2c732b5ea563a17c6276ef4af30"; + + #[test] + fn test_jetton_burn_parser() -> Result<(), TonMessageError> { + let boc_with_indicator = + BagOfCells::parse_hex(JETTON_BURN_WITH_CUSTOM_PAYLOAD_INDICATOR_MSG).unwrap(); + let cell_with_indicator = boc_with_indicator.single_root().unwrap(); + let result_jetton_transfer_msg_with_indicator: JettonBurnMessage = + JettonBurnMessage::parse(cell_with_indicator)?; + + let expected_jetton_transfer_msg = JettonBurnMessage { + query_id: 667217747695, + amount: BigUint::from(528161u64), + response_destination: TonAddress::from_str( + "EQBYE3OMjPlkHPsc-Dxs9zXk66yXXvKr9vgbMIoOPi-XUa-f", + ) + .unwrap(), + custom_payload: None, + }; + + assert_eq!( + expected_jetton_transfer_msg, + result_jetton_transfer_msg_with_indicator + ); + + let boc = BagOfCells::parse_hex(NOT_BURN).unwrap(); + let cell = boc.single_root().unwrap(); + + let result_jetton_transfer_msg = JettonBurnMessage::parse(cell)?; + + let expected_jetton_transfer_msg = JettonBurnMessage { + query_id: 1, + amount: BigUint::from(300000000000u64), + response_destination: TonAddress::from_str( + "EQBmmSYIpYH8IxubmmOlnhlD8NRhY5la9SsdC-MTt3pXmOSI", + ) + .unwrap(), + custom_payload: None, + }; + + assert_eq!(expected_jetton_transfer_msg, result_jetton_transfer_msg); + Ok(()) + } + + #[test] + fn test_jetton_burn_builder() { + let result_cell = JettonBurnMessage::new(&BigUint::from(528161u64)) + .with_query_id(667217747695) + .with_response_destination( + &TonAddress::from_str("EQBYE3OMjPlkHPsc-Dxs9zXk66yXXvKr9vgbMIoOPi-XUa-f").unwrap(), + ) + .build() + .unwrap(); + + let result_boc_serialized = BagOfCells::from_root(result_cell).serialize(false).unwrap(); + let expected_boc_serialized = + hex::decode(JETTON_BURN_WITH_CUSTOM_PAYLOAD_INDICATOR_MSG).unwrap(); + + assert_eq!(expected_boc_serialized, result_boc_serialized); + + let result_cell = JettonBurnMessage { + query_id: 1, + amount: BigUint::from(300000000000u64), + response_destination: TonAddress::from_str( + "EQBmmSYIpYH8IxubmmOlnhlD8NRhY5la9SsdC-MTt3pXmOSI", + ) + .unwrap(), + custom_payload: None, + } + .build() + .unwrap(); + + let result_boc_serialized = BagOfCells::from_root(result_cell).serialize(false).unwrap(); + let expected_boc_serialized = hex::decode(NOT_BURN).unwrap(); + + assert_eq!(expected_boc_serialized, result_boc_serialized); + } +} diff --git a/src/message/jetton/jetton_transfer.rs b/src/message/jetton/jetton_transfer.rs index e310daf5..7aead1a9 100644 --- a/src/message/jetton/jetton_transfer.rs +++ b/src/message/jetton/jetton_transfer.rs @@ -3,7 +3,7 @@ use num_traits::Zero; use super::JETTON_TRANSFER; use crate::address::TonAddress; -use crate::cell::{ArcCell, Cell, CellBuilder}; +use crate::cell::{ArcCell, Cell, CellBuilder, EMPTY_ARC_CELL}; use crate::message::{InvalidMessage, TonMessageError, ZERO_COINS}; /// Creates a body for jetton transfer according to TL-B schema: @@ -14,7 +14,7 @@ use crate::message::{InvalidMessage, TonMessageError, ZERO_COINS}; /// forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) /// = InternalMsgBody; /// ``` -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct JettonTransferMessage { /// arbitrary request number. pub query_id: u64, @@ -29,7 +29,7 @@ pub struct JettonTransferMessage { /// the amount of nanotons to be sent to the destination address. pub forward_ton_amount: BigUint, /// optional custom data that should be sent to the destination address. - pub forward_payload: Option, + pub forward_payload: ArcCell, } impl JettonTransferMessage { @@ -41,7 +41,7 @@ impl JettonTransferMessage { response_destination: TonAddress::null(), custom_payload: None, forward_ton_amount: ZERO_COINS.clone(), - forward_payload: None, + forward_payload: EMPTY_ARC_CELL.clone(), } } @@ -55,23 +55,23 @@ impl JettonTransferMessage { self } - pub fn with_custom_payload(&mut self, custom_payload: &ArcCell) -> &mut Self { - self.custom_payload = Some(custom_payload.clone()); + pub fn with_custom_payload(&mut self, custom_payload: ArcCell) -> &mut Self { + self.custom_payload = Some(custom_payload); self } pub fn with_forward_payload( &mut self, forward_ton_amount: &BigUint, - forward_payload: &ArcCell, + forward_payload: ArcCell, ) -> &mut Self { self.forward_ton_amount.clone_from(forward_ton_amount); - self.forward_payload = Some(forward_payload.clone()); + self.forward_payload = forward_payload; self } pub fn build(&self) -> Result { - if self.forward_ton_amount.is_zero() && self.forward_payload.is_some() { + if self.forward_ton_amount.is_zero() && self.forward_payload == EMPTY_ARC_CELL.clone() { return Err(TonMessageError::ForwardTonAmountIsNegative); } @@ -81,19 +81,9 @@ impl JettonTransferMessage { message.store_coins(&self.amount)?; message.store_address(&self.destination)?; message.store_address(&self.response_destination)?; - if let Some(cp) = self.custom_payload.as_ref() { - message.store_bit(true)?; - message.store_reference(cp)?; - } else { - message.store_bit(false)?; - } + message.store_maybe_cell_ref(&self.custom_payload)?; message.store_coins(&self.forward_ton_amount)?; - if let Some(fp) = self.forward_payload.as_ref() { - message.store_bit(true)?; - message.store_reference(fp)?; - } else { - message.store_bit(false)?; - } + message.store_either_cell_or_cell_ref(&self.forward_payload)?; Ok(message.build()?) } @@ -113,33 +103,11 @@ impl JettonTransferMessage { let amount = parser.load_coins()?; let destination = parser.load_address()?; let response_destination = parser.load_address()?; - let has_custom_payload = parser.load_bit()?; + let custom_payload = parser.load_maybe_cell_ref()?; let forward_ton_amount = parser.load_coins()?; - let has_forward_payload = parser.load_bit()?; + let forward_payload = parser.load_either_cell_or_cell_ref()?; parser.ensure_empty()?; - let (custom_payload, forward_payload) = match (has_custom_payload, has_forward_payload) { - (true, true) => { - cell.expect_reference_count(2)?; - ( - Some(cell.reference(0)?.clone()), - Some(cell.reference(1)?.clone()), - ) - } - (true, false) => { - cell.expect_reference_count(1)?; - (Some(cell.reference(0)?.clone()), None) - } - (false, true) => { - cell.expect_reference_count(1)?; - (None, Some(cell.reference(0)?.clone())) - } - (false, false) => { - cell.expect_reference_count(0)?; - (None, None) - } - }; - let result = JettonTransferMessage { query_id, amount, @@ -160,21 +128,27 @@ mod tests { use std::sync::Arc; use num_bigint::BigUint; - use tokio_test::assert_ok; use crate::address::TonAddress; use crate::cell::{BagOfCells, Cell}; - use crate::message::JettonTransferMessage; - // message origin: https://tonviewer.com/transaction/2e250e3c9367d8092f15e09fb3c3d750749187c2a528a616bf0e88e5f36ca3f4 + use crate::message::{JettonTransferMessage, TonMessageError}; + const JETTON_TRANSFER_MSG : &str="b5ee9c720101020100a800016d0f8a7ea5001f5512dab844d643b9aca00800ef3b9902a271b2a01c8938a523cfe24e71847aaeb6a620001ed44a77ac0e709c1033428f030100d7259385618009dd924373a9aad41b28cec02da9384d67363af2034fc2a7ccc067e28d4110de86e66deb002365dfa32dfd419308ebdf35e0f6ba7c42534bbb5dab5e89e28ea3e0455cc2d2f00257a672371a90e149b7d25864dbfd44827cc1e8a30df1b1e0c4338502ade2ad96"; const TRANSFER_PAYLOAD: &str = "259385618009DD924373A9AAD41B28CEC02DA9384D67363AF2034FC2A7CCC067E28D4110DE86E66DEB002365DFA32DFD419308EBDF35E0F6BA7C42534BBB5DAB5E89E28EA3E0455CC2D2F00257A672371A90E149B7D25864DBFD44827CC1E8A30DF1B1E0C4338502ADE2AD94"; #[test] - fn test_jetton_transfer_parser() { + fn test_jetton_transfer_parser() -> Result<(), TonMessageError> { let boc = BagOfCells::parse_hex(JETTON_TRANSFER_MSG).unwrap(); let cell = boc.single_root().unwrap(); - let result_jetton_transfer_msg = assert_ok!(JettonTransferMessage::parse(&cell)); + let result_jetton_transfer_msg = JettonTransferMessage::parse(cell)?; + + let transfer_message_cell = Arc::new(Cell::new( + hex::decode(TRANSFER_PAYLOAD).unwrap(), + 862, + vec![], + false, + )?); let expected_jetton_transfer_msg = JettonTransferMessage { query_id: 8819263745311958, @@ -187,15 +161,14 @@ mod tests { .unwrap(), custom_payload: None, forward_ton_amount: BigUint::from(215000000u64), - forward_payload: Some(Arc::new( - Cell::new(hex::decode(TRANSFER_PAYLOAD).unwrap(), 862, vec![], false).unwrap(), - )), + forward_payload: transfer_message_cell, }; assert_eq!(expected_jetton_transfer_msg, result_jetton_transfer_msg); + Ok(()) } #[test] - fn test_jetton_transfer_builder() { + fn test_jetton_transfer_builder() -> Result<(), TonMessageError> { let jetton_transfer_msg = JettonTransferMessage { query_id: 8819263745311958, amount: BigUint::from(1000000000u64), @@ -207,16 +180,17 @@ mod tests { .unwrap(), custom_payload: None, forward_ton_amount: BigUint::from(215000000u64), - forward_payload: Some(Arc::new( + forward_payload: Arc::new( Cell::new(hex::decode(TRANSFER_PAYLOAD).unwrap(), 862, vec![], false).unwrap(), - )), + ), }; - let result_cell = assert_ok!(jetton_transfer_msg.build()); + let result_cell = jetton_transfer_msg.build()?; let result_boc_serialized = BagOfCells::from_root(result_cell).serialize(false).unwrap(); let expected_boc_serialized = hex::decode(JETTON_TRANSFER_MSG).unwrap(); - assert_eq!(expected_boc_serialized, result_boc_serialized) + assert_eq!(expected_boc_serialized, result_boc_serialized); + Ok(()) } } diff --git a/src/message/jetton/transfer_notification.rs b/src/message/jetton/transfer_notification.rs index 293becf9..b5bad75c 100644 --- a/src/message/jetton/transfer_notification.rs +++ b/src/message/jetton/transfer_notification.rs @@ -2,7 +2,7 @@ use num_bigint::BigUint; use super::JETTON_TRANSFER_NOTIFICATION; use crate::address::TonAddress; -use crate::cell::{ArcCell, Cell, CellBuilder}; +use crate::cell::{ArcCell, Cell, CellBuilder, EMPTY_ARC_CELL}; use crate::message::{InvalidMessage, TonMessageError}; /// Creates a body for jetton transfer notification according to TL-B schema: @@ -12,7 +12,7 @@ use crate::message::{InvalidMessage, TonMessageError}; /// sender:MsgAddress forward_payload:(Either Cell ^Cell) /// = InternalMsgBody; /// ``` -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct JettonTransferNotificationMessage { /// should be equal with request's query_id. pub query_id: u64, @@ -21,7 +21,7 @@ pub struct JettonTransferNotificationMessage { /// is address of the previous owner of transferred jettons. pub sender: TonAddress, /// optional custom data that should be sent to the destination address. - pub forward_payload: Option, + pub forward_payload: ArcCell, } impl JettonTransferNotificationMessage { @@ -30,7 +30,7 @@ impl JettonTransferNotificationMessage { query_id: 0, amount: amount.clone(), sender: sender.clone(), - forward_payload: None, + forward_payload: EMPTY_ARC_CELL.clone(), } } @@ -39,24 +39,20 @@ impl JettonTransferNotificationMessage { self } - pub fn with_forward_payload(&mut self, forward_payload: &ArcCell) -> &mut Self { - self.forward_payload = Some(forward_payload.clone()); + pub fn with_forward_payload(&mut self, forward_payload: ArcCell) -> &mut Self { + self.forward_payload = forward_payload; self } pub fn build(&self) -> Result { - let mut message = CellBuilder::new(); - message.store_u32(32, JETTON_TRANSFER_NOTIFICATION)?; - message.store_u64(64, self.query_id)?; - message.store_coins(&self.amount)?; - message.store_address(&self.sender)?; - if let Some(fp) = self.forward_payload.as_ref() { - message.store_bit(true)?; - message.store_reference(fp)?; - } else { - message.store_bit(false)?; - } - Ok(message.build()?) + let mut builder = CellBuilder::new(); + builder.store_u32(32, JETTON_TRANSFER_NOTIFICATION)?; + builder.store_u64(64, self.query_id)?; + builder.store_coins(&self.amount)?; + builder.store_address(&self.sender)?; + builder.store_either_cell_or_cell_ref(&self.forward_payload)?; + + Ok(builder.build()?) } pub fn parse(cell: &Cell) -> Result { @@ -77,17 +73,9 @@ impl JettonTransferNotificationMessage { } let amount = parser.load_coins()?; let sender = parser.load_address()?; - let has_forward_payload = parser.load_bit()?; + let forward_payload = parser.load_either_cell_or_cell_ref()?; parser.ensure_empty()?; - let forward_payload = if has_forward_payload { - cell.expect_reference_count(1)?; - Some(cell.reference(0)?.clone()) - } else { - cell.expect_reference_count(0)?; - None - }; - let result = JettonTransferNotificationMessage { query_id, amount, @@ -105,18 +93,16 @@ mod tests { use std::sync::Arc; use num_bigint::BigUint; - use tokio_test::assert_ok; use crate::address::TonAddress; use crate::cell::{BagOfCells, Cell}; - use crate::message::JettonTransferNotificationMessage; + use crate::message::{JettonTransferNotificationMessage, TonMessageError}; - // message origin: https://tonviewer.com/transaction/1b19a1ea5fdefd93ffc6051f67a8e89e02a5ead168a70c6ccd38f6d2e3f0e1d5 const JETTON_TRANSFER_NOTIFICATION_MSG: &str = "b5ee9c720101020100a60001647362d09c000000d2c7ceef23401312d008003be20895401cd8539741eb7815d5e63b3429014018d7e5f7800de16a984f27730100dd25938561800f2465b65c76b1b562f32423676970b431319419d5f45ffd2eeb2155ce6ab7eacc78ee0250ef0300077c4112a8039b0a72e83d6f02babcc766852028031afcbef001bc2d5309e4ee700257a672371a90e149b7d25864dbfd44827cc1e8a30df1b1e0c4338502ade2ad96"; const TRANSFER_NOTIFICATION_PAYLOAD: &str = "25938561800f2465b65c76b1b562f32423676970b431319419d5f45ffd2eeb2155ce6ab7eacc78ee0250ef0300077c4112a8039b0a72e83d6f02babcc766852028031afcbef001bc2d5309e4ee700257a672371a90e149b7d25864dbfd44827cc1e8a30df1b1e0c4338502ade2ad94"; #[test] - fn test_jetton_transfer_notification_parser() { + fn test_jetton_transfer_notification_parser() -> Result<(), TonMessageError> { let boc = BagOfCells::parse_hex(JETTON_TRANSFER_NOTIFICATION_MSG).unwrap(); let cell = boc.single_root().unwrap(); @@ -125,7 +111,7 @@ mod tests { amount: BigUint::from(20000000u64), sender: TonAddress::from_str("EQAd8QRKoA5sKcug9bwK6vMdmhSAoAxr8vvABvC1TCeTude5") .unwrap(), - forward_payload: Some(Arc::new( + forward_payload: Arc::new( Cell::new( hex::decode(TRANSFER_NOTIFICATION_PAYLOAD).unwrap(), 886, @@ -133,25 +119,25 @@ mod tests { false, ) .unwrap(), - )), + ), }; - let result_jetton_transfer_msg = - assert_ok!(JettonTransferNotificationMessage::parse(&cell)); + let result_jetton_transfer_msg = JettonTransferNotificationMessage::parse(cell)?; assert_eq!( expected_jetton_transfer_notification_msg, result_jetton_transfer_msg - ) + ); + Ok(()) } #[test] - fn test_jetton_transfer_notification_builder() { + fn test_jetton_transfer_notification_builder() -> Result<(), TonMessageError> { let jetton_transfer_notification_msg = JettonTransferNotificationMessage { query_id: 905295359779, amount: BigUint::from(20000000u64), sender: TonAddress::from_str("EQAd8QRKoA5sKcug9bwK6vMdmhSAoAxr8vvABvC1TCeTude5") .unwrap(), - forward_payload: Some(Arc::new( + forward_payload: Arc::new( Cell::new( hex::decode(TRANSFER_NOTIFICATION_PAYLOAD).unwrap(), 886, @@ -159,14 +145,15 @@ mod tests { false, ) .unwrap(), - )), + ), }; - let result_cell = assert_ok!(jetton_transfer_notification_msg.build()); + let result_cell = jetton_transfer_notification_msg.build()?; let expected_boc_serialized = hex::decode(JETTON_TRANSFER_NOTIFICATION_MSG).unwrap(); let result_boc_serialized = BagOfCells::from_root(result_cell).serialize(false).unwrap(); - assert_eq!(expected_boc_serialized, result_boc_serialized) + assert_eq!(expected_boc_serialized, result_boc_serialized); + Ok(()) } } diff --git a/src/types.rs b/src/types.rs index 765b7cf7..def64712 100644 --- a/src/types.rs +++ b/src/types.rs @@ -6,3 +6,12 @@ mod tvm_stack_entry; pub use tvm_stack_entry::*; mod error; pub use error::*; + +pub const TON_HASH_BYTES: usize = 32; +pub const ZERO_HASH: TonHash = [0; 32]; +pub type TonHash = [u8; TON_HASH_BYTES]; + +pub const DEFAULT_CELL_HASH: TonHash = [ + 150, 162, 150, 210, 36, 242, 133, 198, 123, 238, 147, 195, 15, 138, 48, 145, 87, 240, 218, 163, + 93, 197, 184, 126, 65, 11, 120, 99, 10, 9, 207, 199, +]; diff --git a/tests/error_test.rs b/tests/error_test.rs index d24efd93..ef14d695 100644 --- a/tests/error_test.rs +++ b/tests/error_test.rs @@ -92,7 +92,13 @@ fn test_ton_cell_error_output() { }, ); log::error!("{}", TonCellError::InvalidAddressType(200)); - log::error!("{}", TonCellError::NonEmptyReader(300)); + log::error!( + "{}", + TonCellError::NonEmptyReader { + remaining_bits: 300, + remaining_refs: 3 + } + ); } #[test] From 8770433a38ccff4dbd38034d69294cf82c69fde7 Mon Sep 17 00:00:00 2001 From: dbaranov34 Date: Wed, 7 Aug 2024 19:04:03 +0400 Subject: [PATCH 19/29] ignoring remaining refs --- src/cell/parser.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cell/parser.rs b/src/cell/parser.rs index 02195b0f..255dc75b 100644 --- a/src/cell/parser.rs +++ b/src/cell/parser.rs @@ -191,8 +191,9 @@ impl<'a> CellParser<'a> { pub fn ensure_empty(&mut self) -> Result<(), TonCellError> { let remaining_bits = self.remaining_bits(); - let remaining_refs = self.references.len() - self.next_ref; - if remaining_bits == 0 && remaining_refs == 0 { + let remaining_refs = self.references.len() - self.next_ref; + // if remaining_bits == 0 && remaining_refs == 0 { // todo: We will restore reference checking in in 0.18 + if remaining_bits == 0 { Ok(()) } else { Err(TonCellError::NonEmptyReader { From b216c87849f5f6025da2698253c27a96c5867777 Mon Sep 17 00:00:00 2001 From: dbaranov34 Date: Thu, 8 Aug 2024 17:32:44 +0400 Subject: [PATCH 20/29] clippy --- src/contract/jetton/master_contract.rs | 4 ++-- src/contract/jetton/wallet_contract.rs | 2 +- src/contract/nft/collection_contract.rs | 2 +- src/contract/nft/item_contract.rs | 2 +- src/contract/wallet/wallet_contract.rs | 4 ++-- tests/contract_test.rs | 4 ++-- tests/farm_data_test.rs | 4 ++-- tests/jetton_test.rs | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/contract/jetton/master_contract.rs b/src/contract/jetton/master_contract.rs index 3af359f7..a19d8518 100644 --- a/src/contract/jetton/master_contract.rs +++ b/src/contract/jetton/master_contract.rs @@ -31,7 +31,7 @@ pub trait JettonMasterContract: TonContractInterface { let method = JettonMasterMethods::GetJettonData.into(); let address = self.address().clone(); - let res = self.run_get_method(method, &Vec::new()).await?; + let res = self.run_get_method(method, Vec::new()).await?; let stack = res.stack; if stack.len() == JETTON_DATA_STACK_ELEMENTS { @@ -71,7 +71,7 @@ pub trait JettonMasterContract: TonContractInterface { .map_cell_error(method, owner_address)?; let cell_slice = CellSlice::full_cell(cell).map_cell_error(method, owner_address)?; let slice = TvmStackEntry::Slice(cell_slice); - let res = self.run_get_method(method, &vec![slice]).await?; + let res = self.run_get_method(method, vec![slice]).await?; let stack = res.stack; if stack.len() == 1 { stack[0].get_address().map_stack_error(method, &address) diff --git a/src/contract/jetton/wallet_contract.rs b/src/contract/jetton/wallet_contract.rs index 6bdade6f..548bcb7a 100644 --- a/src/contract/jetton/wallet_contract.rs +++ b/src/contract/jetton/wallet_contract.rs @@ -27,7 +27,7 @@ pub trait JettonWalletContract: TonContractInterface { let method = JettonWalletMethods::GetWalletData.into(); let address = self.address().clone(); - let res = self.run_get_method(method, &Vec::new()).await?; + let res = self.run_get_method(method, Vec::new()).await?; let stack = res.stack; if stack.len() == WALLET_DATA_STACK_ELEMENTS { diff --git a/src/contract/nft/collection_contract.rs b/src/contract/nft/collection_contract.rs index 85877b5c..73cf9bc3 100644 --- a/src/contract/nft/collection_contract.rs +++ b/src/contract/nft/collection_contract.rs @@ -41,7 +41,7 @@ pub trait NftCollectionContract: TonContractInterface { let method = NftCollectionMethods::GetCollectionData.into(); let address = self.address().clone(); - let stack = self.run_get_method(method, &Vec::new()).await?.stack; + let stack = self.run_get_method(method, Vec::new()).await?.stack; if stack.len() == NFT_COLLECTION_STACK_ELEMENTS { let next_item_index = stack[0].get_i64().map_stack_error(method, &address)?; let cell = stack[1].get_cell().map_stack_error(method, &address)?; diff --git a/src/contract/nft/item_contract.rs b/src/contract/nft/item_contract.rs index 8996c051..d3bb2141 100644 --- a/src/contract/nft/item_contract.rs +++ b/src/contract/nft/item_contract.rs @@ -43,7 +43,7 @@ pub trait NftItemContract: TonContractInterface { const NFT_DATA_STACK_ELEMENTS: usize = 5; let address = self.address().clone(); - let stack = self.run_get_method(method, &Vec::new()).await?.stack; + let stack = self.run_get_method(method, Vec::new()).await?.stack; if stack.len() == NFT_DATA_STACK_ELEMENTS { let init = stack[0].get_bool().map_stack_error(method, &address)?; let index = stack[1].get_biguint().map_stack_error(method, &address)?; diff --git a/src/contract/wallet/wallet_contract.rs b/src/contract/wallet/wallet_contract.rs index 95e3842c..47933d5c 100644 --- a/src/contract/wallet/wallet_contract.rs +++ b/src/contract/wallet/wallet_contract.rs @@ -14,7 +14,7 @@ enum WalletContractMethods { pub trait TonWalletContract: TonContractInterface { async fn seqno(&self) -> Result { let method: &str = WalletContractMethods::Seqno.into(); - let res = self.run_get_method("seqno", &Vec::new()).await?; + let res = self.run_get_method("seqno", Vec::new()).await?; let stack = res.stack; if stack.len() != 1 { Err(TonContractError::InvalidMethodResultStackSize { @@ -31,7 +31,7 @@ pub trait TonWalletContract: TonContractInterface { async fn get_public_key(&self) -> Result, TonContractError> { let method: &str = WalletContractMethods::GetPublicKey.into(); - let res = self.run_get_method(method, &Vec::new()).await?; + let res = self.run_get_method(method, Vec::new()).await?; let stack = res.stack; if stack.len() != 1 { Err(TonContractError::InvalidMethodResultStackSize { diff --git a/tests/contract_test.rs b/tests/contract_test.rs index 2d62349c..3e0cf4b1 100644 --- a/tests/contract_test.rs +++ b/tests/contract_test.rs @@ -34,7 +34,7 @@ pub struct PoolData { #[async_trait] pub trait PoolContract: TonContractInterface { async fn get_pool_data(&self) -> anyhow::Result { - let res = assert_ok!(self.run_get_method("get_pool_data", &Vec::new()).await); + let res = assert_ok!(self.run_get_method("get_pool_data", Vec::new()).await); if res.stack.len() == 10 { let pool_data = PoolData { reserve0: assert_ok!(res.stack[0].get_biguint()), @@ -58,7 +58,7 @@ pub trait PoolContract: TonContractInterface { } async fn invalid_method(&self) -> Result { - self.run_get_method("invalid_method", &Vec::new()).await + self.run_get_method("invalid_method", Vec::new()).await } } diff --git a/tests/farm_data_test.rs b/tests/farm_data_test.rs index 19962d6e..fd63274e 100644 --- a/tests/farm_data_test.rs +++ b/tests/farm_data_test.rs @@ -109,7 +109,7 @@ async fn test_get_farming_minter_data() { let stack = assert_ok!( contract - .run_get_method("get_farming_minter_data", &Vec::new()) + .run_get_method("get_farming_minter_data", Vec::new()) .await ); @@ -139,7 +139,7 @@ async fn test_get_farming_data() { let stack = assert_ok!( contract - .run_get_method("get_farming_data", &Vec::new()) + .run_get_method("get_farming_data", Vec::new()) .await ); diff --git a/tests/jetton_test.rs b/tests/jetton_test.rs index 76c6f95e..6dca3db6 100644 --- a/tests/jetton_test.rs +++ b/tests/jetton_test.rs @@ -175,7 +175,7 @@ async fn test_jetton_image_data() -> anyhow::Result<()> { 17, 18, 84, 70, 179, 240, 137, 163, 42, 147, 119, 220, ]; let mut hasher: Sha256 = Sha256::new(); - hasher.update(&content_res.image_data.unwrap()); + hasher.update(content_res.image_data.unwrap()); let img_hash = hasher.finalize()[..].to_vec(); assert_eq!(TARGET_IMAGE_HASH.to_vec(), img_hash); From ae06ec6499471af86cb7466fedc0dd8f85a98d9a Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Thu, 8 Aug 2024 13:40:48 +0000 Subject: [PATCH 21/29] Impl #168: Fix byte alignment in CellBuilder --- src/cell/builder.rs | 66 +++++++++++++++++++++++++++++++++++++-------- src/cell/parser.rs | 2 +- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/cell/builder.rs b/src/cell/builder.rs index 288752cf..751b2199 100644 --- a/src/cell/builder.rs +++ b/src/cell/builder.rs @@ -89,18 +89,22 @@ impl CellBuilder { } pub fn store_uint(&mut self, bit_len: usize, val: &BigUint) -> Result<&mut Self, TonCellError> { - if val.bits() as usize > bit_len { + let val_bits = if val.is_zero() { + 0 + } else { + val.bits() as usize + }; + + if val_bits > bit_len { return Err(TonCellError::cell_builder_error(format!( "Value {} doesn't fit in {} bits (takes {} bits)", - val, - bit_len, - val.bits() + val, bit_len, val_bits ))); } // example: bit_len=13, val=5. 5 = 00000101, we must store 0000000000101 // leading_zeros_bits = 10 // leading_zeros_bytes = 10 / 8 = 1 - let leading_zero_bits = bit_len - val.bits() as usize; + let leading_zero_bits = bit_len - val_bits; let leading_zeros_bytes = leading_zero_bits / 8; for _ in 0..leading_zeros_bytes { self.store_byte(0)?; @@ -113,9 +117,13 @@ impl CellBuilder { // and then store val's high byte in minimum number of bits let val_bytes = val.to_bytes_be(); let high_bits_cnt = { - let cnt = val.bits() % 8; + let cnt = val_bits % 8; if cnt == 0 { - 8 + if val.is_zero() { + 0 + } else { + 8 + } } else { cnt } @@ -133,13 +141,17 @@ impl CellBuilder { pub fn store_int(&mut self, bit_len: usize, val: &BigInt) -> Result<&mut Self, TonCellError> { let (sign, mag) = val.clone().into_parts(); + + let mag_bits = if mag.is_zero() { + bit_len as u64 + } else { + mag.bits() + }; let bit_len = bit_len - 1; // reserve 1 bit for sign - if bit_len < mag.bits() as usize { + if bit_len < mag_bits as usize { return Err(TonCellError::cell_builder_error(format!( "Value {} doesn't fit in {} bits (takes {} bits)", - val, - bit_len, - mag.bits() + val, bit_len, mag_bits ))); } if sign == Sign::Minus { @@ -369,6 +381,7 @@ mod tests { use std::str::FromStr; use num_bigint::{BigInt, BigUint, Sign}; + use num_traits::Zero; use crate::address::TonAddress; use crate::cell::builder::extend_and_invert_bits; @@ -567,9 +580,40 @@ mod tests { println!("cell: {:?}", cell); for bits_num in bits_for_tests.iter() { let written_value = cell_parser.load_uint(*bits_num)?; + println!("RES: {:?},{:?}", written_value, value); + assert_eq!(written_value, value); } Ok(()) } + + #[test] + fn test_padding() -> Result<(), TonCellError> { + let mut writer = CellBuilder::new(); + writer.store_uint(32, &BigUint::zero())?; //deadline + + let cell = writer.build()?; + + println!("{:?}", cell); + assert_eq!(cell.data.len(), 4); + assert_eq!(cell.bit_len, 32); + Ok(()) + } + + #[test] + fn test_padding_r() -> Result<(), TonCellError> { + let mut writer = CellBuilder::new(); + writer.store_uint(32, &BigUint::zero())?; //deadline + writer.store_address(&TonAddress::null())?; //recipientAddress + writer.store_address(&TonAddress::null())?; //referralAddress + writer.store_bit(false)?; //fulfillPayload + writer.store_bit(false)?; //rejectPayload + let cell = writer.build()?; + println!("{:?}", cell); + + assert_eq!(cell.data.len(), 5); + assert_eq!(cell.bit_len, 38); + Ok(()) + } } diff --git a/src/cell/parser.rs b/src/cell/parser.rs index 255dc75b..53bbca0f 100644 --- a/src/cell/parser.rs +++ b/src/cell/parser.rs @@ -191,7 +191,7 @@ impl<'a> CellParser<'a> { pub fn ensure_empty(&mut self) -> Result<(), TonCellError> { let remaining_bits = self.remaining_bits(); - let remaining_refs = self.references.len() - self.next_ref; + let remaining_refs = self.references.len() - self.next_ref; // if remaining_bits == 0 && remaining_refs == 0 { // todo: We will restore reference checking in in 0.18 if remaining_bits == 0 { Ok(()) From a16c8f934a013702055b1e136aac65518f4649b5 Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Tue, 13 Aug 2024 09:52:34 +0000 Subject: [PATCH 22/29] Impl #169: fixed d2 descriptor of cell --- src/cell.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/cell.rs b/src/cell.rs index 797bd0d2..d1494943 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -419,7 +419,7 @@ impl Default for Cell { } fn get_repr_for_data( - (original_data, original_data_bit_len): (&[u8], usize), + original_data_bit_len: usize, (data, data_bit_len): (&[u8], usize), refs: &[ArcCell], level_mask: LevelMask, @@ -433,7 +433,7 @@ fn get_repr_for_data( let mut writer = BitWriter::endian(Vec::with_capacity(buffer_len), BigEndian); let d1 = get_refs_descriptor(cell_type, refs, level_mask.apply(level).mask()); - let d2 = get_bits_descriptor(original_data, original_data_bit_len); + let d2 = get_bits_descriptor(original_data_bit_len); // Write descriptors writer.write(8, d1).map_cell_parser_error()?; @@ -505,7 +505,7 @@ fn calculate_hashes_and_depths( // Calculate Hash let repr = get_repr_for_data( - (data, bit_len), + bit_len, (current_data, current_bit_len), references, level_mask, @@ -532,10 +532,8 @@ fn get_refs_descriptor(cell_type: CellType, references: &[ArcCell], level_mask: references.len() as u8 + 8 * cell_type_var + level_mask as u8 * 32 } -fn get_bits_descriptor(data: &[u8], bit_len: usize) -> u8 { - let rest_bits = bit_len % 8; - let full_bytes = rest_bits == 0; - data.len() as u8 * 2 - !full_bytes as u8 // subtract 1 if the last byte is not full +fn get_bits_descriptor(bit_len: usize) -> u8 { + (bit_len / 8 + (bit_len + 7) / 8) as u8 } fn write_data( From 7b7c68ba19c5e92878e2266b26f750c0f7216237 Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Tue, 13 Aug 2024 10:31:03 +0000 Subject: [PATCH 23/29] Downstream 0.17.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e08b48c4..c366aa44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.17.0" +version = "0.17.2" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" From 8433ff9ebebec8bedb0cc330f512e63c368c388e Mon Sep 17 00:00:00 2001 From: dbaranov34 Date: Wed, 14 Aug 2024 15:20:51 +0400 Subject: [PATCH 24/29] fixed load _dict --- src/cell.rs | 2 +- src/cell/dict_loader.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/cell.rs b/src/cell.rs index d1494943..de9122a2 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -317,7 +317,7 @@ impl Cell { if dict_loader.key_bit_len() - pp.bit_len() == 0 { let bytes = pp.get_value_as_bytes(); let key = dict_loader.extract_key(bytes.as_slice())?; - let offset = self.bit_len - parser.remaining_bits(); + let offset = parser.remaining_bits(); let cell_slice = CellSlice::new_with_offset(self, offset)?; let value = dict_loader.extract_value(&cell_slice)?; map.insert(key, value); diff --git a/src/cell/dict_loader.rs b/src/cell/dict_loader.rs index 1b862b80..5666583e 100644 --- a/src/cell/dict_loader.rs +++ b/src/cell/dict_loader.rs @@ -162,3 +162,38 @@ where self.bit_len } } + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use num_bigint::BigUint; + + use crate::cell::{key_extractor_u8, value_extractor_uint, BagOfCells, GenericDictLoader}; + + #[test] + fn tmp() { + let dict_boc_str = "te6cckEBBgEAWgABGccNPKUADZm5MepOjMABAgHNAgMCASAEBQAnQAAAAAAAAAAAAAABMlF4tR2RgCAAJgAAAAAAAAAAAAABaFhaZZhr6AAAJgAAAAAAAAAAAAAAR8sYU4eC4AA1PIC5"; + let dict_boc = BagOfCells::parse_base64(&dict_boc_str).unwrap(); + let cell = dict_boc.single_root().unwrap(); + let loader = GenericDictLoader::new(key_extractor_u8, value_extractor_uint, 8); + let result = cell + .reference(0) + .unwrap() + .load_generic_dict(&loader) + .unwrap(); + + let mut expected_result = HashMap::new(); + expected_result.extend( + [ + (0, BigUint::from(25965603044000000000u128)), + (1, BigUint::from(5173255344000000000u64)), + (2, BigUint::from(344883687000000000u64)), + ] + .iter() + .cloned(), + ); + + assert_eq!(expected_result, result); + } +} From a576d26cc7d74cdbabc2fa18648f9798518e9fd3 Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Fri, 16 Aug 2024 15:33:00 +0000 Subject: [PATCH 25/29] Downstream 0.17.3 --- Cargo.toml | 2 +- src/cell.rs | 2 +- src/cell/dict_loader.rs | 29 ++++++++++++++--------------- src/cell/slice.rs | 32 +------------------------------- src/types/tvm_stack_entry.rs | 9 ++++++--- 5 files changed, 23 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c366aa44..297f9f2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.17.2" +version = "0.17.3" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" diff --git a/src/cell.rs b/src/cell.rs index de9122a2..d1494943 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -317,7 +317,7 @@ impl Cell { if dict_loader.key_bit_len() - pp.bit_len() == 0 { let bytes = pp.get_value_as_bytes(); let key = dict_loader.extract_key(bytes.as_slice())?; - let offset = parser.remaining_bits(); + let offset = self.bit_len - parser.remaining_bits(); let cell_slice = CellSlice::new_with_offset(self, offset)?; let value = dict_loader.extract_value(&cell_slice)?; map.insert(key, value); diff --git a/src/cell/dict_loader.rs b/src/cell/dict_loader.rs index 5666583e..9bc5b22e 100644 --- a/src/cell/dict_loader.rs +++ b/src/cell/dict_loader.rs @@ -107,14 +107,18 @@ pub fn value_extractor_snake_formatted_string( pub fn value_extractor_uint(cell_slice: &CellSlice) -> Result { let bit_len = cell_slice.end_bit - cell_slice.start_bit; - cell_slice.parser()?.skip_bits(cell_slice.start_bit)?; - cell_slice.parser()?.load_uint(bit_len) + let mut parser = cell_slice.cell.parser(); + parser.skip_bits(cell_slice.start_bit)?; + let result = parser.load_uint(bit_len)?; + Ok(result) } pub fn value_extractor_int(cell_slice: &CellSlice) -> Result { let bit_len = cell_slice.end_bit - cell_slice.start_bit; - cell_slice.parser()?.skip_bits(cell_slice.start_bit)?; - cell_slice.parser()?.load_int(bit_len) + let mut parser = cell_slice.cell.parser(); + parser.skip_bits(cell_slice.start_bit)?; + let result = parser.load_int(bit_len)?; + Ok(result) } pub struct GenericDictLoader @@ -172,7 +176,7 @@ mod test { use crate::cell::{key_extractor_u8, value_extractor_uint, BagOfCells, GenericDictLoader}; #[test] - fn tmp() { + fn dict_loader_test() { let dict_boc_str = "te6cckEBBgEAWgABGccNPKUADZm5MepOjMABAgHNAgMCASAEBQAnQAAAAAAAAAAAAAABMlF4tR2RgCAAJgAAAAAAAAAAAAABaFhaZZhr6AAAJgAAAAAAAAAAAAAAR8sYU4eC4AA1PIC5"; let dict_boc = BagOfCells::parse_base64(&dict_boc_str).unwrap(); let cell = dict_boc.single_root().unwrap(); @@ -183,16 +187,11 @@ mod test { .load_generic_dict(&loader) .unwrap(); - let mut expected_result = HashMap::new(); - expected_result.extend( - [ - (0, BigUint::from(25965603044000000000u128)), - (1, BigUint::from(5173255344000000000u64)), - (2, BigUint::from(344883687000000000u64)), - ] - .iter() - .cloned(), - ); + let expected_result: HashMap = HashMap::from([ + (0, BigUint::from(25965603044000000000u128)), + (1, BigUint::from(5173255344000000000u64)), + (2, BigUint::from(344883687000000000u64)), + ]); assert_eq!(expected_result, result); } diff --git a/src/cell/slice.rs b/src/cell/slice.rs index 55ac6f8b..9186e4fa 100644 --- a/src/cell/slice.rs +++ b/src/cell/slice.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use bitstream_io::{BigEndian, BitRead, BitReader}; use crate::cell::util::BitReadExt; -use crate::cell::{ArcCell, Cell, CellParser, MapTonCellError, TonCellError}; +use crate::cell::{ArcCell, Cell, MapTonCellError, TonCellError}; #[derive(Debug, Clone, PartialEq)] pub struct CellSlice { @@ -68,36 +68,6 @@ impl CellSlice { }) } - pub fn parser(&self) -> Result { - let bit_len = self.end_bit - self.start_bit; - Ok(CellParser::new( - bit_len, - &self.cell.data, - &self.cell.references, - )) - } - - #[allow(clippy::let_and_return)] - pub fn parse(&self, parse: F) -> Result - where - F: FnOnce(&mut CellParser) -> Result, - { - let mut reader = self.parser()?; - let res = parse(&mut reader); - res - } - - #[allow(clippy::let_and_return)] - pub fn parse_fully(&self, parse: F) -> Result - where - F: FnOnce(&mut CellParser) -> Result, - { - let mut reader = self.parser()?; - let res = parse(&mut reader); - reader.ensure_empty()?; - res - } - pub fn reference(&self, idx: usize) -> Result<&ArcCell, TonCellError> { if idx > self.end_ref - self.start_ref { return Err(TonCellError::InvalidIndex { diff --git a/src/types/tvm_stack_entry.rs b/src/types/tvm_stack_entry.rs index baf5ca2c..8415b71c 100644 --- a/src/types/tvm_stack_entry.rs +++ b/src/types/tvm_stack_entry.rs @@ -99,15 +99,18 @@ impl TvmStackEntry { TvmStackEntry::Cell(cell) => cell .parse_fully(|r| r.load_address()) .map_err(StackParseError::CellError), - TvmStackEntry::Slice(slice) => slice - .parse_fully(|r| r.load_address()) - .map_err(StackParseError::CellError), + TvmStackEntry::Slice(slice) => { + let cell = slice.into_cell()?; + cell.parse_fully(|r| r.load_address()) + .map_err(StackParseError::CellError) + } t => Err(StackParseError::InvalidEntryType { expected: "Slice".to_string(), found: t.clone(), }), } } + pub fn get_string(&self) -> Result { match self { TvmStackEntry::Slice(slice) => { From 3b86873233016a7e7ba4a5bd5e67651c97511b79 Mon Sep 17 00:00:00 2001 From: dbaranov34 Date: Fri, 16 Aug 2024 19:56:12 +0400 Subject: [PATCH 26/29] bump sys version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 297f9f2d..8fc8a930 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ reqwest = "0.12" thiserror = "1" tokio = { version = "1", features = ["rt","macros"] } tokio-retry = "0.3" -tonlib-sys = "=2024.6.1" +tonlib-sys = "=2024.8" [dev-dependencies] anyhow = "1" From 9a6ee86dffb4eea66a6f0c062eb8a9777e5001e9 Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Mon, 19 Aug 2024 13:34:18 +0000 Subject: [PATCH 27/29] Downstram 0.17.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8fc8a930..50b8699b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.17.3" +version = "0.17.4" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT" From a1d0b68a007e7e94ccab3774630e2878a7dd08c5 Mon Sep 17 00:00:00 2001 From: Dmitrii Baranov Date: Wed, 28 Aug 2024 14:35:06 +0000 Subject: [PATCH 28/29] Impl#171; Support references in load_either_cell_or_cell_ref --- src/cell/dict_loader.rs | 2 +- src/cell/parser.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/cell/dict_loader.rs b/src/cell/dict_loader.rs index 9bc5b22e..0cb2fa32 100644 --- a/src/cell/dict_loader.rs +++ b/src/cell/dict_loader.rs @@ -178,7 +178,7 @@ mod test { #[test] fn dict_loader_test() { let dict_boc_str = "te6cckEBBgEAWgABGccNPKUADZm5MepOjMABAgHNAgMCASAEBQAnQAAAAAAAAAAAAAABMlF4tR2RgCAAJgAAAAAAAAAAAAABaFhaZZhr6AAAJgAAAAAAAAAAAAAAR8sYU4eC4AA1PIC5"; - let dict_boc = BagOfCells::parse_base64(&dict_boc_str).unwrap(); + let dict_boc = BagOfCells::parse_base64(dict_boc_str).unwrap(); let cell = dict_boc.single_root().unwrap(); let loader = GenericDictLoader::new(key_extractor_u8, value_extractor_uint, 8); let result = cell diff --git a/src/cell/parser.rs b/src/cell/parser.rs index 53bbca0f..7edf1f51 100644 --- a/src/cell/parser.rs +++ b/src/cell/parser.rs @@ -248,7 +248,12 @@ impl<'a> CellParser<'a> { } else { let remaining_bits = self.remaining_bits(); let data = self.load_bits(remaining_bits)?; - let result = Arc::new(Cell::new(data, remaining_bits, vec![], false)?); + let remaining_ref_count = self.references.len() - self.next_ref; + let mut references = vec![]; + for _ in 0..remaining_ref_count { + references.push(self.next_reference()?) + } + let result = Arc::new(Cell::new(data, remaining_bits, references, false)?); Ok(result) } } @@ -266,10 +271,12 @@ impl<'a> CellParser<'a> { #[cfg(test)] mod tests { + use std::sync::Arc; + use num_bigint::{BigInt, BigUint}; use crate::address::TonAddress; - use crate::cell::Cell; + use crate::cell::{Cell, CellBuilder}; #[test] fn test_load_bit() { @@ -545,4 +552,33 @@ mod tests { assert!(parser.next_reference().is_ok()); assert!(parser.next_reference().is_err()); } + + #[test] + fn test_either_with_references() { + let reference_cell = Cell::new([0xA5, 0x5A].to_vec(), 12, vec![], false).unwrap(); + let cell_either = Arc::new( + Cell::new( + [0xFF, 0xB0].to_vec(), + 12, + vec![reference_cell.into()], + false, + ) + .unwrap(), + ); + let cell = CellBuilder::new() + .store_bit(true) + .unwrap() + .store_either_cell_or_cell_ref(&cell_either) + .unwrap() + .build() + .unwrap(); + + let mut parser = cell.parser(); + + let result_first_bit = parser.load_bit().unwrap(); + let result_cell_either = parser.load_either_cell_or_cell_ref().unwrap(); + + assert!(result_first_bit); + assert_eq!(result_cell_either, cell_either); + } } From f5c636f4c716ac0e909f91ef45de56950d1f1728 Mon Sep 17 00:00:00 2001 From: dbaranov34 Date: Wed, 28 Aug 2024 18:39:21 +0400 Subject: [PATCH 29/29] bump version --- .gitlab-ci.yml | 102 ---------------------------------------- .pre-commit-config.yaml | 5 -- Cargo.toml | 2 +- 3 files changed, 1 insertion(+), 108 deletions(-) delete mode 100644 .gitlab-ci.yml delete mode 100644 .pre-commit-config.yaml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index e1b653fc..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,102 +0,0 @@ -image: ${CI_REGISTRY}/ston-fi/docker/rust-build:20.10.24_1.79.0-bb606509 - -# Prevent duplicate pipelines, branch pipeline and merge_request pipeline -workflow: - rules: - - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS' - when: never - - if: '$CI_COMMIT_BRANCH || $CI_COMMIT_TAG' - -stages: - - test - -variables: - FF_USE_FASTZIP: 1 - CACHE_COMPRESSION_LEVEL: "fastest" - CARGO_HOME: "${CI_PROJECT_DIR}/.cargo" - RUSTFLAGS: "-D warnings -C target-cpu=znver2" - TARGET_CPU_MARCH: "znver2" - - -.snippets: - get-cache: - - | - if [ "$CI_COMMIT_REF_NAME" != "$CI_DEFAULT_BRANCH" ]; then - export CACHE_FALLBACK_KEY="cache-$CI_DEFAULT_BRANCH-$CI_RUNNER_ID-non-protected"; - else - export CACHE_FALLBACK_KEY="cache-main-$CI_RUNNER_ID-protected"; - fi - echo "Using cache key: cache-$CI_COMMIT_REF_SLUG" - echo "Fallback cache key: $CACHE_FALLBACK_KEY" - -cache: - key: shared-cache - paths: - - target/ - - .cargo/ - - -test: - stage: test - before_script: - - !reference [.snippets, get-cache] - script: - - cargo fmt --check - - cargo clippy --release - - cargo clippy --features "state_cache" --release - - cargo clippy --features "emulate_get_method" --release - - - cargo build --release - - cargo build --release --features "state_cache" - - cargo build --release --features "emulate_get_method" - - cargo test --lib - - cargo test --lib --features "state_cache" - - cargo test --lib --features "emulate_get_method" - tags: - - zen4 - rules: - - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE != 'merge_request_event' - -integration_test: - stage: test - before_script: - - !reference [.snippets, get-cache] - script: - - cargo install cargo-nextest - - cargo nextest run --release - - cargo nextest run --release --features "state_cache" - - cargo nextest run --release --features "emulate_get_method" - tags: - - zen4 - when: manual - -test-mr: - tags: - - zen4 - stage: test - before_script: - - !reference [.snippets, get-cache] - script: - - cargo fmt --check - - cargo clippy --release - - cargo clippy --release --features "state_cache" - - cargo clippy --release --features "emulate_get_method" - - cargo build --release - - cargo build --release --features "state_cache" - - cargo rustc --release --features "emulate_get_method" - - cargo test --lib --all-features - rules: - - if: $CI_PIPELINE_SOURCE == 'merge_request_event' - -test-master: - tags: - - zen4 - stage: test - script: - - cargo fmt --check - - cargo rustc --all-features -- -D warnings - - cargo test --lib --all-features - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index c24bafa1..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -repos: -- repo: https://git.stonfi.net/ston-fi/infrastructure/pre-commit-hooks - rev: b2553d529beb93e75e5ca40663716476faa89ea6 - hooks: - - id: pre-commit-rust-cargo-fmt diff --git a/Cargo.toml b/Cargo.toml index 50b8699b..b5eb5c33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tonlib" -version = "0.17.4" +version = "0.17.5" edition = "2021" description = "Rust SDK for The Open Network" license = "MIT"