tonlib_core/wallet/versioned/
v5.rs

1use crate::cell::{ArcCell, CellBuilder, CellParser, TonCellError};
2use crate::tlb_types::block::out_action::{OutAction, OutActionSendMsg, OutList};
3use crate::tlb_types::primitives::reference::Ref;
4use crate::tlb_types::tlb::{TLBPrefix, TLB};
5use crate::types::TonHash;
6use crate::wallet::versioned::utils::validate_msgs_count;
7
8/// WalletVersion::V5R1
9/// https://github.com/ton-blockchain/wallet-contract-v5/blob/main/types.tlb#L29
10#[derive(Debug, PartialEq, Clone)]
11pub struct WalletDataV5 {
12    pub signature_allowed: bool,
13    pub seqno: u32,
14    pub wallet_id: i32,
15    pub public_key: TonHash,
16    pub extensions: Option<Ref<ArcCell>>,
17}
18
19/// https://docs.ton.org/participate/wallets/contracts#wallet-v5
20/// signature is not considered as part of msg body
21/// https://github.com/ton-blockchain/wallet-contract-v5/blob/main/types.tlb
22/// This implementation support only jetton transfer messages
23#[derive(Debug, PartialEq, Clone)]
24pub struct WalletExtMsgBodyV5 {
25    pub wallet_id: i32,
26    pub valid_until: u32,
27    pub msg_seqno: u32,
28    pub msgs_modes: Vec<u8>,
29    pub msgs: Vec<ArcCell>,
30}
31
32impl WalletDataV5 {
33    pub fn new(wallet_id: i32, public_key: TonHash) -> Self {
34        Self {
35            signature_allowed: true,
36            seqno: 0,
37            wallet_id,
38            public_key,
39            extensions: None,
40        }
41    }
42}
43
44impl TLB for WalletDataV5 {
45    fn read_definition(parser: &mut CellParser) -> Result<Self, TonCellError> {
46        Ok(Self {
47            signature_allowed: parser.load_bit()?,
48            seqno: parser.load_u32(32)?,
49            wallet_id: parser.load_i32(32)?,
50            public_key: parser.load_tonhash()?,
51            extensions: TLB::read(parser)?,
52        })
53    }
54
55    fn write_definition(&self, dst: &mut CellBuilder) -> Result<(), TonCellError> {
56        dst.store_bit(self.signature_allowed)?;
57        dst.store_u32(32, self.seqno)?;
58        dst.store_i32(32, self.wallet_id)?;
59        dst.store_tonhash(&self.public_key)?;
60        self.extensions.write(dst)?;
61        Ok(())
62    }
63}
64
65impl TLB for WalletExtMsgBodyV5 {
66    const PREFIX: TLBPrefix = TLBPrefix::new(32, 0x7369676e);
67    fn read_definition(parser: &mut CellParser) -> Result<Self, TonCellError> {
68        let wallet_id = parser.load_i32(32)?;
69        let valid_until = parser.load_u32(32)?;
70        let msg_seqno = parser.load_u32(32)?;
71        let inner_request = InnerRequest::read(parser)?;
72        let (msgs, msgs_modes) = parse_inner_request(inner_request)?;
73        Ok(Self {
74            wallet_id,
75            valid_until,
76            msg_seqno,
77            msgs_modes,
78            msgs,
79        })
80    }
81
82    fn write_definition(&self, dst: &mut CellBuilder) -> Result<(), TonCellError> {
83        dst.store_i32(32, self.wallet_id)?;
84        dst.store_u32(32, self.valid_until)?;
85        dst.store_u32(32, self.msg_seqno)?;
86        let inner_req = build_inner_request(&self.msgs, &self.msgs_modes)?;
87        inner_req.write(dst)?;
88        Ok(())
89    }
90}
91
92// https://github.com/ton-blockchain/wallet-contract-v5/blob/88557ebc33047a95207f6e47ac8aadb102dff744/types.tlb#L26
93#[derive(Debug, PartialEq, Clone)]
94pub(super) struct InnerRequest {
95    out_actions: Option<Ref<OutList>>, // tlb tells there is Option<OutList>, but it lies
96                                       // other_actions: Option<()> unsupported
97}
98
99impl TLB for InnerRequest {
100    fn read_definition(parser: &mut CellParser) -> Result<Self, TonCellError> {
101        let out_actions = TLB::read(parser)?;
102        if parser.load_bit()? {
103            return Err(TonCellError::InternalError(
104                "other_actions parsing is unsupported".to_string(),
105            ));
106        }
107        Ok(Self { out_actions })
108    }
109
110    fn write_definition(&self, dst: &mut CellBuilder) -> Result<(), TonCellError> {
111        self.out_actions.write(dst)?;
112        dst.store_bit(false)?; // other_actions are not supported
113        Ok(())
114    }
115}
116
117fn parse_inner_request(request: InnerRequest) -> Result<(Vec<ArcCell>, Vec<u8>), TonCellError> {
118    let mut out_list = match request.out_actions {
119        Some(out_list) => out_list.0,
120        None => return Ok((vec![], vec![])),
121    };
122    let mut msgs = vec![];
123    let mut msgs_modes = vec![];
124    while let OutList::Some(action) = out_list {
125        if let OutAction::SendMsg(action_send_msg) = &action.action {
126            msgs.push(action_send_msg.out_msg.clone());
127            msgs_modes.push(action_send_msg.mode);
128        } else {
129            let err_str = format!("Unsupported OutAction: {action:?}");
130            return Err(TonCellError::InvalidCellData(err_str));
131        }
132        out_list = TLB::from_cell(&action.prev.0)?;
133    }
134
135    Ok((msgs, msgs_modes))
136}
137
138fn build_inner_request(msgs: &[ArcCell], msgs_modes: &[u8]) -> Result<InnerRequest, TonCellError> {
139    validate_msgs_count(msgs, msgs_modes, 255)?;
140    // TODO suboptimal - can be done in 1 pass, but here we have 1 loop pass + recursion in OutList
141    let mut actions = vec![];
142    for (msg, mode) in msgs.iter().zip(msgs_modes.iter()) {
143        let action = OutActionSendMsg {
144            mode: *mode,
145            out_msg: msg.clone(),
146        };
147        actions.push(OutAction::SendMsg(action));
148    }
149
150    let out_list = OutList::new(&actions)?;
151
152    let req = InnerRequest {
153        out_actions: Some(Ref::new(out_list)),
154    };
155    Ok(req)
156}
157
158#[cfg(test)]
159mod test {
160    use super::*;
161    use crate::cell::Cell;
162    use crate::tlb_types::tlb::TLB;
163    use crate::wallet::versioned::{DEFAULT_WALLET_ID_V5R1, DEFAULT_WALLET_ID_V5R1_TESTNET};
164
165    #[test]
166    fn test_wallet_data_v5() -> anyhow::Result<()> {
167        // https://tonviewer.com/UQDwj2jGHWEbPpDf0I2qktDwqtv6tBCfBVNH9gJEnM-QmHDa
168        let src_boc_hex = "b5ee9c7241010101002b00005180000000bfffff88e5f9bbe4db9b026385fb9a446ee75d0a7bb1dd77956387b468eb01950900a4fa20cbe13a2a";
169        let wallet_data = WalletDataV5::from_boc_hex(src_boc_hex)?;
170        assert_eq!(wallet_data.seqno, 1);
171        assert_eq!(wallet_data.wallet_id, DEFAULT_WALLET_ID_V5R1);
172        assert_eq!(
173            wallet_data.public_key,
174            TonHash::from_hex("cbf377c9b73604c70bf73488ddceba14f763baef2ac70f68d1d6032a120149f4")?
175        );
176        assert_eq!(wallet_data.extensions, None);
177
178        let serial_boc_hex = wallet_data.to_boc_hex(true)?;
179        assert_eq!(src_boc_hex, serial_boc_hex);
180        let restored = WalletDataV5::from_boc_hex(&serial_boc_hex)?;
181        assert_eq!(wallet_data, restored);
182        Ok(())
183    }
184
185    #[test]
186    fn test_wallet_data_v5_testnet() -> anyhow::Result<()> {
187        let src_boc_hex = "b5ee9c7201010101002b000051800000013ffffffed2b31b23dbe5144a626b9d5d1d4208e36d97e4adb472d42c073bfff85b3107e4a0";
188        let wallet_data = WalletDataV5::from_boc_hex(src_boc_hex)?;
189        assert_eq!(wallet_data.seqno, 2);
190        assert_eq!(wallet_data.wallet_id, DEFAULT_WALLET_ID_V5R1_TESTNET);
191        Ok(())
192    }
193
194    #[test]
195    fn test_wallet_ext_msg_body_v5() -> anyhow::Result<()> {
196        // https://tonviewer.com/transaction/b4c5eddc52d0e23dafb2da6d022a5b6ae7eba52876fa75d32b2a95fa30c7e2f0
197        let body_hex = "b5ee9c720101040100940001a17369676e7fffff11ffffffff00000000bc04889cb28b36a3a00810e363a413763ec34860bf0fce552c5d36e37289fafd442f1983d740f92378919d969dd530aec92d258a0779fb371d4659f10ca1b3826001020a0ec3c86d030302006642007847b4630eb08d9f486fe846d5496878556dfd5a084f82a9a3fb01224e67c84c187a1200000000000000000000000000000000";
198        let body_cell = Cell::from_boc_hex(body_hex)?;
199        let mut body_parser = body_cell.parser();
200        let body = WalletExtMsgBodyV5::read(&mut body_parser)?;
201        let sign = body_parser.load_bytes(64)?;
202
203        assert_eq!(body.wallet_id, DEFAULT_WALLET_ID_V5R1);
204        assert_eq!(body.valid_until, 4294967295);
205        assert_eq!(body.msg_seqno, 0);
206        assert_eq!(body.msgs_modes, vec![3]);
207        assert_eq!(body.msgs.len(), 1);
208
209        let serial_cell = body.to_cell()?;
210        let signed_serial = CellBuilder::new()
211            .store_cell(&serial_cell)?
212            .store_slice(&sign)?
213            .build()?;
214
215        assert_eq!(body_cell, signed_serial);
216        let parsed_back = WalletExtMsgBodyV5::from_cell(&signed_serial)?;
217        assert_eq!(body, parsed_back);
218        Ok(())
219    }
220}