1use anyhow::Result;
2use tycho_types::models::{CurrencyCollection, IntAddr, MsgInfo, StateInit};
3use tycho_types::num::Tokens;
4use tycho_types::prelude::*;
5
6use crate::ExecutorState;
7use crate::util::{ExtStorageStat, StorageStatLimits};
8
9impl ExecutorState<'_> {
10 pub fn receive_in_msg(&mut self, msg_root: Cell) -> Result<ReceivedMessage> {
24 let is_masterchain = self.address.is_masterchain();
25
26 let is_external;
27 let bounce_enabled;
28 let mut msg_balance_remaining;
29
30 let mut slice = msg_root.as_slice_allow_exotic();
32 match MsgInfo::load_from(&mut slice)? {
33 MsgInfo::Int(info) => {
35 self.check_message_dst(&info.dst)?;
36
37 is_external = false;
39 bounce_enabled = info.bounce;
40
41 msg_balance_remaining = info.value;
43 msg_balance_remaining.try_add_assign_tokens(info.ihr_fee)?;
44
45 if info.created_lt >= self.start_lt {
47 self.start_lt = info.created_lt + 1;
48 self.end_lt = self.start_lt + 1;
49 }
50 }
51 MsgInfo::ExtIn(info) => {
53 self.check_message_dst(&info.dst)?;
54
55 is_external = true;
57 bounce_enabled = false;
58
59 let Some(mut stats) =
61 ExtStorageStat::compute_for_slice(&slice, StorageStatLimits {
62 bit_count: self.config.size_limits.max_msg_bits,
63 cell_count: self.config.size_limits.max_msg_cells,
64 })
65 else {
66 anyhow::bail!("inbound message limits exceeded");
67 };
68
69 stats.cell_count -= 1; stats.bit_count -= slice.size_bits() as u64; let fwd_fee = if self.is_special {
73 Tokens::ZERO
76 } else {
77 self.config
78 .fwd_prices(is_masterchain)
79 .compute_fwd_fee(stats)
80 };
81
82 if self.balance.tokens < fwd_fee {
84 anyhow::bail!("cannot pay for importing an external message");
85 }
86 self.balance.tokens -= fwd_fee;
87 self.total_fees.try_add_assign(fwd_fee)?;
88
89 msg_balance_remaining = CurrencyCollection::ZERO;
91 }
92 MsgInfo::ExtOut(_) => anyhow::bail!("unexpected incoming ExtOut message"),
94 }
95
96 let init = if slice.load_bit()? {
98 Some(if slice.load_bit()? {
99 let state_root = slice.load_reference_cloned()?;
101 anyhow::ensure!(
102 !state_root.is_exotic(),
103 "state init must be an ordinary cell"
104 );
105
106 let mut slice = state_root.as_slice_allow_exotic();
107 let parsed = StateInit::load_from(&mut slice)?;
108 anyhow::ensure!(slice.is_empty(), "state init contains extra data");
109
110 MsgStateInit {
111 root: state_root,
112 parsed,
113 }
114 } else {
115 let mut state_init_cs = slice;
117
118 let parsed = StateInit::load_from(&mut slice)?;
120 state_init_cs.skip_last(slice.size_bits(), slice.size_refs())?;
122 let state_root = CellBuilder::build_from(state_init_cs)?;
123
124 MsgStateInit {
125 root: state_root,
126 parsed,
127 }
128 })
129 } else {
130 None
131 };
132
133 let body = if slice.load_bit()? {
135 let body_cell = slice.load_reference_cloned()?;
137 anyhow::ensure!(slice.is_empty(), "message contains extra data");
138
139 CellSliceParts::from(body_cell)
140 } else {
141 (slice.range(), msg_root.clone())
143 };
144
145 if self.config.is_blackhole(&self.address) {
147 self.burned = msg_balance_remaining.tokens;
148 msg_balance_remaining.tokens = Tokens::ZERO;
149 }
150
151 Ok(ReceivedMessage {
153 root: msg_root,
154 init,
155 body,
156 is_external,
157 bounce_enabled,
158 balance_remaining: msg_balance_remaining,
159 })
160 }
161
162 fn check_message_dst(&self, dst: &IntAddr) -> Result<()> {
163 match dst {
164 IntAddr::Std(dst) => {
165 anyhow::ensure!(dst.anycast.is_none(), "anycast is not supported");
166 anyhow::ensure!(*dst == self.address, "message destination address mismatch");
167 Ok(())
168 }
169 IntAddr::Var(_) => anyhow::bail!("`addr_var` is not supported"),
170 }
171 }
172}
173
174#[derive(Debug, Clone)]
176pub struct ReceivedMessage {
177 pub root: Cell,
179 pub init: Option<MsgStateInit>,
181 pub body: CellSliceParts,
183
184 pub is_external: bool,
186 pub bounce_enabled: bool,
188
189 pub balance_remaining: CurrencyCollection,
192}
193
194#[derive(Debug, Clone)]
196pub struct MsgStateInit {
197 pub root: Cell,
199 pub parsed: StateInit,
201}
202
203impl MsgStateInit {
204 pub fn root_hash(&self) -> &HashBytes {
205 self.root.repr_hash()
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use tycho_types::models::{BurningConfig, ExtInMsgInfo, ExtOutMsgInfo, IntMsgInfo, StdAddr};
212 use tycho_types::num::Tokens;
213
214 use super::*;
215 use crate::tests::{
216 make_big_tree, make_custom_config, make_default_config, make_default_params, make_message,
217 };
218
219 const OK_BALANCE: Tokens = Tokens::new(10_000_000_000);
220 const STUB_ADDR: StdAddr = StdAddr::new(0, HashBytes::ZERO);
221
222 #[test]
225 fn receive_ext_in_works() {
226 let params = make_default_params();
227 let config = make_default_config();
228
229 let mut state = ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE);
230 let prev_start_lt = state.start_lt;
231 let prev_end_lt = state.end_lt;
232 let prev_balance = state.balance.clone();
233 let prev_acc_state = state.state.clone();
234
235 let msg_root = make_message(
236 ExtInMsgInfo {
237 dst: STUB_ADDR.into(),
238 ..Default::default()
239 },
240 Some(StateInit::default()),
241 None,
242 );
243 let msg = state.receive_in_msg(msg_root.clone()).unwrap();
244 assert!(msg.is_external);
246 assert!(!msg.bounce_enabled);
247 {
248 let init = msg.init.unwrap();
249 let target = StateInit::default();
250 assert_eq!(init.parsed, target);
251 let target_hash = *CellBuilder::build_from(&target).unwrap().repr_hash();
252 assert_eq!(init.root_hash(), &target_hash);
253 }
254 assert!(msg.body.0.is_empty());
255 assert_eq!(msg.balance_remaining, CurrencyCollection::ZERO);
256
257 assert_eq!(state.start_lt, prev_start_lt);
259 assert_eq!(state.end_lt, prev_end_lt);
260 assert_eq!(
263 state.total_fees,
264 Tokens::new(config.fwd_prices.lump_price as _)
265 );
266 assert_eq!(state.balance.other, prev_balance.other);
268 assert_eq!(state.balance.tokens, prev_balance.tokens - state.total_fees);
270 assert_eq!(state.state, prev_acc_state);
272 }
273
274 #[test]
275 fn receive_int_to_non_existent() {
276 let params = make_default_params();
277 let config = make_default_config();
278
279 let mut state = ExecutorState::new_non_existent(¶ms, &config, &STUB_ADDR);
280 let prev_start_lt = state.start_lt;
281 assert_eq!(prev_start_lt, 0);
282 let prev_balance = state.balance.clone();
283 let prev_acc_state = state.state.clone();
284
285 let msg_lt = 1000;
286 let msg_root = make_message(
287 IntMsgInfo {
288 dst: STUB_ADDR.into(),
289 value: OK_BALANCE.into(),
290 bounce: true,
291 created_lt: msg_lt,
292 ..Default::default()
293 },
294 None,
295 Some({
296 let mut b = CellBuilder::new();
297 b.store_u32(0xdeafbeaf).unwrap();
298 b
299 }),
300 );
301 let msg = state.receive_in_msg(msg_root.clone()).unwrap();
302 assert!(!msg.is_external);
304 assert!(msg.bounce_enabled);
305 assert!(msg.init.is_none());
306 assert_eq!(
307 CellSlice::apply(&msg.body).unwrap().load_u32().unwrap(),
308 0xdeafbeaf
309 );
310 assert_eq!(msg.balance_remaining, OK_BALANCE.into());
311
312 assert_eq!(state.start_lt, msg_lt + 1);
314 assert_eq!(state.end_lt, state.start_lt + 1);
315 assert_eq!(state.total_fees, Tokens::ZERO);
317 assert_eq!(state.balance, prev_balance);
319 assert_eq!(state.state, prev_acc_state);
321 }
322
323 #[test]
324 fn receive_int_to_blackhole() {
325 let addr = StdAddr::new(-1, HashBytes::ZERO);
326
327 let params = make_default_params();
328 let config = make_custom_config(|config| {
329 config.set_burning_config(&BurningConfig {
330 blackhole_addr: Some(addr.address),
331 ..Default::default()
332 })?;
333 Ok(())
334 });
335
336 let mut state = ExecutorState::new_uninit(¶ms, &config, &addr, OK_BALANCE);
337 let prev_start_lt = state.start_lt;
338 assert_eq!(prev_start_lt, 0);
339 let prev_balance = state.balance.clone();
340 let prev_acc_state = state.state.clone();
341
342 let msg_lt = 1000;
343 let msg_root = make_message(
344 IntMsgInfo {
345 dst: addr.into(),
346 value: OK_BALANCE.into(),
347 bounce: true,
348 created_lt: msg_lt,
349 ..Default::default()
350 },
351 None,
352 None,
353 );
354 let msg = state.receive_in_msg(msg_root).unwrap();
355 assert!(!msg.is_external);
357 assert!(msg.bounce_enabled);
358 assert!(msg.init.is_none());
359 assert!(msg.body.0.is_empty());
360 assert_eq!(msg.balance_remaining, CurrencyCollection::ZERO);
361
362 assert_eq!(state.start_lt, msg_lt + 1);
364 assert_eq!(state.end_lt, state.start_lt + 1);
365 assert_eq!(state.total_fees, Tokens::ZERO);
367 assert_eq!(state.balance, prev_balance);
369 assert_eq!(state.state, prev_acc_state);
371 assert_eq!(state.burned, OK_BALANCE);
373 }
374
375 #[test]
378 fn receive_ext_out() {
379 let params = make_default_params();
380 let config = make_default_config();
381
382 ExecutorState::new_non_existent(¶ms, &config, &STUB_ADDR)
383 .receive_in_msg(make_message(
384 ExtOutMsgInfo {
385 dst: None,
386 src: STUB_ADDR.into(),
387 created_at: 1,
388 created_lt: 1,
389 },
390 None,
391 None,
392 ))
393 .inspect_err(|e| println!("{e}"))
394 .unwrap_err();
395 }
396
397 #[test]
398 fn receive_ext_in_on_non_existent() {
399 let params = make_default_params();
400 let config = make_default_config();
401
402 ExecutorState::new_non_existent(¶ms, &config, &STUB_ADDR)
403 .receive_in_msg(make_message(
404 ExtInMsgInfo {
405 dst: STUB_ADDR.into(),
406 ..Default::default()
407 },
408 None,
409 None,
410 ))
411 .inspect_err(|e| println!("{e}"))
412 .unwrap_err();
413 }
414
415 #[test]
416 fn receive_ext_in_not_enough_balance() {
417 let params = make_default_params();
418 let config = make_default_config();
419
420 ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, Tokens::new(1))
421 .receive_in_msg(make_message(
422 ExtInMsgInfo {
423 dst: STUB_ADDR.into(),
424 ..Default::default()
425 },
426 None,
427 None,
428 ))
429 .inspect_err(|e| println!("{e}"))
430 .unwrap_err();
431 }
432
433 #[test]
434 fn receive_internal_balance_overflow() {
435 let params = make_default_params();
436 let config = make_default_config();
437
438 ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, Tokens::MAX)
439 .receive_in_msg(make_message(
440 IntMsgInfo {
441 dst: STUB_ADDR.into(),
442 value: Tokens::MAX.into(),
443 ihr_fee: Tokens::MAX,
444 ..Default::default()
445 },
446 None,
447 None,
448 ))
449 .inspect_err(|e| println!("{e}"))
450 .unwrap_err();
451 }
452
453 #[test]
454 fn receive_with_invalid_dst() {
455 let params = make_default_params();
456 let config = make_default_config();
457
458 let other_addr = StdAddr::new(0, HashBytes([1; 32]));
459
460 ExecutorState::new_non_existent(¶ms, &config, &STUB_ADDR)
462 .receive_in_msg(make_message(
463 ExtInMsgInfo {
464 dst: other_addr.clone().into(),
465 ..Default::default()
466 },
467 None,
468 None,
469 ))
470 .inspect_err(|e| println!("{e}"))
471 .unwrap_err();
472
473 ExecutorState::new_non_existent(¶ms, &config, &STUB_ADDR)
475 .receive_in_msg(make_message(
476 IntMsgInfo {
477 dst: other_addr.clone().into(),
478 ..Default::default()
479 },
480 None,
481 None,
482 ))
483 .inspect_err(|e| println!("{e}"))
484 .unwrap_err();
485 }
486
487 #[test]
488 fn invalid_message_structure() -> anyhow::Result<()> {
489 let params = make_default_params();
490 let config = make_default_config();
491 let cx = Cell::empty_context();
492
493 let run_on_uninit = |msg| {
494 ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE)
495 .receive_in_msg(msg)
496 .inspect_err(|e| println!("{e}"))
497 .unwrap_err();
498 };
499
500 run_on_uninit(CellBuilder::build_from(0xdeafbeafu32)?);
502
503 run_on_uninit({
505 let mut b = CellBuilder::new();
506 MsgInfo::Int(IntMsgInfo {
508 dst: STUB_ADDR.into(),
509 ..Default::default()
510 })
511 .store_into(&mut b, cx)?;
512
513 b.store_bit_one()?;
515 b.store_bit_one()?;
516 b.store_reference(CellBuilder::build_from(0xdeafbeafu32)?)?;
517
518 b.store_bit_zero()?;
520
521 b.build()?
523 });
524
525 run_on_uninit({
527 let mut b = CellBuilder::new();
528 MsgInfo::Int(IntMsgInfo {
530 dst: STUB_ADDR.into(),
531 ..Default::default()
532 })
533 .store_into(&mut b, cx)?;
534
535 b.store_bit_one()?;
537 b.store_bit_one()?;
538 b.store_reference({
539 let mut b = CellBuilder::new();
540 StateInit::default().store_into(&mut b, cx)?;
541 b.store_u32(0xdeafbeaf)?;
542 b.build()?
543 })?;
544
545 b.store_bit_zero()?;
547
548 b.build()?
550 });
551
552 run_on_uninit({
554 let mut b = CellBuilder::new();
555 MsgInfo::Int(IntMsgInfo {
557 dst: STUB_ADDR.into(),
558 ..Default::default()
559 })
560 .store_into(&mut b, cx)?;
561
562 b.store_bit_zero()?;
564
565 b.store_bit_one()?;
567 b.store_reference(Cell::empty_cell())?;
568 b.store_u32(0xdeafbeaf)?;
569
570 b.build()?
572 });
573
574 Ok(())
575 }
576
577 #[test]
578 #[cfg_attr(miri, ignore)]
579 fn msg_out_of_limits() {
580 let params = make_default_params();
581 let config = make_default_config();
582
583 let body = make_big_tree(8, &mut 0, config.size_limits.max_msg_cells as u16 + 10);
584
585 ExecutorState::new_uninit(¶ms, &config, &STUB_ADDR, OK_BALANCE)
586 .receive_in_msg(make_message(
587 ExtInMsgInfo {
588 dst: STUB_ADDR.into(),
589 ..Default::default()
590 },
591 None,
592 Some({
593 let mut b = CellBuilder::new();
594 b.store_slice(body.as_slice_allow_exotic()).unwrap();
595 b
596 }),
597 ))
598 .inspect_err(|e| println!("{e}"))
599 .unwrap_err();
600 }
601}