From 8d7a6b3895de4302e2ae38bf0f70fa26dbb6e078 Mon Sep 17 00:00:00 2001 From: Arseny Mitin Date: Mon, 19 Aug 2024 00:36:21 +0400 Subject: [PATCH 1/3] feat(contracts): wallet v5r1 --- crates/contracts/src/wallet/mod.rs | 173 ++++----- crates/contracts/src/wallet/v4r2/mod.rs | 111 +++++- crates/contracts/src/wallet/v5r1/mod.rs | 359 ++++++++++++++++++ .../src/wallet/v5r1/wallet_v5r1.code | 1 + crates/tlb-ton/src/action.rs | 163 ++++++++ crates/tlb-ton/src/lib.rs | 3 + crates/tlb-ton/src/library.rs | 44 +++ crates/tlb-ton/src/list.rs | 51 +++ crates/tlb/src/as/data.rs | 2 +- crates/tlb/src/cell.rs | 1 - crates/tlb/src/de/parser.rs | 22 +- 11 files changed, 799 insertions(+), 131 deletions(-) create mode 100644 crates/contracts/src/wallet/v5r1/mod.rs create mode 100644 crates/contracts/src/wallet/v5r1/wallet_v5r1.code create mode 100644 crates/tlb-ton/src/action.rs create mode 100644 crates/tlb-ton/src/library.rs create mode 100644 crates/tlb-ton/src/list.rs diff --git a/crates/contracts/src/wallet/mod.rs b/crates/contracts/src/wallet/mod.rs index 318bebc..b8eb1e1 100644 --- a/crates/contracts/src/wallet/mod.rs +++ b/crates/contracts/src/wallet/mod.rs @@ -1,6 +1,7 @@ //! TON [Wallet](https://docs.ton.org/participate/wallets/contracts) pub mod mnemonic; pub mod v4r2; +pub mod v5r1; use std::{marker::PhantomData, sync::Arc}; @@ -8,14 +9,9 @@ use anyhow::anyhow; use chrono::{DateTime, Utc}; use nacl::sign::{signature, Keypair, PUBLIC_KEY_LENGTH}; use num_bigint::BigUint; -use tlb::{ - bits::{de::BitReaderExt, ser::BitWriterExt}, - de::{CellDeserialize, CellParser, CellParserError}, - r#as::Ref, - ser::{CellBuilder, CellBuilderError, CellSerialize, CellSerializeExt}, - Cell, -}; +use tlb::{ser::CellSerialize, Cell}; use tlb_ton::{ + action::SendMsgAction, message::{CommonMsgInfo, ExternalInMsgInfo, Message}, state_init::StateInit, MsgAddress, @@ -49,6 +45,9 @@ impl Wallet where V: WalletVersion, { + // TODO + // pub fn derive_state(workchain_id: i32, state: ) + /// Derive wallet from its workchain, keypair and id pub fn derive(workchain_id: i32, key_pair: Keypair, wallet_id: u32) -> anyhow::Result { Ok(Self { @@ -84,27 +83,46 @@ where self.wallet_id } + #[inline] + pub fn sign(&self, msg: impl AsRef<[u8]>) -> anyhow::Result<[u8; 64]> { + signature(msg.as_ref(), self.key_pair.skey.as_slice()) + .map_err(|e| anyhow!("{}", e.message))? + .try_into() + .map_err(|sig: Vec<_>| { + anyhow!( + "got signature of a wrong size, expected 64, got: {}", + sig.len() + ) + }) + } + /// Shortcut to [create](Wallet::create_external_body), /// [sign](Wallet::sign_body) and [wrap](Wallet::wrap_signed) external /// message ready for sending to TON blockchain. /// /// ```rust - /// # use tlb_ton::{message::Message, currency::ONE_TON}; + /// # use hex_literal::hex; + /// # use tlb::Cell; + /// # use tlb_ton::{ + /// # message::Message, + /// # currency::ONE_TON, + /// # action::SendMsgAction, + /// # }; /// # use ton_contracts::wallet::{ /// # mnemonic::Mnemonic, - /// # v4r2::V4R2, + /// # v5r1::V5R1, /// # Wallet, - /// # WalletOpSendMessage, /// # }; + /// # /// # let mnemonic: Mnemonic = "jewel loop vast intact snack drip fatigue lunch erode green indoor balance together scrub hen monster hour narrow banner warfare increase panel sound spell" /// # .parse() /// # .unwrap(); /// # let keypair = mnemonic.generate_keypair(None).unwrap(); - /// # let wallet = Wallet::::derive_default(keypair).unwrap(); + /// # let wallet = Wallet::::derive_default(keypair).unwrap(); /// let msg = wallet.create_external_message( /// Default::default(), // DateTime::UNIX_EPOCH means no deadline /// 0, // seqno - /// [WalletOpSendMessage { + /// [SendMsgAction { /// mode: 3, /// message: Message::<()>::transfer( /// "EQAWezezpqKTbO6xjCussXDdIeJ7XxTcErjA6uD3T3r7AwTk" @@ -117,59 +135,57 @@ where /// .unwrap(), /// }], /// false, // do not deploy wallet - /// ); + /// ).unwrap(); + /// # let mut b = Cell::builder(); + /// # b.store(msg).unwrap(); + /// # let cell = b.into_cell(); + /// # assert_eq!( + /// # hex!("607b41a4b219fbc6d23f4aae5c4b85e5ceca07bc0ba732ae02a621588f0577d4"), + ///# cell.hash(), + /// # ); /// ``` #[inline] pub fn create_external_message( &self, expire_at: DateTime, seqno: u32, - msgs: impl IntoIterator, + msgs: impl IntoIterator, state_init: bool, - ) -> anyhow::Result, V::Data>> { - let body = self.create_external_body(expire_at, seqno, msgs); - let signed = self.sign_body(&body)?; - let wrapped = self.wrap_signed(signed, state_init); + ) -> anyhow::Result, V::Data>> { + let sign_body = self.create_sign_body(expire_at, seqno, msgs); + let signature = self.sign_body(&sign_body)?; + let body = V::wrap_signed_external(sign_body, signature); + let wrapped = self.wrap_external_msg(body, state_init); Ok(wrapped) } /// Create external body for this wallet. #[inline] - pub fn create_external_body( + pub fn create_sign_body( &self, expire_at: DateTime, seqno: u32, - msgs: impl IntoIterator, - ) -> V::MessageBody { - V::create_external_body(self.wallet_id, expire_at, seqno, msgs) + msgs: impl IntoIterator, + ) -> V::SignBody { + V::create_sign_body(self.wallet_id, expire_at, seqno, msgs) } /// Sign body from [`.create_external_body()`](Wallet::create_external_body) /// using this wallet's private key - pub fn sign_body(&self, msg: &V::MessageBody) -> anyhow::Result { - let msg = msg.to_cell()?; - Ok(SignedBody { - sig: signature(msg.hash().as_slice(), self.key_pair.skey.as_slice()) - .map_err(|e| anyhow!("{}", e.message))? - .try_into() - .map_err(|sig: Vec<_>| { - anyhow!( - "got signature of a wrong size, expected 64, got: {}", - sig.len() - ) - })?, - msg, - }) + pub fn sign_body(&self, msg: &V::SignBody) -> anyhow::Result<[u8; 64]> { + let mut b = Cell::builder(); + b.store(msg)?; + self.sign(b.into_cell().hash()) } /// Wrap signed body from [`.sign_body()`](Wallet::sign_body) in a message /// ready for sending to TON blockchain. #[inline] - pub fn wrap_signed( + pub fn wrap_external_msg( &self, - body: SignedBody, + body: V::ExternalMsgBody, state_init: bool, - ) -> Message, V::Data> { + ) -> Message, V::Data> { Message { info: CommonMsgInfo::ExternalIn(ExternalInMsgInfo { src: MsgAddress::NULL, @@ -186,41 +202,11 @@ where } } -/// Signed body retuned from [`Wallet::sign_body()`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SignedBody { - pub sig: [u8; 64], - pub msg: T, -} - -impl CellSerialize for SignedBody -where - T: CellSerialize, -{ - #[inline] - fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - builder.pack(self.sig)?.store(&self.msg)?; - Ok(()) - } -} - -impl<'de, T> CellDeserialize<'de> for SignedBody -where - T: CellDeserialize<'de>, -{ - #[inline] - fn parse(parser: &mut CellParser<'de>) -> Result> { - Ok(Self { - sig: parser.unpack()?, - msg: parser.parse()?, - }) - } -} - /// Version of [`Wallet`] pub trait WalletVersion { type Data: CellSerialize; - type MessageBody: CellSerialize; + type SignBody: CellSerialize; + type ExternalMsgBody: CellSerialize; /// Code of the wallet for use with [`StateInit`] fn code() -> Arc; @@ -228,46 +214,15 @@ pub trait WalletVersion { /// Init data for use with [`StateInit`] fn init_data(wallet_id: u32, pubkey: [u8; PUBLIC_KEY_LENGTH]) -> Self::Data; - /// Creates external body for [`Wallet::sign_body()`] - fn create_external_body( + /// Creates body for further signing with + /// [`.wrap_signed_external()`](WalletVersion::wrap_signed_external) + fn create_sign_body( wallet_id: u32, expire_at: DateTime, seqno: u32, - msgs: impl IntoIterator, - ) -> Self::MessageBody; -} - -/// Operation for [`Wallet`] to send message -pub struct WalletOpSendMessage { - /// See - pub mode: u8, - pub message: Message, -} - -impl WalletOpSendMessage -where - T: CellSerialize, - IC: CellSerialize, - ID: CellSerialize, -{ - #[inline] - pub fn normalize(&self) -> Result { - Ok(WalletOpSendMessage { - mode: self.mode, - message: self.message.normalize()?, - }) - } -} + msgs: impl IntoIterator, + ) -> Self::SignBody; -impl CellSerialize for WalletOpSendMessage -where - T: CellSerialize, - IC: CellSerialize, - ID: CellSerialize, -{ - #[inline] - fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - builder.pack(self.mode)?.store_as::<_, Ref>(&self.message)?; - Ok(()) - } + /// Wraps signed body into external [`Message::body`] + fn wrap_signed_external(body: Self::SignBody, signature: [u8; 64]) -> Self::ExternalMsgBody; } diff --git a/crates/contracts/src/wallet/v4r2/mod.rs b/crates/contracts/src/wallet/v4r2/mod.rs index 1bc1ce7..1077b16 100644 --- a/crates/contracts/src/wallet/v4r2/mod.rs +++ b/crates/contracts/src/wallet/v4r2/mod.rs @@ -1,3 +1,4 @@ +use core::iter; use std::sync::Arc; use chrono::{DateTime, Utc}; @@ -9,14 +10,14 @@ use tlb::{ de::{CellDeserialize, CellParser, CellParserError}, r#as::{NoArgs, Ref}, ser::{CellBuilder, CellBuilderError, CellSerialize}, - Cell, + Cell, Error, }; use tlb_ton::{ - boc::BagOfCells, currency::Grams, hashmap::HashmapE, state_init::StateInit, MsgAddress, - UnixTimestamp, + action::SendMsgAction, boc::BagOfCells, currency::Grams, hashmap::HashmapE, + state_init::StateInit, MsgAddress, UnixTimestamp, }; -use super::{WalletOpSendMessage, WalletVersion}; +use super::WalletVersion; lazy_static! { static ref WALLET_V4R2_CODE_CELL: Arc = { @@ -33,7 +34,8 @@ pub struct V4R2; impl WalletVersion for V4R2 { type Data = WalletV4R2Data; - type MessageBody = WalletV4R2Message; + type SignBody = WalletV4R2SignBody; + type ExternalMsgBody = WalletV4R2ExternalBody; fn code() -> Arc { WALLET_V4R2_CODE_CELL.clone() @@ -48,19 +50,23 @@ impl WalletVersion for V4R2 { } } - fn create_external_body( + fn create_sign_body( wallet_id: u32, expire_at: DateTime, seqno: u32, - msgs: impl IntoIterator, - ) -> Self::MessageBody { - WalletV4R2Message { + msgs: impl IntoIterator, + ) -> Self::SignBody { + WalletV4R2SignBody { wallet_id, expire_at, seqno, op: WalletV4R2Op::Send(msgs.into_iter().collect()), } } + + fn wrap_signed_external(body: Self::SignBody, signature: [u8; 64]) -> Self::ExternalMsgBody { + WalletV4R2ExternalBody { signature, body } + } } #[derive(Debug, Clone, PartialEq, Eq)] @@ -88,7 +94,7 @@ impl CellSerialize for WalletV4R2Data { impl<'de> CellDeserialize<'de> for WalletV4R2Data { fn parse(parser: &mut CellParser<'de>) -> Result> { - let d = Self { + Ok(Self { seqno: parser.unpack()?, wallet_id: parser.unpack()?, pubkey: parser.unpack()?, @@ -97,19 +103,19 @@ impl<'de> CellDeserialize<'de> for WalletV4R2Data { (), (), ))?, - }; - Ok(d) + }) } } -pub struct WalletV4R2Message { +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WalletV4R2SignBody { pub wallet_id: u32, pub expire_at: DateTime, pub seqno: u32, pub op: WalletV4R2Op, } -impl CellSerialize for WalletV4R2Message { +impl CellSerialize for WalletV4R2SignBody { fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { builder .pack(self.wallet_id)? @@ -120,25 +126,67 @@ impl CellSerialize for WalletV4R2Message { } } +impl<'de> CellDeserialize<'de> for WalletV4R2SignBody { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + wallet_id: parser.unpack()?, + expire_at: parser.unpack_as::<_, UnixTimestamp>()?, + seqno: parser.unpack()?, + op: parser.parse()?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub enum WalletV4R2Op { - Send(Vec), + Send(Vec), DeployAndInstall(WalletV4R2OpDeployAndInstallPlugin), Install(WalletV4R2OpPlugin), Remove(WalletV4R2OpPlugin), } +impl WalletV4R2Op { + const SEND_PREFIX: u8 = 0; + const DEPLOY_AND_INSTALL_PREFIX: u8 = 1; + const INSTALL_PREFIX: u8 = 2; + const REMOVE_PREFIX: u8 = 3; +} + impl CellSerialize for WalletV4R2Op { fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { match self { - Self::Send(msgs) => builder.pack(0u8)?.store_many(msgs)?, - Self::DeployAndInstall(msg) => builder.pack(1u8)?.store(msg)?, - Self::Install(msg) => builder.pack(2u8)?.store(msg)?, - Self::Remove(msg) => builder.pack(3u8)?.store(msg)?, + Self::Send(msgs) => builder.pack(Self::SEND_PREFIX)?.store_many(msgs)?, + Self::DeployAndInstall(msg) => { + builder.pack(Self::DEPLOY_AND_INSTALL_PREFIX)?.store(msg)? + } + Self::Install(msg) => builder.pack(Self::INSTALL_PREFIX)?.store(msg)?, + Self::Remove(msg) => builder.pack(Self::REMOVE_PREFIX)?.store(msg)?, }; Ok(()) } } +impl<'de> CellDeserialize<'de> for WalletV4R2Op { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(match parser.unpack()? { + Self::SEND_PREFIX => Self::Send( + iter::from_fn(|| { + if parser.no_references_left() { + return None; + } + Some(parser.parse()) + }) + .collect::, _>>()?, + ), + Self::DEPLOY_AND_INSTALL_PREFIX => Self::DeployAndInstall(parser.parse()?), + Self::INSTALL_PREFIX => Self::Install(parser.parse()?), + Self::REMOVE_PREFIX => Self::Remove(parser.parse()?), + op => return Err(Error::custom(format!("unknown op: {op:0b}"))), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct WalletV4R2OpDeployAndInstallPlugin { pub plugin_workchain: i8, pub plugin_balance: BigUint, @@ -178,6 +226,7 @@ where } } +#[derive(Debug, Clone, PartialEq, Eq)] pub struct WalletV4R2OpPlugin { pub plugin_address: MsgAddress, pub amount: BigUint, @@ -208,6 +257,30 @@ impl<'de> CellDeserialize<'de> for WalletV4R2OpPlugin { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WalletV4R2ExternalBody { + pub signature: [u8; 64], + pub body: WalletV4R2SignBody, +} + +impl CellSerialize for WalletV4R2ExternalBody { + #[inline] + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder.pack(self.signature)?.store(&self.body)?; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for WalletV4R2ExternalBody { + #[inline] + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + signature: parser.unpack()?, + body: parser.parse()?, + }) + } +} + #[cfg(test)] mod tests { use tlb::bits::{de::unpack_fully, ser::pack_with}; diff --git a/crates/contracts/src/wallet/v5r1/mod.rs b/crates/contracts/src/wallet/v5r1/mod.rs new file mode 100644 index 0000000..dfe97c7 --- /dev/null +++ b/crates/contracts/src/wallet/v5r1/mod.rs @@ -0,0 +1,359 @@ +use std::sync::Arc; + +use chrono::{DateTime, Utc}; +use lazy_static::lazy_static; +use nacl::sign::PUBLIC_KEY_LENGTH; +use tlb::{ + bits::{de::BitReaderExt, ser::BitWriterExt}, + de::{CellDeserialize, CellParser, CellParserError}, + r#as::{Data, NoArgs}, + ser::{CellBuilder, CellBuilderError, CellSerialize}, + Cell, Error, +}; +use tlb_ton::{ + action::{OutAction, SendMsgAction}, + boc::BagOfCells, + hashmap::HashmapE, + list::List, + MsgAddress, UnixTimestamp, +}; + +use super::WalletVersion; + +lazy_static! { + static ref WALLET_V5R1_CODE_CELL: Arc = { + BagOfCells::parse_base64(include_str!("./wallet_v5r1.code")) + .unwrap() + .single_root() + .expect("code BoC must be single root") + .clone() + }; +} + +/// Wallet [v5r1](https://github.com/ton-blockchain/wallet-contract-v5/blob/main/Specification.md). +pub struct V5R1; + +impl WalletVersion for V5R1 { + type Data = WalletV5R1Data; + type SignBody = WalletV5RSignBody; + type ExternalMsgBody = WalletV5R1MsgBody; + + #[inline] + fn code() -> Arc { + WALLET_V5R1_CODE_CELL.clone() + } + + #[inline] + fn init_data(wallet_id: u32, pubkey: [u8; nacl::sign::PUBLIC_KEY_LENGTH]) -> Self::Data { + WalletV5R1Data { + is_signature_allowed: true, + seqno: 0, + wallet_id, + pubkey, + extensions: HashmapE::Empty, + } + } + + #[inline] + fn create_sign_body( + wallet_id: u32, + valid_until: DateTime, + msg_seqno: u32, + msgs: impl IntoIterator, + ) -> Self::SignBody { + WalletV5RSignBody { + wallet_id, + valid_until, + msg_seqno, + inner: WalletV5R1InnerRequest { + out_actions: msgs.into_iter().map(OutAction::SendMsg).collect(), + other_actions: [].into(), + }, + } + } + + #[inline] + fn wrap_signed_external(body: Self::SignBody, signature: [u8; 64]) -> Self::ExternalMsgBody { + WalletV5R1MsgBody::ExternalSigned(WalletV5R1SignedRequest { body, signature }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WalletV5R1Data { + pub is_signature_allowed: bool, + pub seqno: u32, + pub wallet_id: u32, + pub pubkey: [u8; PUBLIC_KEY_LENGTH], + pub extensions: HashmapE, +} + +impl CellSerialize for WalletV5R1Data { + #[inline] + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder + .pack(self.is_signature_allowed)? + .pack(self.seqno)? + .pack(self.wallet_id)? + .pack(self.pubkey)? + .store_as_with::<_, &HashmapE>, NoArgs<_>>>( + &self.extensions, + (256, (), ()), + )?; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for WalletV5R1Data { + #[inline] + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + is_signature_allowed: parser.unpack()?, + seqno: parser.unpack()?, + wallet_id: parser.unpack()?, + pubkey: parser.unpack()?, + extensions: parser.parse_as_with::<_, HashmapE>, NoArgs<_>>>(( + 258, + (), + (), + ))?, + }) + } +} + +/// ```tlb +/// actions$_ out_actions:(Maybe OutList) has_other_actions:(## 1) {m:#} {n:#} other_actions:(ActionList n m) = InnerRequest; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WalletV5R1InnerRequest { + pub out_actions: Vec, + pub other_actions: Vec, +} + +impl CellSerialize for WalletV5R1InnerRequest { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder + .store_as::<_, Option<&List>>( + Some(&self.out_actions).filter(|actions| !actions.is_empty()), + )? + .store_as::<_, Option<&List>>( + Some(&self.other_actions).filter(|other| !other.is_empty()), + )?; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for WalletV5R1InnerRequest { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + out_actions: parser.parse_as::<_, Option>()?.unwrap_or_default(), + other_actions: parser.parse_as::<_, Option>()?.unwrap_or_default(), + }) + } +} + +/// ```tlb +/// action_add_ext#02 addr:MsgAddressInt = ExtendedAction; +/// action_delete_ext#03 addr:MsgAddressInt = ExtendedAction; +/// action_set_signature_auth_allowed#04 allowed:(## 1) = ExtendedAction; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExtendedAction { + /// ```tlb + /// action_add_ext#02 addr:MsgAddressInt = ExtendedAction; + /// ``` + AddExtension(MsgAddress), + + /// ```tlb + /// action_delete_ext#03 addr:MsgAddressInt = ExtendedAction; + /// ``` + DeleteExtension(MsgAddress), + + /// ```tlb + /// action_set_signature_auth_allowed#04 allowed:(## 1) = ExtendedAction; + /// ``` + SetSignatureAuthAllowed(bool), +} + +impl ExtendedAction { + const ADD_EXTENSION_PREFIX: u8 = 0x02; + const DELETE_EXTENSION_PREFIX: u8 = 0x03; + const SET_SIGNATURE_AUTH_ALLOWED_PREFIX: u8 = 0x04; +} + +impl CellSerialize for ExtendedAction { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + match self { + Self::AddExtension(addr) => builder.pack(Self::ADD_EXTENSION_PREFIX)?.pack(addr)?, + Self::DeleteExtension(addr) => { + builder.pack(Self::DELETE_EXTENSION_PREFIX)?.pack(addr)? + } + Self::SetSignatureAuthAllowed(allowed) => builder + .pack(Self::SET_SIGNATURE_AUTH_ALLOWED_PREFIX)? + .pack(allowed)?, + }; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for ExtendedAction { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(match parser.unpack()? { + Self::ADD_EXTENSION_PREFIX => Self::AddExtension(parser.unpack()?), + Self::DELETE_EXTENSION_PREFIX => Self::DeleteExtension(parser.unpack()?), + Self::SET_SIGNATURE_AUTH_ALLOWED_PREFIX => { + Self::SetSignatureAuthAllowed(parser.unpack()?) + } + prefix => return Err(Error::custom(format!("unknown prefix: {prefix:#0x}"))), + }) + } +} + +/// ```tlb +/// signed_request$_ // 32 (opcode from outer) +/// wallet_id: # // 32 +/// valid_until: # // 32 +/// msg_seqno: # // 32 +/// inner: InnerRequest // +/// signature: bits512 // 512 +///= SignedRequest; // Total: 688 .. 976 + ^Cell +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WalletV5RSignBody { + pub wallet_id: u32, + pub valid_until: DateTime, + pub msg_seqno: u32, + pub inner: WalletV5R1InnerRequest, +} + +impl CellSerialize for WalletV5RSignBody { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder + .pack(self.wallet_id)? + .pack_as::<_, UnixTimestamp>(self.valid_until)? + .pack(self.msg_seqno)? + .store(&self.inner)?; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for WalletV5RSignBody { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + wallet_id: parser.unpack()?, + valid_until: parser.unpack_as::<_, UnixTimestamp>()?, + msg_seqno: parser.unpack()?, + inner: parser.parse()?, + }) + } +} + +/// ```tlb +/// signed_request$_ // 32 (opcode from outer) +/// wallet_id: # // 32 +/// valid_until: # // 32 +/// msg_seqno: # // 32 +/// inner: InnerRequest // +/// signature: bits512 // 512 +///= SignedRequest; // Total: 688 .. 976 + ^Cell +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct WalletV5R1SignedRequest { + pub body: WalletV5RSignBody, + pub signature: [u8; 64], +} + +impl CellSerialize for WalletV5R1SignedRequest { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder.store(&self.body)?.pack(&self.signature)?; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for WalletV5R1SignedRequest { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + body: parser.parse()?, + signature: parser.unpack()?, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum WalletV5R1MsgBody { + /// ```tlb + /// internal_signed#73696e74 signed:SignedRequest = InternalMsgBody; + /// ``` + InternalSigned(WalletV5R1SignedRequest), + + /// ```tlb + /// internal_extension#6578746e query_id:(## 64) inner:InnerRequest = InternalMsgBody; + /// ``` + InternalExtension { + query_id: u64, + inner: WalletV5R1InnerRequest, + }, + + /// ```tlb + /// external_signed#7369676e signed:SignedRequest = ExternalMsgBody; + /// ``` + ExternalSigned(WalletV5R1SignedRequest), +} + +impl WalletV5R1MsgBody { + const INTERNAL_SIGNED_PREFIX: u32 = 0x73696e74; + const INTERNAL_EXTENSION_PREFIX: u32 = 0x6578746e; + const EXTERNAL_SIGNED_PREFIX: u32 = 0x7369676e; +} + +impl CellSerialize for WalletV5R1MsgBody { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + match self { + Self::InternalSigned(msg) => builder.pack(Self::INTERNAL_SIGNED_PREFIX)?.store(msg)?, + Self::InternalExtension { query_id, inner } => builder + .pack(Self::INTERNAL_EXTENSION_PREFIX)? + .pack(query_id)? + .store(inner)?, + Self::ExternalSigned(msg) => builder.pack(Self::EXTERNAL_SIGNED_PREFIX)?.store(msg)?, + }; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for WalletV5R1MsgBody { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(match parser.unpack()? { + Self::INTERNAL_SIGNED_PREFIX => Self::InternalSigned(parser.parse()?), + Self::INTERNAL_EXTENSION_PREFIX => Self::InternalExtension { + query_id: parser.unpack()?, + inner: parser.parse()?, + }, + Self::EXTERNAL_SIGNED_PREFIX => Self::ExternalSigned(parser.parse()?), + prefix => return Err(Error::custom(format!("unknown prefix: {prefix:#0x}"))), + }) + } +} + +#[cfg(test)] +mod tests { + use tlb::bits::{de::unpack_fully, ser::pack_with}; + use tlb_ton::boc::{BagOfCellsArgs, BoC}; + + use super::*; + + #[test] + fn check_code() { + let packed = pack_with( + BoC::from_root(WALLET_V5R1_CODE_CELL.clone()), + BagOfCellsArgs { + has_idx: false, + has_crc32c: true, + }, + ) + .unwrap(); + + let unpacked: BoC = unpack_fully(packed).unwrap(); + + let got: Cell = unpacked.single_root().unwrap().parse_fully().unwrap(); + assert_eq!(&got, WALLET_V5R1_CODE_CELL.as_ref()); + } +} diff --git a/crates/contracts/src/wallet/v5r1/wallet_v5r1.code b/crates/contracts/src/wallet/v5r1/wallet_v5r1.code new file mode 100644 index 0000000..fe17349 --- /dev/null +++ b/crates/contracts/src/wallet/v5r1/wallet_v5r1.code @@ -0,0 +1 @@ +te6ccgECFAEAAoEAART/APSkE/S88sgLAQIBIAIDAgFIBAUBAvIOAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hAPAgEgBgcCASAICQAZvl8PaiaECAoOuQ+gLAIBbgoLAgFIDA0AGa3OdqJoQCDrkOuF/8AAGa8d9qJoQBDrkOuFj8AAF7Ml+1E0HHXIdcLH4AARsmL7UTQ1woAgAR4g1wsfghBzaWduuvLgin8PAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEAP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKERITAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQAcjDXLAgkji0h8uCS0gDtRNDSAFETuvLQj1RQMJExnAGBAUDXIdcKAPLgjuLIygBYzxbJ7VST8sCN4gAQk1vbMeHXTNA= \ No newline at end of file diff --git a/crates/tlb-ton/src/action.rs b/crates/tlb-ton/src/action.rs new file mode 100644 index 0000000..98fe094 --- /dev/null +++ b/crates/tlb-ton/src/action.rs @@ -0,0 +1,163 @@ +use tlb::{ + bits::{de::BitReaderExt, r#as::NBits, ser::BitWriterExt}, + de::{CellDeserialize, CellParser, CellParserError}, + r#as::Ref, + ser::{CellBuilder, CellBuilderError, CellSerialize}, + Cell, Error, +}; + +use crate::{currency::CurrencyCollection, library::LibRef, message::Message}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OutAction { + /// ```tlb + /// action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any) = OutAction; + /// ``` + SendMsg(SendMsgAction), + + /// ```tlb + /// action_set_code#ad4de08e new_code:^Cell = OutAction; + /// ``` + SetCode(Cell), + + /// ```tlb + /// action_reserve_currency#36e6b809 mode:(## 8) currency:CurrencyCollection = OutAction; + /// ``` + ReserveCurrency(ReserveCurrencyAction), + + /// ```tlb + /// action_change_library#26fa1dd4 mode:(## 7) libref:LibRef = OutAction; + /// ``` + ChangeLibrary(ChangeLibraryAction), +} + +impl OutAction { + const SEND_MSG_PREFIX: u32 = 0x0ec3c86d; + const SET_CODE_PREFIX: u32 = 0xad4de08e; + const RESERVE_CURRENCY_PREFIX: u32 = 0x36e6b809; + const CHANGE_LIBRARY_PREFIX: u32 = 0x26fa1dd4; +} + +impl CellSerialize for OutAction { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + match self { + OutAction::SendMsg(action) => builder.pack(Self::SEND_MSG_PREFIX)?.store(action)?, + OutAction::SetCode(new_code) => builder + .pack(Self::SET_CODE_PREFIX)? + .store_as::<_, Ref>(new_code)?, + OutAction::ReserveCurrency(action) => { + builder.pack(Self::RESERVE_CURRENCY_PREFIX)?.store(action)? + } + OutAction::ChangeLibrary(action) => { + builder.pack(Self::CHANGE_LIBRARY_PREFIX)?.store(action)? + } + }; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for OutAction { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(match parser.unpack()? { + Self::SEND_MSG_PREFIX => Self::SendMsg(parser.parse()?), + Self::SET_CODE_PREFIX => Self::SetCode(parser.parse_as::<_, Ref>()?), + Self::RESERVE_CURRENCY_PREFIX => Self::ReserveCurrency(parser.parse()?), + Self::CHANGE_LIBRARY_PREFIX => Self::ChangeLibrary(parser.parse()?), + prefix => return Err(Error::custom(format!("unknown prefix {prefix:#0x}"))), + }) + } +} + +/// ```tlb +/// action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any) = OutAction; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SendMsgAction { + /// See + pub mode: u8, + pub message: Message, +} + +impl CellSerialize for SendMsgAction +where + T: CellSerialize, + IC: CellSerialize, + ID: CellSerialize, +{ + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder.pack(self.mode)?.store_as::<_, Ref>(&self.message)?; + Ok(()) + } +} + +impl<'de, T, IC, ID> CellDeserialize<'de> for SendMsgAction +where + T: CellDeserialize<'de>, + IC: CellDeserialize<'de>, + ID: CellDeserialize<'de>, +{ + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + mode: parser.unpack()?, + message: parser.parse()?, + }) + } +} + +/// ```tlb +/// action_reserve_currency#36e6b809 mode:(## 8) currency:CurrencyCollection = OutAction; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReserveCurrencyAction { + pub mode: u8, + pub currency: CurrencyCollection, +} + +impl CellSerialize for ReserveCurrencyAction { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder.pack(self.mode)?.store(&self.currency)?; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for ReserveCurrencyAction { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + mode: parser.unpack()?, + currency: parser.parse()?, + }) + } +} + +/// ```tlb +/// action_change_library#26fa1dd4 mode:(## 7) libref:LibRef = OutAction; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ChangeLibraryAction { + pub mode: u8, + pub libref: LibRef, +} + +impl CellSerialize for ChangeLibraryAction +where + R: CellSerialize, +{ + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder + .pack_as::<_, NBits<7>>(self.mode)? + .store(&self.libref)?; + Ok(()) + } +} + +impl<'de, R> CellDeserialize<'de> for ChangeLibraryAction +where + R: CellDeserialize<'de>, +{ + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + mode: parser.unpack_as::<_, NBits<7>>()?, + libref: parser.parse()?, + }) + } +} diff --git a/crates/tlb-ton/src/lib.rs b/crates/tlb-ton/src/lib.rs index 182ab29..c004f60 100644 --- a/crates/tlb-ton/src/lib.rs +++ b/crates/tlb-ton/src/lib.rs @@ -1,9 +1,12 @@ #![doc = include_str!("../README.md")] +pub mod action; mod address; pub mod bin_tree; pub mod boc; pub mod currency; pub mod hashmap; +pub mod library; +pub mod list; pub mod message; pub mod state_init; mod timestamp; diff --git a/crates/tlb-ton/src/library.rs b/crates/tlb-ton/src/library.rs new file mode 100644 index 0000000..ca84063 --- /dev/null +++ b/crates/tlb-ton/src/library.rs @@ -0,0 +1,44 @@ +use tlb::{ + de::{CellDeserialize, CellParser, CellParserError}, + either::Either, + r#as::{Data, Ref}, + ser::{CellBuilder, CellBuilderError, CellSerialize}, + Cell, +}; + +/// ```tlb +/// libref_hash$0 lib_hash:bits256 = LibRef; +/// libref_ref$1 library:^Cell = LibRef; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LibRef { + Hash([u8; 32]), + Ref(R), +} + +impl CellSerialize for LibRef +where + R: CellSerialize, +{ + #[inline] + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder.store_as::<_, Either>(match self { + Self::Hash(hash) => Either::Left(hash), + Self::Ref(library) => Either::Right(library), + })?; + Ok(()) + } +} + +impl<'de, R> CellDeserialize<'de> for LibRef +where + R: CellDeserialize<'de>, +{ + #[inline] + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(match parser.parse_as::<_, Either>()? { + Either::Left(hash) => Self::Hash(hash), + Either::Right(library) => Self::Ref(library), + }) + } +} diff --git a/crates/tlb-ton/src/list.rs b/crates/tlb-ton/src/list.rs new file mode 100644 index 0000000..4ace96a --- /dev/null +++ b/crates/tlb-ton/src/list.rs @@ -0,0 +1,51 @@ +use core::marker::PhantomData; + +use tlb::{ + de::{ + r#as::{CellDeserializeAs, CellDeserializeAsOwned}, + CellParser, CellParserError, + }, + r#as::{Ref, Same}, + ser::{r#as::CellSerializeAs, CellBuilder, CellBuilderError}, + Cell, +}; + +/// ```tlb +/// list_empty$_ {X:Type} = List X 0; +/// list$_ {X:Type} {n:#} prev:^(List X n) v:X = List X (n + 1); +/// ``` +pub struct List(PhantomData); + +impl CellSerializeAs for List +where + for<'a> &'a T: IntoIterator, + for<'a> As: CellSerializeAs<<&'a T as IntoIterator>::Item>, +{ + #[inline] + fn store_as(source: &T, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder.store(source.into_iter().try_fold(Cell::new(), |prev, v| { + let mut list = Cell::builder(); + list.store_as::<_, Ref>(prev)?.store_as::<_, As>(v)?; + Ok(list.into_cell()) + })?)?; + Ok(()) + } +} + +impl<'de, T, As> CellDeserializeAs<'de, Vec> for List +where + As: CellDeserializeAsOwned, +{ + #[inline] + fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { + let mut v = Vec::new(); + let mut cell: Cell = parser.parse()?; + while !cell.references.is_empty() { + let mut p = cell.parser(); + v.push(p.parse_as::<_, As>()?); + cell = p.parse_as::<_, Ref>()?; + } + v.reverse(); + Ok(v) + } +} diff --git a/crates/tlb/src/as/data.rs b/crates/tlb/src/as/data.rs index c450507..ea0599d 100644 --- a/crates/tlb/src/as/data.rs +++ b/crates/tlb/src/as/data.rs @@ -57,7 +57,7 @@ use super::Same; /// builder.store_as::<_, Data>(v)?; /// # let cell = builder.into_cell(); /// # let mut parser = cell.parser(); -/// # let got = +/// # let got = /// // parse as binary data /// parser.parse_as::()?; /// # assert_eq!(got, v); diff --git a/crates/tlb/src/cell.rs b/crates/tlb/src/cell.rs index a6f261d..745b5ee 100644 --- a/crates/tlb/src/cell.rs +++ b/crates/tlb/src/cell.rs @@ -215,7 +215,6 @@ impl Debug for Cell { #[cfg(test)] mod tests { - use hex_literal::hex; use crate::{ diff --git a/crates/tlb/src/de/parser.rs b/crates/tlb/src/de/parser.rs index fccafda..1f76acf 100644 --- a/crates/tlb/src/de/parser.rs +++ b/crates/tlb/src/de/parser.rs @@ -158,10 +158,30 @@ impl<'de> CellParser<'de> { self.pop_reference()?.parse_fully_as_with::(args) } + #[inline] + pub fn bits_left(&self) -> usize { + self.data.len() + } + + #[inline] + pub fn no_bits_left(&self) -> bool { + self.bits_left() == 0 + } + + #[inline] + pub const fn references_left(&self) -> usize { + self.references.len() + } + + #[inline] + pub const fn no_references_left(&self) -> bool { + self.references_left() == 0 + } + /// Returns whether this parser has no more data and references. #[inline] pub fn is_empty(&self) -> bool { - self.data.is_empty() && self.references.is_empty() + self.no_bits_left() && self.no_references_left() } /// Returns an error if this parser has more data or references. From 116c41e09116c01828b2ee2212862e2f3070c99f Mon Sep 17 00:00:00 2001 From: Arseny Mitin Date: Tue, 20 Aug 2024 23:46:13 +0400 Subject: [PATCH 2/3] refactor(wallet): split KeyPair (signer) and the wallet contract structs --- Cargo.lock | 1 + crates/contracts/Cargo.toml | 1 + crates/contracts/src/wallet/mnemonic.rs | 12 +- crates/contracts/src/wallet/mod.rs | 144 +++++++++++------------- crates/contracts/src/wallet/signer.rs | 47 ++++++++ crates/contracts/src/wallet/v5r1/mod.rs | 2 +- crates/contracts/src/wallet/version.rs | 44 ++++++++ crates/tlb-ton/src/address.rs | 11 +- 8 files changed, 175 insertions(+), 87 deletions(-) create mode 100644 crates/contracts/src/wallet/signer.rs create mode 100644 crates/contracts/src/wallet/version.rs diff --git a/Cargo.lock b/Cargo.lock index 2ce5989..7ad6084 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -742,6 +742,7 @@ dependencies = [ "chrono", "hex-literal", "hmac", + "impl-tools", "lazy_static", "nacl", "num-bigint", diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index 5fd274f..627a1f2 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -15,6 +15,7 @@ tlb-ton.workspace = true anyhow.workspace = true bitvec.workspace = true chrono.workspace = true +impl-tools.workspace = true lazy_static.workspace = true num-bigint.workspace = true diff --git a/crates/contracts/src/wallet/mnemonic.rs b/crates/contracts/src/wallet/mnemonic.rs index a2238bf..2135611 100644 --- a/crates/contracts/src/wallet/mnemonic.rs +++ b/crates/contracts/src/wallet/mnemonic.rs @@ -7,7 +7,7 @@ use nacl::sign::generate_keypair; use pbkdf2::{password_hash::Output, pbkdf2_hmac}; use sha2::Sha512; -pub use nacl::sign::Keypair; +use super::KeyPair; lazy_static! { static ref WORDLIST_EN: HashSet<&'static str> = include_str!("./wordlist_en.txt") @@ -21,12 +21,12 @@ lazy_static! { /// /// ```rust /// # use hex_literal::hex; -/// # use ton_contracts::wallet::mnemonic::{Keypair, Mnemonic}; +/// # use ton_contracts::wallet::{KeyPair, mnemonic::Mnemonic}; /// let mnemonic: Mnemonic = "dose ice enrich trigger test dove century still betray gas diet dune use other base gym mad law immense village world example praise game" /// .parse() /// .unwrap(); -/// let kp: Keypair = mnemonic.generate_keypair(None).unwrap(); -/// # assert_eq!(kp.skey, hex!("119dcf2840a3d56521d260b2f125eedc0d4f3795b9e627269a4b5a6dca8257bdc04ad1885c127fe863abb00752fa844e6439bb04f264d70de7cea580b32637ab")); +/// let kp: KeyPair = mnemonic.generate_keypair(None).unwrap(); +/// # assert_eq!(kp.secret_key, hex!("119dcf2840a3d56521d260b2f125eedc0d4f3795b9e627269a4b5a6dca8257bdc04ad1885c127fe863abb00752fa844e6439bb04f264d70de7cea580b32637ab")); /// ``` #[derive(Debug, Clone)] pub struct Mnemonic([&'static str; 24]); @@ -35,7 +35,7 @@ impl Mnemonic { const PBKDF_ITERATIONS: u32 = 100000; /// Generate [`Keypair`] with optional password - pub fn generate_keypair(&self, password: impl Into>) -> anyhow::Result { + pub fn generate_keypair(&self, password: impl Into>) -> anyhow::Result { let entropy = self.entropy(password)?; let seed = Self::pbkdf2_sha512( entropy.as_slice(), @@ -43,7 +43,7 @@ impl Mnemonic { Self::PBKDF_ITERATIONS, 64, )?; - Ok(generate_keypair(&seed[0..32])) + Ok(generate_keypair(&seed[0..32]).into()) } fn entropy(&self, password: impl Into>) -> anyhow::Result<[u8; 64]> { diff --git a/crates/contracts/src/wallet/mod.rs b/crates/contracts/src/wallet/mod.rs index b8eb1e1..eb37fab 100644 --- a/crates/contracts/src/wallet/mod.rs +++ b/crates/contracts/src/wallet/mod.rs @@ -1,15 +1,21 @@ //! TON [Wallet](https://docs.ton.org/participate/wallets/contracts) pub mod mnemonic; +mod signer; pub mod v4r2; pub mod v5r1; +mod version; -use std::{marker::PhantomData, sync::Arc}; +pub use self::{signer::*, version::*}; + +use core::marker::PhantomData; +use std::sync::Arc; -use anyhow::anyhow; use chrono::{DateTime, Utc}; -use nacl::sign::{signature, Keypair, PUBLIC_KEY_LENGTH}; use num_bigint::BigUint; -use tlb::{ser::CellSerialize, Cell}; +use tlb::{ + ser::{CellBuilderError, CellSerializeExt}, + Cell, +}; use tlb_ton::{ action::SendMsgAction, message::{CommonMsgInfo, ExternalInMsgInfo, Message}, @@ -22,7 +28,12 @@ pub const DEFAULT_WALLET_ID: u32 = 0x29a9a317; /// Generic wallet for signing messages /// /// ```rust -/// # use ton_contracts::wallet::{mnemonic::Mnemonic, Wallet, v4r2::V4R2}; +/// # use ton_contracts::wallet::{ +/// # mnemonic::Mnemonic, +/// # KeyPair, +/// # Wallet, +/// # v4r2::V4R2, +/// # }; /// let mnemonic: Mnemonic = "jewel loop vast intact snack drip fatigue lunch erode green indoor balance together scrub hen monster hour narrow banner warfare increase panel sound spell" /// .parse() /// .unwrap(); @@ -37,7 +48,7 @@ pub const DEFAULT_WALLET_ID: u32 = 0x29a9a317; pub struct Wallet { address: MsgAddress, wallet_id: u32, - key_pair: Keypair, + keypair: KeyPair, _phantom: PhantomData, } @@ -45,30 +56,34 @@ impl Wallet where V: WalletVersion, { - // TODO - // pub fn derive_state(workchain_id: i32, state: ) + #[inline] + pub const fn new(address: MsgAddress, keypair: KeyPair, wallet_id: u32) -> Self { + Self { + address, + wallet_id, + keypair, + _phantom: PhantomData, + } + } /// Derive wallet from its workchain, keypair and id - pub fn derive(workchain_id: i32, key_pair: Keypair, wallet_id: u32) -> anyhow::Result { - Ok(Self { - address: MsgAddress::derive( - workchain_id, - StateInit::<_, _> { - code: Some(V::code()), - data: Some(V::init_data(wallet_id, key_pair.pkey)), - ..Default::default() - } - .normalize()?, - )?, + #[inline] + pub fn derive( + workchain_id: i32, + keypair: KeyPair, + wallet_id: u32, + ) -> Result { + Ok(Self::new( + MsgAddress::derive(workchain_id, V::state_init(wallet_id, keypair.public_key))?, + keypair, wallet_id, - key_pair, - _phantom: PhantomData, - }) + )) } /// Shortcut for [`Wallet::derive()`] with default workchain and wallet id - pub fn derive_default(key_pair: Keypair) -> anyhow::Result { - Self::derive(0, key_pair, DEFAULT_WALLET_ID) + #[inline] + pub fn derive_default(keypair: KeyPair) -> Result { + Self::derive(0, keypair, DEFAULT_WALLET_ID) } /// Address of the wallet @@ -83,17 +98,25 @@ where self.wallet_id } + #[inline] + pub const fn public_key(&self) -> &[u8; PUBLIC_KEY_LENGTH] { + &self.keypair.public_key + } + + /// Create external body for this wallet. + #[inline] + pub fn create_sign_body( + &self, + expire_at: DateTime, + seqno: u32, + msgs: impl IntoIterator, + ) -> V::SignBody { + V::create_sign_body(self.wallet_id, expire_at, seqno, msgs) + } + #[inline] pub fn sign(&self, msg: impl AsRef<[u8]>) -> anyhow::Result<[u8; 64]> { - signature(msg.as_ref(), self.key_pair.skey.as_slice()) - .map_err(|e| anyhow!("{}", e.message))? - .try_into() - .map_err(|sig: Vec<_>| { - anyhow!( - "got signature of a wrong size, expected 64, got: {}", - sig.len() - ) - }) + self.keypair.sign(msg) } /// Shortcut to [create](Wallet::create_external_body), @@ -111,6 +134,7 @@ where /// # use ton_contracts::wallet::{ /// # mnemonic::Mnemonic, /// # v5r1::V5R1, + /// # KeyPair, /// # Wallet, /// # }; /// # @@ -159,23 +183,11 @@ where Ok(wrapped) } - /// Create external body for this wallet. - #[inline] - pub fn create_sign_body( - &self, - expire_at: DateTime, - seqno: u32, - msgs: impl IntoIterator, - ) -> V::SignBody { - V::create_sign_body(self.wallet_id, expire_at, seqno, msgs) - } - - /// Sign body from [`.create_external_body()`](Wallet::create_external_body) + /// Sign body from [`.create_sign_body()`](Wallet::create_sign_body) /// using this wallet's private key + #[inline] pub fn sign_body(&self, msg: &V::SignBody) -> anyhow::Result<[u8; 64]> { - let mut b = Cell::builder(); - b.store(msg)?; - self.sign(b.into_cell().hash()) + self.sign(msg.to_cell()?.hash()) } /// Wrap signed body from [`.sign_body()`](Wallet::sign_body) in a message @@ -189,40 +201,16 @@ where Message { info: CommonMsgInfo::ExternalIn(ExternalInMsgInfo { src: MsgAddress::NULL, - dst: self.address, + dst: self.address(), import_fee: BigUint::ZERO, }), - init: state_init.then(|| StateInit::<_, _> { - code: Some(V::code()), - data: Some(V::init_data(self.wallet_id, self.key_pair.pkey)), - ..Default::default() - }), + init: state_init.then(|| self.state_init()), body, } } -} - -/// Version of [`Wallet`] -pub trait WalletVersion { - type Data: CellSerialize; - type SignBody: CellSerialize; - type ExternalMsgBody: CellSerialize; - - /// Code of the wallet for use with [`StateInit`] - fn code() -> Arc; - - /// Init data for use with [`StateInit`] - fn init_data(wallet_id: u32, pubkey: [u8; PUBLIC_KEY_LENGTH]) -> Self::Data; - - /// Creates body for further signing with - /// [`.wrap_signed_external()`](WalletVersion::wrap_signed_external) - fn create_sign_body( - wallet_id: u32, - expire_at: DateTime, - seqno: u32, - msgs: impl IntoIterator, - ) -> Self::SignBody; - /// Wraps signed body into external [`Message::body`] - fn wrap_signed_external(body: Self::SignBody, signature: [u8; 64]) -> Self::ExternalMsgBody; + #[inline] + pub fn state_init(&self) -> StateInit, V::Data> { + V::state_init(self.wallet_id(), *self.public_key()) + } } diff --git a/crates/contracts/src/wallet/signer.rs b/crates/contracts/src/wallet/signer.rs new file mode 100644 index 0000000..db7237c --- /dev/null +++ b/crates/contracts/src/wallet/signer.rs @@ -0,0 +1,47 @@ +use anyhow::anyhow; +use nacl::sign::{signature, Keypair}; + +pub use nacl::sign::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct KeyPair { + /// Secret key of this pair. + pub secret_key: [u8; SECRET_KEY_LENGTH], + + /// Public key of this pair. + pub public_key: [u8; PUBLIC_KEY_LENGTH], +} + +impl From for KeyPair { + fn from(Keypair { skey, pkey }: Keypair) -> Self { + Self { + secret_key: skey, + public_key: pkey, + } + } +} + +impl KeyPair { + #[inline] + pub const fn new( + secret_key: [u8; SECRET_KEY_LENGTH], + public_key: [u8; PUBLIC_KEY_LENGTH], + ) -> Self { + Self { + secret_key, + public_key, + } + } + + pub fn sign(&self, msg: impl AsRef<[u8]>) -> anyhow::Result<[u8; 64]> { + signature(msg.as_ref(), self.secret_key.as_slice()) + .map_err(|e| anyhow!("{}", e.message))? + .try_into() + .map_err(|sig: Vec<_>| { + anyhow!( + "got signature of a wrong size, expected 64, got: {}", + sig.len() + ) + }) + } +} diff --git a/crates/contracts/src/wallet/v5r1/mod.rs b/crates/contracts/src/wallet/v5r1/mod.rs index dfe97c7..2e039e1 100644 --- a/crates/contracts/src/wallet/v5r1/mod.rs +++ b/crates/contracts/src/wallet/v5r1/mod.rs @@ -264,7 +264,7 @@ pub struct WalletV5R1SignedRequest { impl CellSerialize for WalletV5R1SignedRequest { fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - builder.store(&self.body)?.pack(&self.signature)?; + builder.store(&self.body)?.pack(self.signature)?; Ok(()) } } diff --git a/crates/contracts/src/wallet/version.rs b/crates/contracts/src/wallet/version.rs new file mode 100644 index 0000000..fea48c8 --- /dev/null +++ b/crates/contracts/src/wallet/version.rs @@ -0,0 +1,44 @@ +use std::sync::Arc; + +use chrono::{DateTime, Utc}; +use tlb::{ser::CellSerialize, Cell}; +use tlb_ton::{action::SendMsgAction, state_init::StateInit}; + +use super::PUBLIC_KEY_LENGTH; + +/// Version of [`Wallet`] +pub trait WalletVersion { + type Data: CellSerialize; + type SignBody: CellSerialize; + type ExternalMsgBody: CellSerialize; + + /// Code of the wallet for use with [`StateInit`] + fn code() -> Arc; + + /// Init data for use with [`StateInit`] + fn init_data(wallet_id: u32, pubkey: [u8; PUBLIC_KEY_LENGTH]) -> Self::Data; + + /// Creates body for further signing with + /// [`.wrap_signed_external()`](WalletVersion::wrap_signed_external) + fn create_sign_body( + wallet_id: u32, + expire_at: DateTime, + seqno: u32, + msgs: impl IntoIterator, + ) -> Self::SignBody; + + /// Wraps signed body into external [`Message::body`] + fn wrap_signed_external(body: Self::SignBody, signature: [u8; 64]) -> Self::ExternalMsgBody; + + #[inline] + fn state_init( + wallet_id: u32, + pubkey: [u8; PUBLIC_KEY_LENGTH], + ) -> StateInit, Self::Data> { + StateInit { + code: Some(Self::code()), + data: Some(Self::init_data(wallet_id, pubkey)), + ..Default::default() + } + } +} diff --git a/crates/tlb-ton/src/address.rs b/crates/tlb-ton/src/address.rs index 07b86cf..448a980 100644 --- a/crates/tlb-ton/src/address.rs +++ b/crates/tlb-ton/src/address.rs @@ -14,7 +14,7 @@ use tlb::{ r#as::NBits, ser::{BitPack, BitWriter, BitWriterExt}, }, - ser::{CellBuilderError, CellSerializeExt}, + ser::{CellBuilderError, CellSerialize, CellSerializeExt}, Error, ResultExt, StringError, }; @@ -54,7 +54,14 @@ impl MsgAddress { /// [Derive](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract) /// [`MsgAddress`] of a smart-contract by its workchain and [`StateInit`] #[inline] - pub fn derive(workchain_id: i32, state_init: StateInit) -> Result { + pub fn derive( + workchain_id: i32, + state_init: StateInit, + ) -> Result + where + C: CellSerialize, + D: CellSerialize, + { Ok(Self { workchain_id, address: state_init.to_cell()?.hash(), From d3ef2cc6f5c5d6c3c8d502074e55df656471f6e4 Mon Sep 17 00:00:00 2001 From: Arseny Mitin Date: Wed, 21 Aug 2024 02:20:40 +0400 Subject: [PATCH 3/3] refactor: deserialize Vec with args --- crates/bits/src/as/bits.rs | 37 ++++++++++++- crates/bits/src/as/integer.rs | 4 +- crates/bits/src/de/args/mod.rs | 7 ++- crates/bits/src/de/reader.rs | 19 +------ crates/contracts/src/wallet/v5r1/mod.rs | 69 +++++++++++++++++-------- crates/tlb-ton/src/action.rs | 26 +++++++--- crates/tlb-ton/src/address.rs | 59 +++++++++++++++++++-- crates/tlb-ton/src/boc.rs | 11 +++- crates/tlb-ton/src/list.rs | 25 +++++---- crates/tlb-ton/src/message.rs | 30 +++++++---- crates/tlb/src/lib.rs | 19 +++---- crates/tlb/src/ser/builder.rs | 9 ++++ 12 files changed, 224 insertions(+), 91 deletions(-) diff --git a/crates/bits/src/as/bits.rs b/crates/bits/src/as/bits.rs index 63ac7e2..444a95a 100644 --- a/crates/bits/src/as/bits.rs +++ b/crates/bits/src/as/bits.rs @@ -1,10 +1,12 @@ -use bitvec::{order::Msb0, slice::BitSlice, view::AsBits}; +use bitvec::{order::Msb0, slice::BitSlice, vec::BitVec, view::AsBits}; use crate::{ de::{r#as::BitUnpackAs, BitReader, BitReaderExt}, ser::{r#as::BitPackAs, BitPack, BitWriter, BitWriterExt}, }; +use super::args::NoArgs; + /// **Ser**ialize value by taking a reference to [`BitSlice`] on it. pub struct AsBitSlice; @@ -40,6 +42,37 @@ where /// **De**/**ser**ialize value from/into exactly `N` bits. pub struct NBits; +/// **De**/**ser**ialize bits by prefixing its length with `N`-bit integer. +pub struct VarBits; + +impl BitPackAs for VarBits +where + T: AsRef>, +{ + #[inline] + fn pack_as(source: &T, mut writer: W) -> Result<(), W::Error> + where + W: BitWriter, + { + let source = source.as_ref(); + writer + .pack_as::<_, NBits>(source.len())? + .pack(source)?; + Ok(()) + } +} + +impl BitUnpackAs> for VarBits { + #[inline] + fn unpack_as(mut reader: R) -> Result, R::Error> + where + R: BitReader, + { + let num_bits = reader.unpack_as::<_, NBits>()?; + reader.unpack_with(num_bits) + } +} + /// **De**/**ser**ialize bytes by prefixing its length with `N`-bit integer. pub struct VarBytes; @@ -67,6 +100,6 @@ impl BitUnpackAs> for VarBytes>()?; - reader.read_bytes_vec(num_bytes) + reader.unpack_as_with::<_, Vec>>((num_bytes, ())) } } diff --git a/crates/bits/src/as/integer.rs b/crates/bits/src/as/integer.rs index d6ad22f..7d1f3c6 100644 --- a/crates/bits/src/as/integer.rs +++ b/crates/bits/src/as/integer.rs @@ -44,7 +44,7 @@ impl BitUnpackAs for NBits { where R: BitReader, { - let mut bits = reader.read_bitvec(BITS)?; + let mut bits: BitVec = reader.unpack_with(BITS)?; let total_bits = (BITS + 7) & !7; bits.resize(total_bits, false); bits.shift_right(total_bits - BITS); @@ -81,7 +81,7 @@ impl BitUnpackAs for NBits { where R: BitReader, { - let mut bits = reader.read_bitvec(BITS)?; + let mut bits: BitVec = reader.unpack_with(BITS)?; let total_bits = (BITS + 7) & !7; bits.resize(total_bits, false); bits.shift_right(total_bits - BITS); diff --git a/crates/bits/src/de/args/mod.rs b/crates/bits/src/de/args/mod.rs index 9c6ad05..1dd2cc6 100644 --- a/crates/bits/src/de/args/mod.rs +++ b/crates/bits/src/de/args/mod.rs @@ -185,12 +185,17 @@ where } impl BitUnpackWithArgs for BitVec { + /// length type Args = usize; + #[inline] fn unpack_with(mut reader: R, len: Self::Args) -> Result where R: BitReader, { - reader.read_bitvec(len) + let mut dst = BitVec::with_capacity(len); + dst.resize(len, false); + reader.read_bits_into(&mut dst)?; + Ok(dst) } } diff --git a/crates/bits/src/de/reader.rs b/crates/bits/src/de/reader.rs index 23db4dd..0bbd287 100644 --- a/crates/bits/src/de/reader.rs +++ b/crates/bits/src/de/reader.rs @@ -1,6 +1,6 @@ use core::iter; -use ::bitvec::{order::Msb0, slice::BitSlice, vec::BitVec, view::AsMutBits}; +use ::bitvec::{order::Msb0, slice::BitSlice, view::AsMutBits}; use impl_tools::autoimpl; use crate::{ @@ -46,15 +46,6 @@ pub trait BitReader { /// Extension helper for [`BitReader`]. pub trait BitReaderExt: BitReader { - /// Reads `n` bits and returns newly created [`BitVec`] - #[inline] - fn read_bitvec(&mut self, n: usize) -> Result, Self::Error> { - let mut dst = BitVec::with_capacity(n); - dst.resize(n, false); - self.read_bits_into(&mut dst)?; - Ok(dst) - } - /// Reads `dst.len()` bytes into given byte slice #[inline] fn read_bytes_into(&mut self, mut dst: impl AsMut<[u8]>) -> Result<(), Self::Error> { @@ -69,14 +60,6 @@ pub trait BitReaderExt: BitReader { Ok(arr) } - /// Read `n` bytes and return [`Vec`] - #[inline] - fn read_bytes_vec(&mut self, n: usize) -> Result, Self::Error> { - let mut v = vec![0; n]; - self.read_bytes_into(&mut v)?; - Ok(v) - } - /// Unpack value using its [`BitUnpack`] implementation #[inline] fn unpack(&mut self) -> Result diff --git a/crates/contracts/src/wallet/v5r1/mod.rs b/crates/contracts/src/wallet/v5r1/mod.rs index 2e039e1..a776060 100644 --- a/crates/contracts/src/wallet/v5r1/mod.rs +++ b/crates/contracts/src/wallet/v5r1/mod.rs @@ -8,7 +8,7 @@ use tlb::{ de::{CellDeserialize, CellParser, CellParserError}, r#as::{Data, NoArgs}, ser::{CellBuilder, CellBuilderError, CellSerialize}, - Cell, Error, + Cell, Error, ResultExt, }; use tlb_ton::{ action::{OutAction, SendMsgAction}, @@ -67,7 +67,7 @@ impl WalletVersion for V5R1 { msg_seqno, inner: WalletV5R1InnerRequest { out_actions: msgs.into_iter().map(OutAction::SendMsg).collect(), - other_actions: [].into(), + extended: [].into(), }, } } @@ -126,7 +126,7 @@ impl<'de> CellDeserialize<'de> for WalletV5R1Data { #[derive(Debug, Clone, PartialEq, Eq)] pub struct WalletV5R1InnerRequest { pub out_actions: Vec, - pub other_actions: Vec, + pub extended: Vec, } impl CellSerialize for WalletV5R1InnerRequest { @@ -135,9 +135,7 @@ impl CellSerialize for WalletV5R1InnerRequest { .store_as::<_, Option<&List>>( Some(&self.out_actions).filter(|actions| !actions.is_empty()), )? - .store_as::<_, Option<&List>>( - Some(&self.other_actions).filter(|other| !other.is_empty()), - )?; + .store_as::<_, Option<&List>>(Some(&self.extended).filter(|other| !other.is_empty()))?; Ok(()) } } @@ -145,8 +143,14 @@ impl CellSerialize for WalletV5R1InnerRequest { impl<'de> CellDeserialize<'de> for WalletV5R1InnerRequest { fn parse(parser: &mut CellParser<'de>) -> Result> { Ok(Self { - out_actions: parser.parse_as::<_, Option>()?.unwrap_or_default(), - other_actions: parser.parse_as::<_, Option>()?.unwrap_or_default(), + out_actions: parser + .parse_as::<_, Option>() + .context("out_actions")? + .unwrap_or_default(), + extended: parser + .parse_as::<_, Option>() + .context("extended")? + .unwrap_or_default(), }) } } @@ -288,10 +292,7 @@ pub enum WalletV5R1MsgBody { /// ```tlb /// internal_extension#6578746e query_id:(## 64) inner:InnerRequest = InternalMsgBody; /// ``` - InternalExtension { - query_id: u64, - inner: WalletV5R1InnerRequest, - }, + InternalExtension(InternalExtensionWalletV5R1MsgBody), /// ```tlb /// external_signed#7369676e signed:SignedRequest = ExternalMsgBody; @@ -309,10 +310,9 @@ impl CellSerialize for WalletV5R1MsgBody { fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { match self { Self::InternalSigned(msg) => builder.pack(Self::INTERNAL_SIGNED_PREFIX)?.store(msg)?, - Self::InternalExtension { query_id, inner } => builder - .pack(Self::INTERNAL_EXTENSION_PREFIX)? - .pack(query_id)? - .store(inner)?, + Self::InternalExtension(msg) => { + builder.pack(Self::INTERNAL_EXTENSION_PREFIX)?.store(msg)? + } Self::ExternalSigned(msg) => builder.pack(Self::EXTERNAL_SIGNED_PREFIX)?.store(msg)?, }; Ok(()) @@ -322,17 +322,42 @@ impl CellSerialize for WalletV5R1MsgBody { impl<'de> CellDeserialize<'de> for WalletV5R1MsgBody { fn parse(parser: &mut CellParser<'de>) -> Result> { Ok(match parser.unpack()? { - Self::INTERNAL_SIGNED_PREFIX => Self::InternalSigned(parser.parse()?), - Self::INTERNAL_EXTENSION_PREFIX => Self::InternalExtension { - query_id: parser.unpack()?, - inner: parser.parse()?, - }, - Self::EXTERNAL_SIGNED_PREFIX => Self::ExternalSigned(parser.parse()?), + Self::INTERNAL_SIGNED_PREFIX => { + Self::InternalSigned(parser.parse().context("internal_signed")?) + } + Self::INTERNAL_EXTENSION_PREFIX => { + Self::InternalExtension(parser.parse().context("internal_extension")?) + } + Self::EXTERNAL_SIGNED_PREFIX => { + Self::ExternalSigned(parser.parse().context("external_signed")?) + } prefix => return Err(Error::custom(format!("unknown prefix: {prefix:#0x}"))), }) } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InternalExtensionWalletV5R1MsgBody { + query_id: u64, + inner: WalletV5R1InnerRequest, +} + +impl CellSerialize for InternalExtensionWalletV5R1MsgBody { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder.pack(self.query_id)?.store(&self.inner)?; + Ok(()) + } +} + +impl<'de> CellDeserialize<'de> for InternalExtensionWalletV5R1MsgBody { + fn parse(parser: &mut CellParser<'de>) -> Result> { + Ok(Self { + query_id: parser.unpack()?, + inner: parser.parse()?, + }) + } +} + #[cfg(test)] mod tests { use tlb::bits::{de::unpack_fully, ser::pack_with}; diff --git a/crates/tlb-ton/src/action.rs b/crates/tlb-ton/src/action.rs index 98fe094..ee2b575 100644 --- a/crates/tlb-ton/src/action.rs +++ b/crates/tlb-ton/src/action.rs @@ -3,7 +3,7 @@ use tlb::{ de::{CellDeserialize, CellParser, CellParserError}, r#as::Ref, ser::{CellBuilder, CellBuilderError, CellSerialize}, - Cell, Error, + Cell, Error, ResultExt, }; use crate::{currency::CurrencyCollection, library::LibRef, message::Message}; @@ -39,6 +39,7 @@ impl OutAction { } impl CellSerialize for OutAction { + #[inline] fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { match self { OutAction::SendMsg(action) => builder.pack(Self::SEND_MSG_PREFIX)?.store(action)?, @@ -57,12 +58,19 @@ impl CellSerialize for OutAction { } impl<'de> CellDeserialize<'de> for OutAction { + #[inline] fn parse(parser: &mut CellParser<'de>) -> Result> { Ok(match parser.unpack()? { - Self::SEND_MSG_PREFIX => Self::SendMsg(parser.parse()?), - Self::SET_CODE_PREFIX => Self::SetCode(parser.parse_as::<_, Ref>()?), - Self::RESERVE_CURRENCY_PREFIX => Self::ReserveCurrency(parser.parse()?), - Self::CHANGE_LIBRARY_PREFIX => Self::ChangeLibrary(parser.parse()?), + Self::SEND_MSG_PREFIX => Self::SendMsg(parser.parse().context("action_send_msg")?), + Self::SET_CODE_PREFIX => { + Self::SetCode(parser.parse_as::<_, Ref>().context("action_set_code")?) + } + Self::RESERVE_CURRENCY_PREFIX => { + Self::ReserveCurrency(parser.parse().context("action_reserve_currency")?) + } + Self::CHANGE_LIBRARY_PREFIX => { + Self::ChangeLibrary(parser.parse().context("action_change_library")?) + } prefix => return Err(Error::custom(format!("unknown prefix {prefix:#0x}"))), }) } @@ -84,6 +92,7 @@ where IC: CellSerialize, ID: CellSerialize, { + #[inline] fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { builder.pack(self.mode)?.store_as::<_, Ref>(&self.message)?; Ok(()) @@ -96,10 +105,11 @@ where IC: CellDeserialize<'de>, ID: CellDeserialize<'de>, { + #[inline] fn parse(parser: &mut CellParser<'de>) -> Result> { Ok(Self { mode: parser.unpack()?, - message: parser.parse()?, + message: parser.parse_as::<_, Ref>()?, }) } } @@ -114,6 +124,7 @@ pub struct ReserveCurrencyAction { } impl CellSerialize for ReserveCurrencyAction { + #[inline] fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { builder.pack(self.mode)?.store(&self.currency)?; Ok(()) @@ -121,6 +132,7 @@ impl CellSerialize for ReserveCurrencyAction { } impl<'de> CellDeserialize<'de> for ReserveCurrencyAction { + #[inline] fn parse(parser: &mut CellParser<'de>) -> Result> { Ok(Self { mode: parser.unpack()?, @@ -142,6 +154,7 @@ impl CellSerialize for ChangeLibraryAction where R: CellSerialize, { + #[inline] fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { builder .pack_as::<_, NBits<7>>(self.mode)? @@ -154,6 +167,7 @@ impl<'de, R> CellDeserialize<'de> for ChangeLibraryAction where R: CellDeserialize<'de>, { + #[inline] fn parse(parser: &mut CellParser<'de>) -> Result> { Ok(Self { mode: parser.unpack_as::<_, NBits<7>>()?, diff --git a/crates/tlb-ton/src/address.rs b/crates/tlb-ton/src/address.rs index 448a980..2d11dc0 100644 --- a/crates/tlb-ton/src/address.rs +++ b/crates/tlb-ton/src/address.rs @@ -10,8 +10,9 @@ use crc::Crc; use strum::Display; use tlb::{ bits::{ + bitvec::{order::Msb0, vec::BitVec}, de::{BitReader, BitReaderExt, BitUnpack}, - r#as::NBits, + r#as::{NBits, VarBits}, ser::{BitPack, BitWriter, BitWriterExt}, }, ser::{CellBuilderError, CellSerialize, CellSerializeExt}, @@ -252,7 +253,7 @@ impl BitPack for MsgAddress { writer .pack(MsgAddressTag::Std)? // anycast:(Maybe Anycast) - .pack(false)? + .pack::>(None)? // workchain_id:int8 .pack(self.workchain_id as i8)? // address:bits256 @@ -271,7 +272,8 @@ impl BitUnpack for MsgAddress { match reader.unpack()? { MsgAddressTag::Null => Ok(Self::NULL), MsgAddressTag::Std => { - reader.skip(1)?; // anycast:(Maybe Anycast) + // anycast:(Maybe Anycast) + let _: Option = reader.unpack()?; Ok(Self { // workchain_id:int8 workchain_id: reader.unpack::()? as i32, @@ -279,6 +281,24 @@ impl BitUnpack for MsgAddress { address: reader.unpack()?, }) } + MsgAddressTag::Var => { + // anycast:(Maybe Anycast) + let _: Option = reader.unpack()?; + // addr_len:(## 9) + let addr_len: u16 = reader.unpack_as::<_, NBits<9>>()?; + if addr_len != 256 { + // TODO + return Err(Error::custom(format!( + "only 256-bit addresses are supported for addr_var$11, got {addr_len} bits" + ))); + } + Ok(Self { + // workchain_id:int32 + workchain_id: reader.unpack()?, + // address:(bits addr_len) + address: reader.unpack()?, + }) + } tag => Err(Error::custom(format!("unsupported address tag: {tag}"))), } } @@ -324,6 +344,39 @@ impl BitUnpack for MsgAddressTag { } } +/// ```tlb +/// anycast_info$_ depth:(#<= 30) { depth >= 1 } rewrite_pfx:(bits depth) = Anycast; +/// ``` +pub struct Anycast { + pub rewrite_pfx: BitVec, +} + +impl BitPack for Anycast { + fn pack(&self, mut writer: W) -> Result<(), W::Error> + where + W: BitWriter, + { + if self.rewrite_pfx.is_empty() { + return Err(Error::custom("depth >= 1")); + } + writer.pack_as::<_, VarBits<5>>(&self.rewrite_pfx)?; + Ok(()) + } +} + +impl BitUnpack for Anycast { + fn unpack(mut reader: R) -> Result + where + R: BitReader, + { + let rewrite_pfx = reader.unpack_as::<_, VarBits<5>>()?; + if rewrite_pfx.is_empty() { + return Err(Error::custom("depth >= 1")); + } + Ok(Self { rewrite_pfx }) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/tlb-ton/src/boc.rs b/crates/tlb-ton/src/boc.rs index cd4e73a..a1c37d4 100644 --- a/crates/tlb-ton/src/boc.rs +++ b/crates/tlb-ton/src/boc.rs @@ -1,6 +1,7 @@ //! Collection of types related to [Bag Of Cells](https://docs.ton.org/develop/data-formats/cell-boc#bag-of-cells) use std::{ collections::{HashMap, HashSet}, + fmt::Debug, sync::Arc, }; @@ -53,7 +54,7 @@ pub type BoC = BagOfCells; /// # Ok(()) /// # } /// ``` -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct BagOfCells { roots: Vec>, } @@ -111,6 +112,12 @@ impl BagOfCells { } } +impl Debug for BagOfCells { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_set().entries(&self.roots).finish() + } +} + /// [`BitPackWithArgs::Args`] for [`BagOfCells`] #[derive(Debug, Clone, Copy, Default)] pub struct BagOfCellsArgs { @@ -493,7 +500,7 @@ impl BitUnpackWithArgs for RawCell { let num_bytes: usize = ((bits_descriptor >> 1) + (bits_descriptor & 1)) as usize; let full_bytes = (bits_descriptor & 1) == 0; - let mut data = reader.read_bitvec(num_bytes * 8)?; + let mut data: BitVec = reader.unpack_with(num_bytes * 8)?; if !data.is_empty() && !full_bytes { let trailing_zeros = data.trailing_zeros(); if trailing_zeros >= 8 { diff --git a/crates/tlb-ton/src/list.rs b/crates/tlb-ton/src/list.rs index 4ace96a..dc8c155 100644 --- a/crates/tlb-ton/src/list.rs +++ b/crates/tlb-ton/src/list.rs @@ -1,13 +1,10 @@ use core::marker::PhantomData; use tlb::{ - de::{ - r#as::{CellDeserializeAs, CellDeserializeAsOwned}, - CellParser, CellParserError, - }, + de::{r#as::CellDeserializeAs, CellParser, CellParserError}, r#as::{Ref, Same}, ser::{r#as::CellSerializeAs, CellBuilder, CellBuilderError}, - Cell, + Cell, ResultExt, }; /// ```tlb @@ -23,10 +20,10 @@ where { #[inline] fn store_as(source: &T, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { - builder.store(source.into_iter().try_fold(Cell::new(), |prev, v| { + builder.store(source.into_iter().try_fold(Cell::builder(), |prev, v| { let mut list = Cell::builder(); list.store_as::<_, Ref>(prev)?.store_as::<_, As>(v)?; - Ok(list.into_cell()) + Ok(list) })?)?; Ok(()) } @@ -34,16 +31,18 @@ where impl<'de, T, As> CellDeserializeAs<'de, Vec> for List where - As: CellDeserializeAsOwned, + As: CellDeserializeAs<'de, T>, { #[inline] fn parse_as(parser: &mut CellParser<'de>) -> Result, CellParserError<'de>> { let mut v = Vec::new(); - let mut cell: Cell = parser.parse()?; - while !cell.references.is_empty() { - let mut p = cell.parser(); - v.push(p.parse_as::<_, As>()?); - cell = p.parse_as::<_, Ref>()?; + let mut p: CellParser<'de> = parser.parse()?; + while !p.no_references_left() { + v.push( + p.parse_as::<_, As>() + .with_context(|| format!("[{}]", v.len()))?, + ); + p = p.parse_as::<_, Ref>()?; } v.reverse(); Ok(v) diff --git a/crates/tlb-ton/src/message.rs b/crates/tlb-ton/src/message.rs index 1bc1f1f..1cff2a3 100644 --- a/crates/tlb-ton/src/message.rs +++ b/crates/tlb-ton/src/message.rs @@ -11,7 +11,7 @@ use tlb::{ either::Either, r#as::{DefaultOnNone, Ref, Same}, ser::{CellBuilder, CellBuilderError, CellSerialize, CellSerializeExt}, - Cell, + Cell, ResultExt, }; use crate::{ @@ -40,11 +40,13 @@ where IC: CellSerialize, ID: CellSerialize, { + #[inline] pub fn with_state_init(mut self, state_init: impl Into>>) -> Self { self.init = state_init.into(); self } + #[inline] pub fn normalize(&self) -> Result { Ok(Message { info: self.info.clone(), @@ -93,12 +95,14 @@ where { fn parse(parser: &mut CellParser<'de>) -> Result> { Ok(Self { - info: parser.parse()?, + info: parser.parse().context("info")?, init: parser - .parse_as::<_, Option>>()? + .parse_as::<_, Option>>() + .context("init")? .map(Either::into_inner), body: parser - .parse_as::, Either>()? + .parse_as::, Either>() + .context("body")? .into_inner(), }) } @@ -131,6 +135,7 @@ impl CommonMsgInfo { } impl CellSerialize for CommonMsgInfo { + #[inline] fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { match self { Self::Internal(msg) => builder @@ -151,15 +156,20 @@ impl CellSerialize for CommonMsgInfo { } impl<'de> CellDeserialize<'de> for CommonMsgInfo { + #[inline] fn parse(parser: &mut CellParser<'de>) -> Result> { match parser.unpack()? { // int_msg_info$0 - false => Ok(Self::Internal(parser.parse()?)), + false => Ok(Self::Internal(parser.parse().context("int_msg_info")?)), true => match parser.unpack()? { // ext_in_msg_info$10 - false => Ok(Self::ExternalIn(parser.unpack()?)), + false => Ok(Self::ExternalIn( + parser.unpack().context("ext_in_msg_info")?, + )), // ext_out_msg_info$11 - true => Ok(Self::ExternalOut(parser.unpack()?)), + true => Ok(Self::ExternalOut( + parser.unpack().context("ext_out_msg_info")?, + )), }, } } @@ -241,9 +251,9 @@ impl<'de> CellDeserialize<'de> for InternalMsgInfo { ihr_disabled: parser.unpack()?, bounce: parser.unpack()?, bounced: parser.unpack()?, - src: parser.unpack()?, - dst: parser.unpack()?, - value: parser.parse()?, + src: parser.unpack().context("src")?, + dst: parser.unpack().context("dst")?, + value: parser.parse().context("value")?, ihr_fee: parser.unpack_as::<_, Grams>()?, fwd_fee: parser.unpack_as::<_, Grams>()?, created_lt: parser.unpack()?, diff --git a/crates/tlb/src/lib.rs b/crates/tlb/src/lib.rs index e9f843a..3174a15 100644 --- a/crates/tlb/src/lib.rs +++ b/crates/tlb/src/lib.rs @@ -30,7 +30,7 @@ //! # r#as::Ref, //! # bits::{r#as::{NBits, VarInt}, ser::BitWriterExt}, //! # Cell, -//! # ser::{CellSerialize, CellBuilder, CellBuilderError}, +//! # ser::{CellSerialize, CellBuilder, CellBuilderError, CellSerializeExt}, //! # StringError, //! # }; //! # @@ -55,16 +55,13 @@ //! } //! //! # fn main() -> Result<(), StringError> { -//! // create a builder -//! let mut builder = Cell::builder(); -//! // serialize value into builder -//! builder.store(Hello { +//! // serialize value into cell +//! let hello = Hello { //! query_id: 0, //! amount: 1_000u64.into(), //! payload: None, -//! })?; -//! // convert builder into cell -//! let cell = builder.into_cell(); +//! }; +//! let cell = hello.to_cell()?; //! # Ok(()) //! # } //! ``` @@ -82,7 +79,7 @@ //! # Cell, //! # de::{CellDeserialize, CellParser, CellParserError}, //! # Error, -//! # ser::{CellSerialize, CellBuilder, CellBuilderError}, +//! # ser::{CellSerialize, CellBuilder, CellBuilderError, CellSerializeExt}, //! # StringError, //! # }; //! # #[derive(Debug, PartialEq)] @@ -129,9 +126,7 @@ //! # amount: 1_000u64.into(), //! # payload: None, //! # }; -//! # let mut builder = Cell::builder(); -//! # builder.store(&orig)?; -//! # let cell = builder.into_cell(); +//! # let cell = orig.to_cell()?; //! let mut parser = cell.parser(); //! let hello: Hello = parser.parse()?; //! # assert_eq!(hello, orig); diff --git a/crates/tlb/src/ser/builder.rs b/crates/tlb/src/ser/builder.rs index 19ce2de..9ceda9d 100644 --- a/crates/tlb/src/ser/builder.rs +++ b/crates/tlb/src/ser/builder.rs @@ -5,6 +5,7 @@ use crate::{ bitvec::{order::Msb0, slice::BitSlice, vec::BitVec}, ser::{BitWriter, LimitWriter}, }, + r#as::Ref, Cell, Error, ResultExt, }; @@ -228,3 +229,11 @@ impl BitWriter for CellBuilder { self.data.repeat_bit(n, bit) } } + +impl CellSerialize for CellBuilder { + fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> { + builder.write_bitslice(&self.data)?; + builder.store_many_as::<_, Ref>(&self.references)?; + Ok(()) + } +}